use anyhow::Context as _;
pub fn parse_bytes(s: &str) -> anyhow::Result<u64> {
let i = s
.find(|c: char| !matches!(c, '0'..='9' | ',' | '.'))
.unwrap_or(s.len());
let (num, unit) = s.split_at(i);
let last = unit.as_bytes().last().unwrap_or(&0);
let unit_scale = match last {
b'B' => 1,
b'b' => 8,
other => anyhow::bail!(
"Invalid unit {:?} for byte count, expected \"B\".",
other)
};
let unit = unit.trim();
let prefix = &unit[..(unit.len() - 1)];
let (base, prefix) = match prefix.strip_suffix('i') {
Some(p) => (1024u64, p),
None => (1000, prefix),
};
if prefix.len() > 1 {
anyhow::bail!("Invalid unit {:?} for byte count, expected \"B\".", unit)
}
let pow = match prefix.as_bytes().first() {
Some(&c) => crate::SI_PREFIXES.bytes()
.position(|b| b == c)
.map(|i| i + 1)
.ok_or_else(|| anyhow::format_err!(
"Invalid SI prefix {:?}.",
prefix))?
.try_into()
.context("SI prefix too large")?,
None => 0,
};
let scale = base.checked_pow(pow)
.context("SI prefix to long")?;
let (int, frac) = match num.find('.') {
Some(i) => (&num[..i], &num[(i+1)..]),
None => (num, ""),
};
let int = int.parse::<u64>()?
.checked_mul(scale)
.context("SI scale too large")?;
let frac_int = if frac.is_empty() {
0
} else {
frac.parse::<u128>()
.context("fractional component")?
};
let frac_int = frac_int.checked_mul(scale.into())
.context("fractional component")?;
let frac = frac_int / 10u128.pow(frac.len().try_into().unwrap_or(u32::MAX));
let frac = frac.try_into()
.context("fractional component")?;
let combined = int.checked_add(frac)
.context("number too large")?;
Ok(combined / unit_scale)
}