commit ccc5f5b01bc6833bf6bcf449917fc017eb006aef Author: Xavier Date: Wed Sep 27 01:12:35 2023 -0400 Initial Commit diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..904aa1f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "simplebyteunit" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c420f6a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "simplebyteunit" +authors = ["Xavier R.M."] +version = "0.1.0" +edition = "2021" diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c57bde --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# SimpleByteUnit + +SimpleByteUnit is a crate which provides a thin encapsulate for integer primitives to facilitate a fast, simple, yet ergonomic byteunit implementation. + +## Usage + +Add 'simplebyteunit' to your 'Cargo.toml': + +``` +[dependencies] +simplebyteunit = "0.1.0" +``` + +## Example + +Generate a human-readable, formatted ByteUnit: + +``` +use simplebyteunit::simplebyteunit::*; + +let byteunit_var = 500000.to_byteunit(SI); + +println!("{byteunit_var}"); + +``` + +Output: + +``` +500 KB +``` diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..4be6772 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,56 @@ +/* + * SimpleByteUnit + * + * Copyright 2023 Xaver R.M. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +use crate::simplebyteunit::*; + +pub fn parse_input(s: &str) -> Result<(f64, f64, i8, bool), Error> { + let v = match s.to_lowercase() { + string if string.ends_with("kib") => ("kib", K, true), + string if string.ends_with("mib") => ("mib", M, true), + string if string.ends_with("gib") => ("gib", G, true), + string if string.ends_with("tib") => ("tib", T, true), + string if string.ends_with("pib") => ("pib", P, true), + string if string.ends_with("eib") => ("eib", E, true), + string if string.ends_with("kb") => ("kb", K, false), + string if string.ends_with("mb") => ("mb", M, false), + string if string.ends_with("gb") => ("gb", G, false), + string if string.ends_with("tb") => ("tb", T, false), + string if string.ends_with("pb") => ("pb", P, false), + string if string.ends_with("eb") => ("eb", E, false), + string if string.ends_with("b") => ("b", B, false), + _ => Err(Error::InvalidUnit(format!("{s} contains no supported nor valid byteunits.")))? + }; + let s = s.to_lowercase() + .replace(v.0, "") + .replace(" ", ""); + let multiplier = match v.2 { true => 1024.0, false => 1000.0 }; + + match s.parse() { + Ok(val) => Ok((val, multiplier, v.1, v.2)), + Err(_) => Err(Error::ErroroneousInput(format!("{s} contains an invalid float or integer value."))) + } +} + +pub fn input_arithmetic(input: (f64, f64, i8, bool)) -> (bool, i64) { + let iec = input.3; + let power_of = input.2; + let multiplier = input.1; + let mut value = input.0; + let mut power: i8 = 0; + + while power < power_of { + value = value * multiplier; + power += 1; + } + + (iec, value as i64) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..feb2aeb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,128 @@ +/* + * SimpleByteUnit + * + * Copyright 2023 Xaver R.M. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/*! +A thin encapsulate for integer primitives to facilitate a fast, simple, yet ergonomic byteunit implementation. + +# Getting Started + +Add 'simplebyteunit' to your 'Cargo.toml': + +```toml +[dependencies] +simplebyteunit = "0.1.0" +``` + +## Example + +Generate a human-readable, formatted ByteUnit: + + +```rust +use simplebyteunit::simplebyteunit::*; + +let byteunit_var = 500000.to_byteunit(SI); + +println!("{byteunit_var}"); + +``` + +Output: + +```shell +500 KB +```` + +## Parsing strings into ByteUnits + +And then you can parse formatted strings back into a ByteUnit + +```rust +use simplebyteunit::simplebyteunit::*; + +let byteunit_var: ByteUnit = "500 KB".into(); + +println!("{byteunit_var}"); + +``` + +Output: + +```shell +500 KB +```` + +## Simple arithmetic operations + +Addition, subtraction, multiplication, subtraction, and division are supported on this type. + +```rust +use simplebyteunit::simplebyteunit::*; + +let a: ByteUnit = ByteUnit::SI(5000000); +let b: ByteUnit = ByteUnit::SI(5000000); +let byteunit_sum = a + b; + +println!("{byteunit_sum}"); + +``` + +Output: + +```shell +1.0 MB +``` + +## Equal/and/or operations + +Equal operations are supported on this type: + +```rust +use simplebyteunit::simplebyteunit::*; + +let a: ByteUnit = "500 KiB".into(); +let b: ByteUnit = "500 KiB".into(); +let byteunit_bool = a == b; + +println!("{byteunit_bool}"); + +``` + +Output: +```shell +true +```` + +Or operations are supported directly on this type: + +```rust +use simplebyteunit::simplebyteunit::*; + +let a: ByteUnit = 5000000.to_byteunit(IEC); +let b: ByteUnit = 5000000.to_byteunit(IEC); +let byteunit_bool = a >= b; + +println!("{byteunit_bool}"); + +``` + +Output: +```shell +true +``` + +*/ + +pub mod simplebyteunit; + +mod input; +mod test; diff --git a/src/simplebyteunit.rs b/src/simplebyteunit.rs new file mode 100644 index 0000000..788c134 --- /dev/null +++ b/src/simplebyteunit.rs @@ -0,0 +1,276 @@ +/* + * SimpleByteUnit + * + * Copyright 2023 Xaver R.M. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/*! +Fast, stupid simple ByteUnit implementation + +Provides a simple way to encapsulate primitives as byteunits. +*/ + +use std::{fmt::{Display, Formatter}, + ops::{Mul, Div, Add, Sub}, str::FromStr}; +use crate::input::{parse_input, input_arithmetic}; + +/// IEC ByteUnit (x*1024) +pub const IEC: ByteUnit<()> = ByteUnit::IEC(()); +/// SI ByteUnit (x*1000) +pub const SI: ByteUnit<()> = ByteUnit::SI(()); +/// Power of 6 (Exa/Exbi) +pub const E: i8 = 6; +/// Power of 5 (Peta/Pebi) +pub const P: i8 = 5; +/// Power of 4 (Tera/Tebi) +pub const T: i8 = 4; +/// Power of 3 (Giga/Gibi) +pub const G: i8 = 3; +/// Power of 2 (Mega/Mebi) +pub const M: i8 = 2; +/// Power of 1 (Kilo/Kibi) +pub const K: i8 = 1; +/// Base unit +pub const B: i8 = 0; + +/// Thin encapsulate of a supported, primitive integer to provide simple byteunit facilities +pub enum ByteUnit { + IEC(T), + SI(T), +} + +#[derive(Debug)] +pub enum Error { + InvalidUnit(String), + ErroroneousInput(String), +} + +/// Trait providing generic abstraction to encapsulate primitive in a ByteUnit +pub trait ToByteUnit { + fn to_byteunit(self, byte: ByteUnit<()>) -> ByteUnit; +} + +impl ToByteUnit for u32 where i64: From { + fn to_byteunit(self, unit: ByteUnit<()>) -> ByteUnit { + match unit { + ByteUnit::IEC(()) => ByteUnit::IEC(self), + ByteUnit::SI(()) => ByteUnit::SI(self), + } + } +} + +impl ToByteUnit for i32 where i64: From { + fn to_byteunit(self, unit: ByteUnit<()>) -> ByteUnit { + match unit { + ByteUnit::IEC(()) => ByteUnit::IEC(self), + ByteUnit::SI(()) => ByteUnit::SI(self), + } + } +} + +impl ToByteUnit for i64 where i64: From { + fn to_byteunit(self, unit: ByteUnit<()>) -> ByteUnit { + match unit { + ByteUnit::IEC(()) => ByteUnit::IEC(self), + ByteUnit::SI(()) => ByteUnit::SI(self), + } + } +} + +impl ByteUnit where i64: From, T: Copy { + fn value(&self) -> (T, f64) { + match self { + Self::IEC(val) => (*val, 1024.0), + Self::SI(val) => (*val, 1000.0) + } + } + + fn unit_suffix<'a>(&self, i: i8) -> &'a str { + match self { + Self::IEC(_) => match i { + K => "KiB", + M => "MiB", + G => "GiB", + T => "TiB", + P => "PiB", + E => "EiB", + _ => "B" + }, + Self::SI(_) => match i { + K => "KB", + M => "MB", + G => "GB", + T => "TB", + P => "PB", + E => "EB", + _ => "B" + } + } + } + + fn arithmetic(&self, power_of: i8) -> (i8, f64) { + let value: (T, f64) = self.value(); + let bytes: i64 = value.0.into(); + let diviser = value.1; + let positive = bytes > -1; + let mut bytes: f64 = if positive { bytes } else { -bytes } as f64; + let mut power = 0; + + while bytes >= diviser && power < power_of { + bytes = bytes / diviser; + power += 1; + } + + match positive { true => (power, bytes), false => (power, -bytes) } + } + + fn format(&self, arithmetic: (i8, f64)) -> String { + let power = arithmetic.0; + let value = arithmetic.1; + + match power { + B => format!("{:.0} {}", value, self.unit_suffix(power)), + _ => format!("{:.2} {}", value, self.unit_suffix(power)), + } + } + + /// Acquire and return base value of encapsulated primitive + pub fn val(&self) -> T { + match self { + Self::IEC(val) => *val, + Self::SI(val) => *val, + } + } + + /// Returns a formatted string with a maximum of the specified power. + pub fn pow(&self, power_of: i8) -> String { + self.format(self.arithmetic(power_of)) + } + + /// Returns a formatted string with a maximum power of 1 (Kilo/Kibi) + pub fn k(&self) -> String { + self.format(self.arithmetic(K)) + } + + /// Returns a formatted string with a maximum power of 2 (Mega/Mebi) + pub fn m(&self) -> String { + self.format(self.arithmetic(M)) + } + + /// Returns a formatted string with a maximum power of 3 (Giga/Gibi) + pub fn g(&self) -> String { + self.format(self.arithmetic(G)) + } + + /// Returns a formatted string with a maximum power of 4 (Tera/Tebi) + pub fn p(&self) -> String { + self.format(self.arithmetic(P)) + } + + /// Returns a formatted string with a maximum power of 5 (Peta/Pebi) + pub fn t(&self) -> String { + self.format(self.arithmetic(T)) + } + + /// Returns a formatted string with a maximum power of 6 (Exa/Exbi) + pub fn e(&self) -> String { + self.format(self.arithmetic(E)) + } +} + +/// Display implementation with a maximum power of 6 (Exa/Exbi) +impl Display for ByteUnit where i64: From, T: Copy { + fn fmt(&self, f:&mut Formatter<'_>) -> std::fmt::Result { + let arithmetic = self.arithmetic(E); + let bytes = arithmetic.1; + let index = arithmetic.0; + + match index { + B => write!(f, "{:.0} {}", bytes, self.unit_suffix(index)), + _ => write!(f, "{:.2} {}", bytes, self.unit_suffix(index)), + } + } +} + +impl From<&str> for ByteUnit where i64: From, i64: Copy { + fn from(value: &str) -> Self { + ByteUnit::from_str(value).unwrap() + } +} + +impl FromStr for ByteUnit where i64: From, i64: Copy { + type Err = Error; + + fn from_str(s: &str) -> Result { + let input = input_arithmetic(parse_input(s)?); + + match input.0 { + true => Ok(ByteUnit::IEC(input.1)), false => Ok(ByteUnit::SI(input.1)) + } + } +} + +impl PartialOrd for ByteUnit where i64: From, T: Copy + PartialEq { + fn partial_cmp(&self, other: &Self) -> Option { + let value = i64::from(self.val()); + let other = i64::from(other.val()); + + value.partial_cmp(&other) + } +} + +impl PartialEq for ByteUnit where i64: From, T: Copy + PartialEq { + fn eq(&self, other: &Self) -> bool { + other.val().eq(&self.val()) + } +} + +impl Add for ByteUnit where i64: From, T: Copy + Add { + type Output = Self; + + fn add(self, input: Self) -> Self::Output { + match self { + Self::IEC(value) => Self::IEC(value + input.val()), + Self::SI(value) => Self::SI(value + input.val()), + } + } +} + +impl Sub for ByteUnit where i64: From, T: Copy + Sub { + type Output = Self; + + fn sub(self, input: Self) -> Self::Output { + match self { + Self::IEC(value) => Self::IEC(value - input.val()), + Self::SI(value) => Self::SI(value - input.val()), + } + } +} + +impl Mul for ByteUnit where i64: From, T: Copy + Mul { + type Output = Self; + + fn mul(self, input: Self) -> Self::Output { + match self { + Self::IEC(value) => Self::IEC(value * input.val()), + Self::SI(value) => Self::SI(value * input.val()), + } + } +} + +impl Div for ByteUnit where i64: From, T: Copy + Div { + type Output = Self; + + fn div(self, input: Self) -> Self::Output { + match self { + Self::IEC(value) => Self::IEC(value / input.val()), + Self::SI(value) => Self::SI(value / input.val()), + } + } +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..d4dd515 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,190 @@ +/* + * SimpleByteUnit + * + * Copyright 2023 Xaver R.M. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#![allow(dead_code,unused_imports)] + +use super::simplebyteunit::*; + +const POSITIVE_5B: i64 = 5000; +const NEGATIVE_5B: i64 = -5000; +const POSITIVE_5K: i64 = 5000000; +const NEGATIVE_5K: i64 = -5000000; +const POSITIVE_5G: i64 = 5000000000; +const NEGATIVE_5G: i64 = -5000000000; +const POSITIVE_5T: i64 = 5000000000000; +const NEGATIVE_5T: i64 = -5000000000000; +const POSITIVE_5P: i64 = 5000000000000000; +const NEGATIVE_5P: i64 = -5000000000000000; +const POSITIVE_5E: i64 = 5000000000000000000; +const NEGATIVE_5E: i64 = -5000000000000000000; + +#[test] +fn format_all() { + assert_eq!(NEGATIVE_5E.to_byteunit(SI).to_string(), "-5.00 EB"); + assert_eq!(POSITIVE_5E.to_byteunit(SI).to_string(), "5.00 EB"); + assert_eq!(NEGATIVE_5P.to_byteunit(SI).to_string(), "-5.00 PB"); + assert_eq!(POSITIVE_5P.to_byteunit(SI).to_string(), "5.00 PB"); + assert_eq!(NEGATIVE_5T.to_byteunit(SI).to_string(), "-5.00 TB"); + assert_eq!(POSITIVE_5T.to_byteunit(SI).to_string(), "5.00 TB"); + assert_eq!(NEGATIVE_5G.to_byteunit(SI).to_string(), "-5.00 GB"); + assert_eq!(POSITIVE_5G.to_byteunit(SI).to_string(), "5.00 GB"); + assert_eq!(POSITIVE_5K.to_byteunit(SI).to_string(), "5.00 MB"); + assert_eq!(NEGATIVE_5K.to_byteunit(SI).to_string(), "-5.00 MB"); + assert_eq!(POSITIVE_5B.to_byteunit(SI).to_string(), "5.00 KB"); + assert_eq!(NEGATIVE_5B.to_byteunit(SI).to_string(), "-5.00 KB"); + assert_eq!(NEGATIVE_5E.to_byteunit(IEC).to_string(), "-4.34 EiB"); + assert_eq!(POSITIVE_5E.to_byteunit(IEC).to_string(), "4.34 EiB"); + assert_eq!(NEGATIVE_5P.to_byteunit(IEC).to_string(), "-4.44 PiB"); + assert_eq!(POSITIVE_5P.to_byteunit(IEC).to_string(), "4.44 PiB"); + assert_eq!(NEGATIVE_5T.to_byteunit(IEC).to_string(), "-4.55 TiB"); + assert_eq!(NEGATIVE_5T.to_byteunit(IEC).to_string(), "-4.55 TiB"); + assert_eq!(POSITIVE_5T.to_byteunit(IEC).to_string(), "4.55 TiB"); + assert_eq!(NEGATIVE_5G.to_byteunit(IEC).to_string(), "-4.66 GiB"); + assert_eq!(POSITIVE_5G.to_byteunit(IEC).to_string(), "4.66 GiB"); + assert_eq!(POSITIVE_5K.to_byteunit(IEC).to_string(), "4.77 MiB"); + assert_eq!(NEGATIVE_5K.to_byteunit(IEC).to_string(), "-4.77 MiB"); + assert_eq!(POSITIVE_5B.to_byteunit(IEC).to_string(), "4.88 KiB"); + assert_eq!(NEGATIVE_5B.to_byteunit(IEC).to_string(), "-4.88 KiB"); + +} + +#[test] +fn bytes() { + assert_eq!(NEGATIVE_5B.to_byteunit(IEC).pow(B), "-5000 B"); + assert_eq!(NEGATIVE_5B.to_byteunit(SI).pow(B), "-5000 B"); + assert_eq!(POSITIVE_5B.to_byteunit(IEC).pow(B), "5000 B"); + assert_eq!(POSITIVE_5B.to_byteunit(SI).pow(B), "5000 B"); + assert_eq!(NEGATIVE_5E.to_byteunit(IEC).pow(B), "-5000000000000000000 B"); + assert_eq!(NEGATIVE_5E.to_byteunit(SI).pow(B), "-5000000000000000000 B"); + assert_eq!(POSITIVE_5E.to_byteunit(IEC).pow(B), "5000000000000000000 B"); + assert_eq!(POSITIVE_5E.to_byteunit(SI).pow(B), "5000000000000000000 B"); +} + +#[test] +fn k() { + assert_eq!(NEGATIVE_5K.to_byteunit(IEC).pow(K), "-4882.81 KiB"); + assert_eq!(POSITIVE_5K.to_byteunit(IEC).pow(K), "4882.81 KiB"); + assert_eq!(NEGATIVE_5K.to_byteunit(SI).pow(K), "-5000.00 KB"); + assert_eq!(POSITIVE_5K.to_byteunit(SI).pow(K), "5000.00 KB"); +} + +#[test] +fn m() { + assert_eq!(NEGATIVE_5G.to_byteunit(IEC).pow(M), "-4768.37 MiB"); + assert_eq!(POSITIVE_5G.to_byteunit(IEC).pow(M), "4768.37 MiB"); + assert_eq!(NEGATIVE_5G.to_byteunit(SI).pow(M), "-5000.00 MB"); + assert_eq!(POSITIVE_5G.to_byteunit(SI).pow(M), "5000.00 MB"); +} + +#[test] +fn g() { + assert_eq!(NEGATIVE_5T.to_byteunit(IEC).pow(G), "-4656.61 GiB"); + assert_eq!(POSITIVE_5T.to_byteunit(IEC).pow(G), "4656.61 GiB"); + assert_eq!(NEGATIVE_5T.to_byteunit(SI).pow(G), "-5000.00 GB"); + assert_eq!(POSITIVE_5T.to_byteunit(SI).pow(G), "5000.00 GB"); +} + +#[test] +fn t() { + assert_eq!(NEGATIVE_5P.to_byteunit(IEC).pow(T), "-4547.47 TiB"); + assert_eq!(POSITIVE_5P.to_byteunit(IEC).pow(T), "4547.47 TiB"); + assert_eq!(NEGATIVE_5P.to_byteunit(SI).pow(T), "-5000.00 TB"); + assert_eq!(POSITIVE_5P.to_byteunit(SI).pow(T), "5000.00 TB"); +} + +#[test] +fn p() { + assert_eq!(NEGATIVE_5E.to_byteunit(IEC).pow(P), "-4440.89 PiB"); + assert_eq!(POSITIVE_5E.to_byteunit(IEC).pow(P), "4440.89 PiB"); + assert_eq!(NEGATIVE_5E.to_byteunit(SI).pow(P), "-5000.00 PB"); + assert_eq!(POSITIVE_5E.to_byteunit(SI).pow(P), "5000.00 PB"); +} + +#[test] +fn e() { + assert_eq!(NEGATIVE_5E.to_byteunit(IEC).pow(E), "-4.34 EiB"); + assert_eq!(POSITIVE_5E.to_byteunit(IEC).pow(E), "4.34 EiB"); + assert_eq!(NEGATIVE_5E.to_byteunit(SI).pow(E), "-5.00 EB"); + assert_eq!(POSITIVE_5E.to_byteunit(SI).pow(E), "5.00 EB"); +} + +#[test] +fn eq() { + assert_eq!(POSITIVE_5G.to_byteunit(SI) == POSITIVE_5G.to_byteunit(SI), true); + assert_eq!(POSITIVE_5B.to_byteunit(SI) == POSITIVE_5G.to_byteunit(SI), false); +} + +#[test] +fn mul() { + let a = ByteUnit::SI(POSITIVE_5B); + let b = ByteUnit::SI(POSITIVE_5K); + let multiplication = a * b; + + assert_eq!(multiplication.to_string(), "25.00 GB"); +} + +#[test] +fn div() { + let a = ByteUnit::SI(POSITIVE_5G); + let b = ByteUnit::SI(POSITIVE_5B); + let division = a / b; + + assert_eq!(division.to_string(), "1.00 MB"); +} + +#[test] +fn add() { + let a = ByteUnit::SI(POSITIVE_5B); + let b = ByteUnit::SI(POSITIVE_5B); + let division = a + b; + + assert_eq!(division.to_string(), "10.00 KB"); +} + +#[test] +fn sub() { + let a = ByteUnit::SI(POSITIVE_5G); + let b = ByteUnit::SI(POSITIVE_5G); + let division = a - b; + + assert_eq!(division.to_string(), "0 B"); + +} + +#[test] +fn partial_cmp() { + let a = ByteUnit::SI(NEGATIVE_5G); + let b = ByteUnit::SI(POSITIVE_5G); + + assert_eq!(a > b, false); + assert_eq!(a < b, true); + assert_eq!(a >= a, true); + assert_eq!(b <= b, true); +} + +#[test] +fn partial_eq() { + let a = ByteUnit::SI(POSITIVE_5G); + let b = ByteUnit::SI(POSITIVE_5G); + + assert_eq!(a == b, true); + assert_eq!(a != b, false); +} + +#[test] +fn into() { + let a: ByteUnit = "-4.34 EiB".into(); + let b: ByteUnit = "-2.50 GB".into(); + + assert_eq!(a.to_string(), "-4.34 EiB"); + assert_eq!(b.to_string(), "-2.50 GB"); +}