Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bytescale/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <thiery.louis@gmail.com>"]
keywords = ["byte", "utility", "human-readable", "format"]
categories = ["development-tools", "filesystem"]
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion humanbyte-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "humanbyte-derive"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
authors = ["Louis Thiery <thiery.louis@gmail.com>"]
description = "A procedural macro for deriving human readable byte functions"
Expand Down
4 changes: 2 additions & 2 deletions humanbyte/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "humanbyte"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
authors = ["Louis Thiery <thiery.louis@gmail.com>"]
description = "A procedural macro for deriving human readable byte functions"
Expand All @@ -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 }
6 changes: 3 additions & 3 deletions humanbyte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 40 additions & 6 deletions humanbyte/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"`.
Expand All @@ -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.)
Expand All @@ -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
)
Expand Down Expand Up @@ -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");
Expand Down
Loading