use std::fmt::Write;
use crate::error::Error;
const KILO: u64 = 1_000;
const MEGA: u64 = 1_000_000;
const GIGA: u64 = 1_000_000_000;
const TERA: u64 = 1_000_000_000_000;
const PETA: u64 = 1_000_000_000_000_000;
const EXA: u64 = 1_000_000_000_000_000_000;
pub fn parse(input: &str) -> Result<u64, Error<'_>> {
parse_with_additional_units(input, &[])
}
pub fn parse_with_additional_units<'a>(
mut input: &'a str,
additional_units: &[(&str, u64)],
) -> Result<u64, Error<'a>> {
if !input.is_ascii() {
return Err(Error::NotAscii);
}
input = input.trim();
let (mut value, original_unit_str) = input.split_at(
input
.bytes()
.position(|b| b.is_ascii_alphabetic())
.unwrap_or(input.len()),
);
let mut unit_str = original_unit_str;
let mut unit = 1;
if !unit_str.is_empty() {
let exponent = match unit_str.as_bytes()[0].to_ascii_lowercase() {
b'k' => Some(KILO),
b'm' => Some(MEGA),
b'g' => Some(GIGA),
b't' => Some(TERA),
b'p' => Some(PETA),
b'e' => Some(EXA),
_ => None,
};
if let Some(exponent) = exponent {
if additional_units.iter().all(|(s, _)| *s != &unit_str[..1]) {
unit *= exponent;
unit_str = &unit_str[1..];
}
}
}
if !unit_str.is_empty() {
for &(additional_unit, addition_factor) in additional_units {
if unit_str == additional_unit {
unit *= addition_factor;
unit_str = "";
break;
}
}
}
if !unit_str.is_empty() {
return Err(Error::InvalidUnit(original_unit_str));
}
value = value.trim();
let (integer_str, mut fraction_str) = value.split_once('.').unwrap_or((value, ""));
fraction_str = fraction_str.trim_end_matches('0');
if integer_str.is_empty() && fraction_str.is_empty() {
return Err(Error::ParseIntError(value, None));
}
fn apply_unit(part: &str, unit: u64, reduce: u64) -> Result<u64, Error<'_>> {
if part.is_empty() {
return Ok(0);
}
Ok(part
.parse::<u64>()
.map_err(|err| Error::ParseIntError(part, Some(err)))?
* unit
/ reduce)
}
Ok(apply_unit(integer_str, unit, 1)?
+ apply_unit(fraction_str, unit, 10u64.pow(fraction_str.len() as u32))?)
}
pub fn format(input: u64) -> String {
if input == 0 {
return "0".to_owned();
}
let input_str = input.to_string();
let unit = match (input_str.len() - 1) / 3 {
0 => "",
1 => "k",
2 => "M",
3 => "G",
4 => "T",
5 => "P",
_ => "E",
};
let mut output = String::with_capacity(8);
let split = (input_str.len() - 1) % 3 + 1;
write!(output, "{}", &input_str[..split]).expect("write error");
let fraction_str = input_str[split..(split + 2).min(input_str.len())].trim_end_matches('0');
if !fraction_str.is_empty() {
write!(output, ".{:.2}", fraction_str).expect("write error");
}
write!(output, "{unit}").expect("write error");
output
}
#[cfg(feature = "serde")]
crate::impl_serde!(
ser:
de:
);
#[cfg(test)]
mod tests {
use crate::error::Error;
#[test]
fn parse() {
assert_eq!(super::parse("12.345k").unwrap(), 12_345);
assert_eq!(super::parse("0.12k").unwrap(), 120);
assert_eq!(super::parse("12").unwrap(), 12);
assert_eq!(super::parse("12k").unwrap(), 12_000);
assert_eq!(super::parse("120k").unwrap(), 120_000);
assert_eq!(super::parse("12.3M").unwrap(), 12_300_000);
assert_eq!(super::parse("12.3G").unwrap(), 12_300_000_000);
assert_eq!(super::parse("12.3T").unwrap(), 12_300_000_000_000);
assert_eq!(super::parse("12.3P").unwrap(), 12_300_000_000_000_000);
assert_eq!(super::parse("1.02k").unwrap(), 1_020); assert_eq!(super::parse("0.2").unwrap(), 0); assert_eq!(super::parse("012.340k").unwrap(), 12_340); assert_eq!(super::parse("12.3456k").unwrap(), 12_345); assert_eq!(super::parse(".5k").unwrap(), 500); assert_eq!(super::parse("5.k").unwrap(), 5_000);
assert_eq!(super::parse(" 12k").unwrap(), 12_000);
assert_eq!(super::parse("12k ").unwrap(), 12_000);
assert_eq!(super::parse("12 k").unwrap(), 12_000);
assert!(matches!(super::parse("k"), Err(Error::ParseIntError("", None))));
assert!(matches!(super::parse(".k"), Err(Error::ParseIntError(".", None))));
assert!(matches!(super::parse("1.1."), Err(Error::ParseIntError("1.", Some(_)))));
assert!(matches!(super::parse("1.1.k"), Err(Error::ParseIntError("1.", Some(_)))));
assert!(matches!(super::parse("1.1.1k"), Err(Error::ParseIntError("1.1", Some(_)))));
assert!(matches!(super::parse(".1.1k"), Err(Error::ParseIntError("1.1", Some(_)))));
assert!(matches!(super::parse("12kk"), Err(Error::InvalidUnit("kk"))));
assert!(matches!(super::parse("12kM"), Err(Error::InvalidUnit("kM"))));
assert!(matches!(super::parse("12k M"), Err(Error::InvalidUnit("k M"))));
}
#[test]
fn parse_with_additional_units() {
let additional_units = &[("b", 1), ("B", 8)];
assert_eq!(super::parse_with_additional_units("12b", additional_units).unwrap(), 12);
assert_eq!(super::parse_with_additional_units("12B", additional_units).unwrap(), 12 * 8);
assert_eq!(
super::parse_with_additional_units("12.345kb", additional_units).unwrap(),
12_345
);
assert_eq!(
super::parse_with_additional_units("12.345kB", additional_units).unwrap(),
12_345 * 8
);
assert_eq!(
super::parse_with_additional_units("0.12kB", additional_units).unwrap(),
120 * 8
);
assert_eq!(super::parse_with_additional_units("0.2", additional_units).unwrap(), 0); assert_eq!(super::parse_with_additional_units("0.125B", additional_units).unwrap(), 1); assert_eq!(super::parse_with_additional_units("0.3B", additional_units).unwrap(), 2); assert_eq!(super::parse_with_additional_units("12.3B", additional_units).unwrap(), 98); assert_eq!(super::parse_with_additional_units("12.34B", additional_units).unwrap(), 98); assert_eq!(
super::parse_with_additional_units("012.340kb", additional_units).unwrap(),
12_340
); assert_eq!(
super::parse_with_additional_units("12.3456kb", additional_units).unwrap(),
12_345
); assert_eq!(
super::parse_with_additional_units("12.3456kB", additional_units).unwrap(),
98_764
); assert_eq!(
super::parse_with_additional_units("12.34567kB", additional_units).unwrap(),
98_765
); assert_eq!(super::parse_with_additional_units(".5kb", additional_units).unwrap(), 500); assert_eq!(super::parse_with_additional_units("5.kb", additional_units).unwrap(), 5_000);
assert_eq!(super::parse_with_additional_units("12", additional_units).unwrap(), 12);
assert_eq!(super::parse_with_additional_units("12k", additional_units).unwrap(), 12_000);
assert_eq!(super::parse_with_additional_units(" 12kb", additional_units).unwrap(), 12_000);
assert_eq!(super::parse_with_additional_units("12kb ", additional_units).unwrap(), 12_000);
assert_eq!(super::parse_with_additional_units("12 kb", additional_units).unwrap(), 12_000);
assert!(matches!(
super::parse_with_additional_units("12bb", additional_units),
Err(Error::InvalidUnit("bb"))
));
assert!(matches!(
super::parse_with_additional_units("12BB", additional_units),
Err(Error::InvalidUnit("BB"))
));
assert!(matches!(
super::parse_with_additional_units("12bB", additional_units),
Err(Error::InvalidUnit("bB"))
));
assert!(matches!(
super::parse_with_additional_units("12Bb", additional_units),
Err(Error::InvalidUnit("Bb"))
));
assert!(matches!(
super::parse_with_additional_units("12Q", additional_units),
Err(Error::InvalidUnit("Q"))
));
let additional_units = &[("k", 2)]; assert_eq!(super::parse_with_additional_units("12k", additional_units).unwrap(), 12 * 2);
let additional_units = &[("AC", 2)]; assert_eq!(
super::parse_with_additional_units("12kAC", additional_units).unwrap(),
12_000 * 2
);
assert!(matches!(
super::parse_with_additional_units("12ACk", additional_units),
Err(Error::InvalidUnit("ACk"))
)); }
#[test]
fn format() {
assert_eq!(super::format(0), "0");
assert_eq!(super::format(1), "1");
assert_eq!(super::format(12), "12");
assert_eq!(super::format(123), "123");
assert_eq!(super::format(1_234), "1.23k");
assert_eq!(super::format(1_023), "1.02k");
assert_eq!(super::format(12_000), "12k");
assert_eq!(super::format(12_345), "12.34k");
assert_eq!(super::format(123_456), "123.45k");
assert_eq!(super::format(1_234_567), "1.23M");
assert_eq!(super::format(12_345_678), "12.34M");
assert_eq!(super::format(123_456_789), "123.45M");
assert_eq!(super::format(1_234_567_891), "1.23G");
assert_eq!(super::format(12_345_678_912), "12.34G");
assert_eq!(super::format(12_300_000_000_000), "12.3T");
assert_eq!(super::format(12_300_000_000_000_000), "12.3P");
assert_eq!(super::format(12_300_000_000_000_000_000), "12.3E");
assert_eq!(super::format(1_200), "1.2k"); assert_eq!(super::format(1_201), "1.2k"); }
}