Skip to main content

decdn_common/cli/
byte_size.rs

1//! Parse a human byte size (`4MiB`, `1048576`, `2M`) for CLI `value_parser`s.
2//! Binary units only: `K`/`KiB`/`KB` = 1024, `M`/`MiB`/`MB` = 1024², `G`/`GiB`/`GB` = 1024³.
3
4/// Parse a byte size: a bare integer (bytes) or an integer followed by a
5/// binary unit suffix (`K`/`KiB`/`KB`, `M`/`MiB`/`MB`, `G`/`GiB`/`GB`, all
6/// powers of 1024). Returns the size in bytes. The error is a `String` so it
7/// renders directly in clap's `value_parser` diagnostics.
8pub fn parse_byte_size(s: &str) -> Result<u64, String> {
9    let t = s.trim();
10    if t.is_empty() {
11        return Err("empty byte size".to_string());
12    }
13    // Split leading digits from an optional unit suffix (whitespace allowed between).
14    let split = t.find(|c: char| !c.is_ascii_digit()).unwrap_or(t.len());
15    let (num, unit) = t.split_at(split);
16    if num.is_empty() {
17        return Err(format!("byte size {s:?} must start with digits"));
18    }
19    let value: u64 = num
20        .parse()
21        .map_err(|_| format!("byte size {s:?} has an invalid number"))?;
22    let mult: u64 = match unit.trim().to_ascii_uppercase().as_str() {
23        "" | "B" => 1,
24        "K" | "KB" | "KIB" => 1024,
25        "M" | "MB" | "MIB" => 1024 * 1024,
26        "G" | "GB" | "GIB" => 1024 * 1024 * 1024,
27        other => return Err(format!("byte size {s:?} has an unknown unit {other:?}")),
28    };
29    value
30        .checked_mul(mult)
31        .ok_or_else(|| format!("byte size {s:?} overflows u64"))
32}
33
34#[cfg(test)]
35#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
36mod tests;