1use {
2 core::str::FromStr,
3};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum Units {
8 Si, Binary, Bytes, }
12
13impl Default for Units {
14 fn default() -> Self {
15 Self::Si
16 }
17}
18impl FromStr for Units {
19 type Err = String;
20 fn from_str(value: &str) -> Result<Self, String> {
21 match value.to_lowercase().as_ref() {
22 "si" => Ok(Self::Si),
23 "binary" => Ok(Self::Binary),
24 "bytes" => Ok(Self::Bytes),
25 _ => Err(format!("Illegal value: {:?} - valid values are 'SI', 'binary', and 'bytes'", value)),
26 }
27 }
28}
29
30static PREFIXES: &[char] = &['K', 'M', 'G', 'T', 'P'];
31
32impl Units {
33 pub fn fmt(self, size: u64) -> String {
34 match self {
35 Self::Si => file_size::fit_4(size),
36 Self::Binary => {
37 if size < 10_000 {
38 size.to_string()
39 } else {
40 let i = size.ilog2() / 10u32;
41 let idx = i as usize - 1;
42 let size = size as f64;
43 if idx >= PREFIXES.len() {
44 "huge".to_string()
45 } else {
46 let v = size / (1024u64.pow(i) as f64);
47 if v >= 10f64 {
48 format!("{:.0}{}i", v.round(), PREFIXES[idx])
49 } else {
50 format!("{:.1}{}i", v, PREFIXES[idx])
51 }
52 }
53 }
54 }
55 Self::Bytes => {
56 let mut rev: Vec<char> = Vec::new();
57 for (i, c) in size.to_string().chars().rev().enumerate() {
58 if i>0 && i%3==0 {
59 rev.push(',');
60 }
61 rev.push(c);
62 }
63 rev.drain(..).rev().collect()
64 }
65 }
66 }
67}
68
69#[test]
70fn test_fmt_binary() {
71 fn check(v: u64, s: &str) {
72 assert_eq!(&Units::Binary.fmt(v), s);
73 }
74 check(0, "0");
75 check(1, "1");
76 check(456, "456");
77 check(1456, "1456");
78 check(9_999, "9999");
79 check(10_000, "9.8Ki");
80 check(12_345, "12Ki");
81 check(123_456, "121Ki");
82 check(1_000_000_000, "954Mi");
83 check(1_073_741_824, "1.0Gi");
84 check(1_234_567_890, "1.1Gi");
85}
86
87#[test]
88fn test_fmt_bytes() {
89 fn check(v: u64, s: &str) {
90 assert_eq!(&Units::Bytes.fmt(v), s);
91 }
92 check(0, "0");
93 check(1, "1");
94 check(456, "456");
95 check(1456, "1,456");
96 check(9_999, "9,999");
97 check(10_000, "10,000");
98 check(12_345, "12,345");
99 check(123_456, "123,456");
100 check(1_234_567, "1,234,567");
101 check(1_000_000_000, "1,000,000,000");
102 check(1_234_567_890, "1,234,567,890");
103}