disty_cli/
formatting.rs

1#[derive(Clone, Copy, clap::ValueEnum)]
2pub enum Format {
3    #[value(name = "float")]
4    Float,
5    #[value(name = "hex")]
6    Hex,
7    #[value(name = "time")]
8    Time,
9    #[value(name = "bytes")]
10    Bytes,
11}
12
13impl Format {
14    pub fn format(&self, value: f64) -> String {
15        match self {
16            Format::Float => format!("{:.2}", value),
17            Format::Hex => format!("0x{:x}", value as u64),
18            Format::Time => format_duration(value),
19            Format::Bytes => format_bytes(value),
20        }
21    }
22}
23
24pub fn format_duration(ns: f64) -> String {
25    if ns < 1e3 {
26        format!("{:.2}ns", ns)
27    } else if ns < 1e6 {
28        format!("{:.2}µs", ns / 1e3)
29    } else if ns < 1e9 {
30        format!("{:.2}ms", ns / 1e6)
31    } else if ns < 60e9 {
32        format!("{:.2}s", ns / 1e9)
33    } else if ns < 3600e9 {
34        let mins = (ns / 60e9).floor();
35        let secs = (ns - mins * 60e9) / 1e9;
36        format!("{}m{:.2}s", mins as i64, secs)
37    } else {
38        let hours = (ns / 3600e9).floor();
39        let mins = ((ns - hours * 3600e9) / 60e9).floor();
40        let secs = (ns - hours * 3600e9 - mins * 60e9) / 1e9;
41        format!("{}h{}m{:.2}s", hours as i64, mins as i64, secs)
42    }
43}
44
45pub fn format_bytes(bytes: f64) -> String {
46    let units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
47    let mut value = bytes;
48    let mut unit_idx = 0;
49
50    while value >= 1024.0 && unit_idx < units.len() - 1 {
51        value /= 1024.0;
52        unit_idx += 1;
53    }
54
55    if unit_idx == 0 {
56        format!("{:.0}{}", value, units[unit_idx])
57    } else {
58        format!("{:.2}{}", value, units[unit_idx])
59    }
60}
61
62/// Selects the largest unit where max_value remains >= 1 to avoid tiny decimals
63/// (e.g., prefers "500ms" over "0.5s", but "2s" over "2000ms")
64pub fn get_display_scale(max_value: f64, format: Format) -> (f64, &'static str) {
65    match format {
66        Format::Time => {
67            // Choose unit based on maximum value (in nanoseconds)
68            if max_value < 1e3 {
69                (1.0, "ns")
70            } else if max_value < 1e6 {
71                (1e3, "µs")
72            } else if max_value < 1e9 {
73                (1e6, "ms")
74            } else {
75                (1e9, "s")
76            }
77        }
78        Format::Bytes => {
79            // Choose unit based on maximum value (in bytes)
80            if max_value < 1024.0 {
81                (1.0, "B")
82            } else if max_value < 1024.0_f64.powi(2) {
83                (1024.0, "KiB")
84            } else if max_value < 1024.0_f64.powi(3) {
85                (1024.0_f64.powi(2), "MiB")
86            } else if max_value < 1024.0_f64.powi(4) {
87                (1024.0_f64.powi(3), "GiB")
88            } else if max_value < 1024.0_f64.powi(5) {
89                (1024.0_f64.powi(4), "TiB")
90            } else {
91                (1024.0_f64.powi(5), "PiB")
92            }
93        }
94        Format::Float => (1.0, ""),
95        Format::Hex => (1.0, ""),
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_format_duration_nanoseconds() {
105        assert_eq!(format_duration(1.0), "1.00ns");
106        assert_eq!(format_duration(500.0), "500.00ns");
107        assert_eq!(format_duration(999.0), "999.00ns");
108    }
109
110    #[test]
111    fn test_format_duration_microseconds() {
112        assert_eq!(format_duration(1e3), "1.00µs");
113        assert_eq!(format_duration(5e3), "5.00µs");
114        assert_eq!(format_duration(500e3), "500.00µs");
115    }
116
117    #[test]
118    fn test_format_duration_milliseconds() {
119        assert_eq!(format_duration(1e6), "1.00ms");
120        assert_eq!(format_duration(5e6), "5.00ms");
121        assert_eq!(format_duration(500e6), "500.00ms");
122    }
123
124    #[test]
125    fn test_format_duration_seconds() {
126        assert_eq!(format_duration(1e9), "1.00s");
127        assert_eq!(format_duration(5e9), "5.00s");
128        assert_eq!(format_duration(30e9), "30.00s");
129    }
130
131    #[test]
132    fn test_format_duration_minutes() {
133        assert_eq!(format_duration(60e9), "1m0.00s");
134        assert_eq!(format_duration(90e9), "1m30.00s");
135        assert_eq!(format_duration(150e9), "2m30.00s");
136    }
137
138    #[test]
139    fn test_format_duration_hours() {
140        assert_eq!(format_duration(3600e9), "1h0m0.00s");
141        assert_eq!(format_duration(3661e9), "1h1m1.00s");
142        assert_eq!(format_duration(7384e9), "2h3m4.00s");
143    }
144
145    #[test]
146    fn test_format_bytes_bytes() {
147        assert_eq!(format_bytes(0.0), "0B");
148        assert_eq!(format_bytes(100.0), "100B");
149        assert_eq!(format_bytes(1023.0), "1023B");
150    }
151
152    #[test]
153    fn test_format_bytes_kibibytes() {
154        assert_eq!(format_bytes(1024.0), "1.00KiB");
155        assert_eq!(format_bytes(2048.0), "2.00KiB");
156        assert_eq!(format_bytes(1536.0), "1.50KiB");
157    }
158
159    #[test]
160    fn test_format_bytes_mebibytes() {
161        assert_eq!(format_bytes(1024.0 * 1024.0), "1.00MiB");
162        assert_eq!(format_bytes(2.5 * 1024.0 * 1024.0), "2.50MiB");
163    }
164
165    #[test]
166    fn test_format_bytes_gibibytes() {
167        assert_eq!(format_bytes(1024.0_f64.powi(3)), "1.00GiB");
168        assert_eq!(format_bytes(5.5 * 1024.0_f64.powi(3)), "5.50GiB");
169    }
170
171    #[test]
172    fn test_format_bytes_tebibytes() {
173        assert_eq!(format_bytes(1024.0_f64.powi(4)), "1.00TiB");
174        assert_eq!(format_bytes(2.75 * 1024.0_f64.powi(4)), "2.75TiB");
175    }
176
177    #[test]
178    fn test_format_bytes_pebibytes() {
179        assert_eq!(format_bytes(1024.0_f64.powi(5)), "1.00PiB");
180        assert_eq!(format_bytes(2.75 * 1024.0_f64.powi(5)), "2.75PiB");
181    }
182
183    #[test]
184    fn test_format_float() {
185        assert_eq!(Format::Float.format(42.567), "42.57");
186        assert_eq!(Format::Float.format(0.123), "0.12");
187        assert_eq!(Format::Float.format(1000.0), "1000.00");
188    }
189
190    #[test]
191    fn test_format_hex() {
192        assert_eq!(Format::Hex.format(255.0), "0xff");
193        assert_eq!(Format::Hex.format(16.0), "0x10");
194        assert_eq!(Format::Hex.format(0.0), "0x0");
195    }
196
197    #[test]
198    fn test_format_time() {
199        assert_eq!(Format::Time.format(1e6), "1.00ms");
200        assert_eq!(Format::Time.format(60e9), "1m0.00s");
201    }
202
203    #[test]
204    fn test_format_bytes_format() {
205        assert_eq!(Format::Bytes.format(1024.0), "1.00KiB");
206        assert_eq!(Format::Bytes.format(1024.0_f64.powi(2)), "1.00MiB");
207    }
208
209    #[test]
210    fn test_get_display_scale_time_nanoseconds() {
211        let (scale, unit) = get_display_scale(500.0, Format::Time);
212        assert_eq!(scale, 1.0);
213        assert_eq!(unit, "ns");
214    }
215
216    #[test]
217    fn test_get_display_scale_time_microseconds() {
218        let (scale, unit) = get_display_scale(5e3, Format::Time);
219        assert_eq!(scale, 1e3);
220        assert_eq!(unit, "µs");
221    }
222
223    #[test]
224    fn test_get_display_scale_time_milliseconds() {
225        let (scale, unit) = get_display_scale(5e6, Format::Time);
226        assert_eq!(scale, 1e6);
227        assert_eq!(unit, "ms");
228    }
229
230    #[test]
231    fn test_get_display_scale_time_seconds() {
232        let (scale, unit) = get_display_scale(5e9, Format::Time);
233        assert_eq!(scale, 1e9);
234        assert_eq!(unit, "s");
235    }
236
237    #[test]
238    fn test_get_display_scale_bytes_b() {
239        let (scale, unit) = get_display_scale(512.0, Format::Bytes);
240        assert_eq!(scale, 1.0);
241        assert_eq!(unit, "B");
242    }
243
244    #[test]
245    fn test_get_display_scale_bytes_kib() {
246        let (scale, unit) = get_display_scale(2048.0, Format::Bytes);
247        assert_eq!(scale, 1024.0);
248        assert_eq!(unit, "KiB");
249    }
250
251    #[test]
252    fn test_get_display_scale_bytes_mib() {
253        let (scale, unit) = get_display_scale(5.0 * 1024.0_f64.powi(2), Format::Bytes);
254        assert_eq!(scale, 1024.0_f64.powi(2));
255        assert_eq!(unit, "MiB");
256    }
257
258    #[test]
259    fn test_get_display_scale_float() {
260        let (scale, unit) = get_display_scale(1000.0, Format::Float);
261        assert_eq!(scale, 1.0);
262        assert_eq!(unit, "");
263    }
264
265    #[test]
266    fn test_get_display_scale_hex() {
267        let (scale, unit) = get_display_scale(255.0, Format::Hex);
268        assert_eq!(scale, 1.0);
269        assert_eq!(unit, "");
270    }
271}