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}