Skip to main content

stuffr_core/
size.rs

1//! Human-readable byte-count rendering.
2//!
3//! Lives here, not in the CLI, so a codec's `--memory-limit` refusal message
4//! and `stuffr info`'s resolved-limit display share one formatter rather than
5//! inventing their own — see the whole-branch review's LOW-5 finding: before
6//! this, the same quantity (a declared or configured memory bound) appeared
7//! as raw **bytes** (`xz_pure.rs`, `lzip.rs`), **KiB** (`lzma_pure.rs`), and
8//! **MiB/GiB** (`stuffr info`) in the same build, which is exactly backwards for
9//! the one message a user reads to decide what to pass back to
10//! `--memory-limit`.
11//!
12//! `stuffr-cli`'s `size::parse_size` is the inverse and stays in the CLI
13//! crate — it is flag-syntax (`512M`, `2G`), not a general utility, and this
14//! crate has no reason to know what a command-line flag looks like. This
15//! function has no such reason to live above `stuffr-core`: it is pure
16//! arithmetic, with no format dependency, so codecs in `stuffr-formats` can
17//! use it directly for their own refusal messages.
18
19/// Renders a byte count the way a human reads it.
20///
21/// Exact multiples get a bare suffix (`256 MiB`); anything else keeps one
22/// decimal (`1.5 GiB`), because rounding a limit or a declared size to a tidy
23/// figure would be actively unhelpful in a message someone is using to size
24/// a flag.
25pub fn format_size(bytes: u64) -> String {
26    const UNITS: [(u64, &str); 3] = [
27        (1024 * 1024 * 1024, "GiB"),
28        (1024 * 1024, "MiB"),
29        (1024, "KiB"),
30    ];
31    for (unit, name) in UNITS {
32        if bytes >= unit {
33            // `is_multiple_of` rather than `% == 0` because clippy requires
34            // it (MSRV 1.88).
35            if bytes.is_multiple_of(unit) {
36                return format!("{} {name}", bytes / unit);
37            }
38            return format!("{:.1} {name}", bytes as f64 / unit as f64);
39        }
40    }
41    format!("{bytes} bytes")
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn format_size_is_readable_and_exact_where_it_can_be() {
50        assert_eq!(format_size(256 * 1024 * 1024), "256 MiB");
51        assert_eq!(format_size(2 * 1024 * 1024 * 1024), "2 GiB");
52        assert_eq!(format_size(512), "512 bytes");
53        assert_eq!(format_size(1536), "1.5 KiB");
54        // Not rounded to "2 GiB": someone reading this to check a limit needs
55        // the value, not a tidy approximation of it.
56        assert_eq!(
57            format_size(1024 * 1024 * 1024 + 512 * 1024 * 1024),
58            "1.5 GiB"
59        );
60    }
61}