From 0602013121f569906448c70a595ccc8860ac270f Mon Sep 17 00:00:00 2001 From: Louis Thiery Date: Tue, 7 Jul 2026 19:26:48 +0000 Subject: [PATCH] Add short display formats (Format::IECShort / Format::SIShort) Compact style matching bytesize's iec_short/si_short: no space, no iB/B suffix ("11.8M", "215B"); output is sort -h compatible. Format is now #[non_exhaustive]; adding variants is why this is 0.4.0. Motivated by pitchfork's TUI, which hand-rolls this style in three places. --- bytescale/Cargo.toml | 4 ++-- humanbyte-derive/Cargo.toml | 2 +- humanbyte/Cargo.toml | 4 ++-- humanbyte/README.md | 6 ++--- humanbyte/src/lib.rs | 46 ++++++++++++++++++++++++++++++++----- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/bytescale/Cargo.toml b/bytescale/Cargo.toml index d2083bf..f115ca7 100644 --- a/bytescale/Cargo.toml +++ b/bytescale/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bytescale" description = "Demonstration of humanbyte usage for human readable byte functions" -version = "0.3.0" +version = "0.4.0" authors = ["Louis Thiery "] keywords = ["byte", "utility", "human-readable", "format"] categories = ["development-tools", "filesystem"] @@ -22,7 +22,7 @@ serde = ["humanbyte/serde"] [dependencies] arbitrary = { version = "1", features = ["derive"], optional = true } -humanbyte = { version = "0.3.0", path = "../humanbyte", features = ["derive"] } +humanbyte = { version = "0.4.0", path = "../humanbyte", features = ["derive"] } [dev-dependencies] schemars = "1" diff --git a/humanbyte-derive/Cargo.toml b/humanbyte-derive/Cargo.toml index f71b64a..e8f9b3a 100644 --- a/humanbyte-derive/Cargo.toml +++ b/humanbyte-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "humanbyte-derive" -version = "0.3.0" +version = "0.4.0" edition = "2024" authors = ["Louis Thiery "] description = "A procedural macro for deriving human readable byte functions" diff --git a/humanbyte/Cargo.toml b/humanbyte/Cargo.toml index f18efe7..de190ef 100644 --- a/humanbyte/Cargo.toml +++ b/humanbyte/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "humanbyte" -version = "0.3.0" +version = "0.4.0" edition = "2024" authors = ["Louis Thiery "] description = "A procedural macro for deriving human readable byte functions" @@ -20,6 +20,6 @@ serde = ["dep:serde", "std", "humanbyte-derive/serde"] schemars = ["dep:schemars", "std", "humanbyte-derive/schemars"] [dependencies] -humanbyte-derive = { version = "0.3.0", path = "../humanbyte-derive", optional = true } +humanbyte-derive = { version = "0.4.0", path = "../humanbyte-derive", optional = true } schemars = { version = "1", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/humanbyte/README.md b/humanbyte/README.md index 328fbfb..cbb57ea 100644 --- a/humanbyte/README.md +++ b/humanbyte/README.md @@ -16,21 +16,21 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -humanbyte = { version = "0.3", features = ["serde"] } +humanbyte = { version = "0.4", features = ["serde"] } ``` ### without serde support ```toml [dependencies] -humanbyte = { version = "0.3" } +humanbyte = { version = "0.4" } ``` ### no_std compatible ```toml [dependencies] -humanbyte = { version = "0.3", no-default-features = true } +humanbyte = { version = "0.4", no-default-features = true } ``` Define your new type and derive `HumanByte` for it. This will derive all the necessary functions for your new type. You diff --git a/humanbyte/src/lib.rs b/humanbyte/src/lib.rs index c750fe2..73e2ab6 100644 --- a/humanbyte/src/lib.rs +++ b/humanbyte/src/lib.rs @@ -65,10 +65,23 @@ const UNITS_IEC: &str = "KMGTPE"; const UNITS_SI: &str = "kMGTPE"; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[non_exhaustive] pub enum Format { #[default] IEC, SI, + /// IEC (binary) units in a short style, e.g. `11.8M`. + /// + /// No space, no `iB` suffix. Designed to produce output compatible with `sort -h`. + /// + /// Note: [`parse`] interprets bare prefixes like `M` as *decimal* units, so + /// short IEC output does not round-trip exactly; prefer [`Format::IEC`] for + /// values that will be parsed back. + IECShort, + /// SI (decimal) units in a short style, e.g. `12.3M`. + /// + /// No space, no `B` suffix. + SIShort, } /// Formats `bytes` as a human-readable string with one decimal, e.g. `"1.5 KiB"`. @@ -80,19 +93,24 @@ pub fn to_string(bytes: u64, format: Format) -> String { /// e.g. `to_string_with_precision(1 << 40, Format::IEC, 2)` is `"1.00 TiB"`. pub fn to_string_with_precision(bytes: u64, format: Format, precision: usize) -> String { let unit = match format { - Format::IEC => KIB, - Format::SI => KB, + Format::IEC | Format::IECShort => KIB, + Format::SI | Format::SIShort => KB, }; let unit_prefix = match format { - Format::IEC => UNITS_IEC.as_bytes(), - Format::SI => UNITS_SI.as_bytes(), + Format::IEC | Format::IECShort => UNITS_IEC.as_bytes(), + Format::SI | Format::SIShort => UNITS_SI.as_bytes(), + }; + let unit_separator = match format { + Format::IEC | Format::SI => " ", + Format::IECShort | Format::SIShort => "", }; let unit_suffix = match format { Format::IEC => "iB", Format::SI => "B", + Format::IECShort | Format::SIShort => "", }; if bytes < unit { - format!("{} B", bytes) + format!("{}{}B", bytes, unit_separator) } else { // Integer log: largest exp such that bytes >= unit^exp. // (f64::ln is unavailable in core, and this is exact at boundaries.) @@ -103,9 +121,10 @@ pub fn to_string_with_precision(bytes: u64, format: Format, precision: usize) -> exp += 1; } format!( - "{:.*} {}{}", + "{:.*}{}{}{}", precision, (bytes as f64 / unit.pow(exp) as f64), + unit_separator, unit_prefix[(exp - 1) as usize] as char, unit_suffix ) @@ -472,6 +491,21 @@ mod tests { } } + #[test] + fn test_short_formats() { + assert_eq!(to_string(1536, Format::IECShort), "1.5K"); + assert_eq!(to_string(1536, Format::SIShort), "1.5k"); + assert_eq!(to_string(215, Format::IECShort), "215B"); + assert_eq!(to_string(12_300_000, Format::SIShort), "12.3M"); + assert_eq!( + to_string_with_precision(GIB + 512 * MIB, Format::IECShort, 0), + "2G" + ); + // long formats unchanged + assert_eq!(to_string(1536, Format::IEC), "1.5 KiB"); + assert_eq!(to_string(215, Format::IEC), "215 B"); + } + #[test] fn test_precision() { assert_eq!(to_string_with_precision(TIB, Format::IEC, 2), "1.00 TiB");