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