simple_fs/common/
pretty.rs1use derive_more::From;
4
5#[derive(Debug, Default, Clone, From)]
6pub struct PrettySizeOptions {
7 #[from]
8 lowest_unit: SizeUnit,
9}
10
11impl From<&str> for PrettySizeOptions {
12 fn from(val: &str) -> Self {
13 SizeUnit::new(val).into()
14 }
15}
16
17impl From<&String> for PrettySizeOptions {
18 fn from(val: &String) -> Self {
19 SizeUnit::new(val).into()
20 }
21}
22
23impl From<String> for PrettySizeOptions {
24 fn from(val: String) -> Self {
25 SizeUnit::new(&val).into()
26 }
27}
28
29#[derive(Debug, Clone, Default)]
30pub enum SizeUnit {
31 #[default]
32 B,
33 KB,
34 MB,
35 GB,
36 TB,
37}
38
39impl SizeUnit {
40 pub fn new(val: &str) -> Self {
42 match val.to_uppercase().as_str() {
43 "B" => Self::B,
44 "KB" => Self::KB,
45 "MB" => Self::MB,
46 "GB" => Self::GB,
47 "TB" => Self::TB,
48 _ => Self::B,
49 }
50 }
51
52 #[inline]
54 pub fn idx(&self) -> usize {
55 match self {
56 Self::B => 0,
57 Self::KB => 1,
58 Self::MB => 2,
59 Self::GB => 3,
60 Self::TB => 4,
61 }
62 }
63}
64
65impl From<&str> for SizeUnit {
66 fn from(val: &str) -> Self {
67 Self::new(val)
68 }
69}
70
71impl From<&String> for SizeUnit {
72 fn from(val: &String) -> Self {
73 Self::new(val)
74 }
75}
76
77impl From<String> for SizeUnit {
78 fn from(val: String) -> Self {
79 Self::new(&val)
80 }
81}
82
83pub fn pretty_size(size_in_bytes: u64) -> String {
102 pretty_size_with_options(size_in_bytes, PrettySizeOptions::default())
103}
104
105pub fn pretty_size_with_options(size_in_bytes: u64, options: impl Into<PrettySizeOptions>) -> String {
134 let options = options.into();
135
136 const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
137
138 let min_unit_idx = options.lowest_unit.idx();
140 let mut size = size_in_bytes as f64;
141 for _ in 0..min_unit_idx {
142 size /= 1000.0;
143 }
144 let mut unit_idx = min_unit_idx;
145
146 while size >= 1000.0 && unit_idx < UNITS.len() - 1 {
148 size /= 1000.0;
149 unit_idx += 1;
150 }
151
152 let unit_str = UNITS[unit_idx];
153
154 if unit_idx == 0 {
156 let number_str = format!("{size_in_bytes:>6}");
158 format!("{number_str} {unit_str} ")
159 } else {
160 let number_str = format!("{size:>6.2}");
162 format!("{number_str} {unit_str}")
163 }
164}
165
166#[cfg(test)]
171mod tests {
172 type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>; use super::*;
175
176 #[test]
177 fn test_pretty_size() -> Result<()> {
178 let cases = [
180 (777, " 777 B "),
181 (8777, " 8.78 KB"),
182 (88777, " 88.78 KB"),
183 (888777, "888.78 KB"),
184 (888700, "888.70 KB"),
185 (200000, "200.00 KB"),
186 (2_000_000, " 2.00 MB"),
187 (900_000_000, "900.00 MB"),
188 (2_345_678_900, " 2.35 GB"),
189 (1_234_567_890_123, " 1.23 TB"),
190 (2_345_678_900_123_456, " 2.35 PB"),
191 (0, " 0 B "),
192 ];
193
194 for &(input, expected) in &cases {
196 let actual = pretty_size(input);
197 assert_eq!(actual, expected, "input: {input}");
198 }
199
200 Ok(())
201 }
202
203 #[test]
204 fn test_pretty_size_with_lowest_unit() -> Result<()> {
205 let options = PrettySizeOptions::from("MB");
207 let cases = [
208 (88777, " 0.09 MB"),
210 (888777, " 0.89 MB"),
211 (1_234_567, " 1.23 MB"),
212 ];
213
214 for &(input, expected) in &cases {
216 let actual = pretty_size_with_options(input, options.clone());
217 assert_eq!(actual, expected, "input: {input}");
218 }
219
220 Ok(())
221 }
222}
223
224