Skip to main content

f00_format/
human.rs

1use f00_core::BlockSize;
2
3/// Format a byte size with binary units (1024) like GNU `ls -h`.
4pub fn human_size(bytes: u64) -> String {
5    human_size_base(bytes, 1024)
6}
7
8/// Format a byte size with SI units (1000) like GNU `ls --si`.
9pub fn human_size_si(bytes: u64) -> String {
10    human_size_base(bytes, 1000)
11}
12
13fn human_size_base(bytes: u64, base: u64) -> String {
14    const UNITS: [&str; 6] = ["B", "K", "M", "G", "T", "P"];
15    if bytes < base {
16        return format!("{bytes}");
17    }
18
19    let mut value = bytes as f64;
20    let mut unit = 0;
21    let base_f = base as f64;
22    while value >= base_f && unit < UNITS.len() - 1 {
23        value /= base_f;
24        unit += 1;
25    }
26
27    if value >= 10.0 || unit == 0 {
28        format!("{:.0}{}", value, UNITS[unit])
29    } else {
30        format!("{:.1}{}", value, UNITS[unit])
31    }
32}
33
34/// Disk blocks for `ls -s`.
35///
36/// `blocks_512` is `st_blocks` (512-byte units). `unit_bytes` is the display
37/// unit size (default 1024 for kibibytes; 512 with POSIXLY_CORRECT / custom
38/// `--block-size`).
39pub fn block_display(blocks_512: u64) -> u64 {
40    block_display_with_unit(blocks_512, 1024)
41}
42
43/// Convert 512-byte units to `unit_bytes`-sized blocks, rounding up.
44pub fn block_display_with_unit(blocks_512: u64, unit_bytes: u64) -> u64 {
45    let unit = unit_bytes.max(1);
46    let bytes = blocks_512.saturating_mul(512);
47    bytes.div_ceil(unit)
48}
49
50/// Format a size field according to block-size / human flags.
51pub fn format_size_bytes(bytes: u64, block_size: BlockSize, human: bool, si: bool) -> String {
52    if human || matches!(block_size, BlockSize::HumanBinary) {
53        return if si || matches!(block_size, BlockSize::HumanSi) {
54            human_size_si(bytes)
55        } else {
56            human_size(bytes)
57        };
58    }
59    if si || matches!(block_size, BlockSize::HumanSi) {
60        return human_size_si(bytes);
61    }
62    match block_size {
63        BlockSize::Bytes(1) | BlockSize::Bytes(0) => bytes.to_string(),
64        BlockSize::Bytes(unit) => {
65            let n = bytes.div_ceil(unit.max(1));
66            n.to_string()
67        }
68        BlockSize::HumanBinary => human_size(bytes),
69        BlockSize::HumanSi => human_size_si(bytes),
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn small_sizes_are_plain() {
79        assert_eq!(human_size(0), "0");
80        assert_eq!(human_size(999), "999");
81    }
82
83    #[test]
84    fn kilobytes() {
85        assert_eq!(human_size(1024), "1.0K");
86        assert_eq!(human_size(1536), "1.5K");
87        assert_eq!(human_size(10 * 1024), "10K");
88    }
89
90    #[test]
91    fn si_uses_1000() {
92        assert_eq!(human_size_si(1000), "1.0K");
93        assert_eq!(human_size_si(1000 * 1000), "1.0M");
94    }
95
96    #[test]
97    fn block_display_rounds() {
98        assert_eq!(block_display(1), 1);
99        assert_eq!(block_display(2), 1);
100        assert_eq!(block_display(3), 2);
101    }
102
103    #[test]
104    fn block_display_custom_unit() {
105        // 2 * 512 = 1024 bytes → 2 blocks of 512
106        assert_eq!(block_display_with_unit(2, 512), 2);
107        // 2 * 512 = 1024 → 1 block of 1024
108        assert_eq!(block_display_with_unit(2, 1024), 1);
109    }
110}