pretty_bytes_rust/
lib.rs

1//! pretty_bytes takes a [u64] that represent a number of bytes and output a [String] in prettier way that make
2//! sense for a human being.
3//!
4//! Provide customizations to use calculation with `1000` or `1024` as well as how many decimal to display.
5//!
6mod pretty_bytes_lib;
7// Expose
8pub use crate::pretty_bytes_lib::PrettyBytesOptions;
9// Do not expose
10use crate::pretty_bytes_lib::{PrettyBytesOptionWithDefault, BIT_UNITS, BYTE_UNITS};
11use std::cmp;
12
13/// pretty_bytes main function that convert bytes into a pretty format that is easier
14/// to read for a humain
15///
16/// # Arguments
17/// The first argument is the number of bytes to transform
18///
19/// The second argument is the option. Can be set to `None` to use the default of 2 decimals and 1024 bytes per unit
20///  
21/// # Examples
22/// ## Example without option (default)
23/// ```rust
24/// use pretty_bytes_rust::pretty_bytes;
25/// let r1 = pretty_bytes(1024 * 1024 * 5 + 50000, None);
26/// assert_eq!(r1, "5.05 Mbit");
27///
28/// ```
29///
30/// ## Example with options to false
31/// ```rust
32/// use pretty_bytes_rust::pretty_bytes;
33/// use pretty_bytes_rust::PrettyBytesOptions;
34/// let r2 = pretty_bytes(1024 * 1024 * 9 + 123, Some(PrettyBytesOptions {
35///           use_1024_instead_of_1000: Some(false),
36///           number_of_decimal: Some(3),
37///           remove_zero_decimal: Some(false)
38///        }));
39/// assert_eq!(r2, "9.437 MB");
40/// ```
41/// ## Example with options to true
42/// ```rust
43/// use pretty_bytes_rust::pretty_bytes;
44/// use pretty_bytes_rust::PrettyBytesOptions;
45/// let r2 = pretty_bytes(1024 * 1024 * 9 + 123, Some(PrettyBytesOptions {
46///           use_1024_instead_of_1000: Some(true),
47///           number_of_decimal: Some(3),
48///           remove_zero_decimal: Some(true)
49///        }));
50/// assert_eq!(r2, "9.000 Mbit");
51/// ```
52pub fn pretty_bytes(bytes: u64, options: Option<PrettyBytesOptions>) -> String {
53    let options_with_default = set_default_options(options);
54
55    let delimiter = if options_with_default.use_1024_instead_of_1000 {
56        1024_f64
57    } else {
58        1000_f64
59    };
60
61    let units = if options_with_default.use_1024_instead_of_1000 {
62        BIT_UNITS
63    } else {
64        BYTE_UNITS
65    };
66
67    let max_units_index = units.len() - 1;
68    let index = get_unit_index(bytes as f64, delimiter, max_units_index as i32);
69    let pretty_bytes = get_string(
70        bytes as f64,
71        delimiter,
72        index,
73        options_with_default.number_of_decimal,
74        options_with_default.remove_zero_decimal,
75    );
76    let unit = units[index as usize];
77    format!("{} {}", pretty_bytes, unit)
78}
79
80fn get_unit_index(bytes: f64, delimiter: f64, max_units_index: i32) -> i32 {
81    // println!("Bytes {}", bytes as f64);
82    // println!("Delimiter {}", delimiter);
83    // println!("Bytes log {}", (bytes as f64).ln());
84    // println!("Delimiter log {}", delimiter.ln());
85    let mut bytes_log = (bytes as f64).ln();
86    if bytes_log < 0.0 {
87        bytes_log = 0.0;
88    }
89    cmp::min(
90        (bytes_log / delimiter.ln()).floor() as i32,
91        max_units_index as i32,
92    )
93}
94
95fn get_string(
96    bytes: f64,
97    delimiter: f64,
98    index: i32,
99    number_of_decimal: usize,
100    remove_zero_decimal: bool,
101) -> String {
102    let result = bytes as f64 / delimiter.powi(index);
103    let mut number_of_decimal_applied = number_of_decimal;
104    if remove_zero_decimal {
105        let result_without_decimal = result as i64 as f64;
106        if result_without_decimal == result {
107            number_of_decimal_applied = 0;
108        }
109    }
110    format!("{:.1$}", result, number_of_decimal_applied)
111}
112
113fn set_default_options(user_options: Option<PrettyBytesOptions>) -> PrettyBytesOptionWithDefault {
114    // Ensure if we the user passed nothing that we have a type with all options with no value
115    let output_format = user_options.unwrap_or(PrettyBytesOptions {
116        use_1024_instead_of_1000: None,
117        number_of_decimal: None,
118        remove_zero_decimal: None,
119    });
120
121    // Give default value to all options not defined by the user
122    PrettyBytesOptionWithDefault {
123        use_1024_instead_of_1000: output_format.use_1024_instead_of_1000.unwrap_or(true),
124        number_of_decimal: output_format.number_of_decimal.unwrap_or(2),
125        remove_zero_decimal: output_format.remove_zero_decimal.unwrap_or(false),
126    }
127}
128#[cfg(test)]
129mod test_set_default_options {
130    use super::*;
131
132    #[test]
133    fn test_set_default_options_no_option() {
134        let result = set_default_options(None);
135        assert_eq!(result.number_of_decimal, 2, "Number of decimal");
136        assert_eq!(result.use_1024_instead_of_1000, true, "Default 1024");
137        assert_eq!(result.remove_zero_decimal, false, "Remove zero decimal");
138    }
139    #[test]
140    fn test_set_default_options_use_user_option() {
141        let result = set_default_options(Some(PrettyBytesOptions {
142            number_of_decimal: Some(5),
143            use_1024_instead_of_1000: Some(false),
144            remove_zero_decimal: Some(false),
145        }));
146        assert_eq!(result.number_of_decimal, 5, "Number of decimal");
147        assert_eq!(result.use_1024_instead_of_1000, false, "Default 1024");
148        assert_eq!(result.remove_zero_decimal, false, "Remove zero decimal");
149    }
150}
151#[cfg(test)]
152mod test_get_unit_index {
153    use super::*;
154    #[test]
155    fn test_get_unit_0() {
156        let result = get_unit_index(0_f64, 1024_f64, 9);
157        assert_eq!(result, 0)
158    }
159    #[test]
160    fn test_get_unit_1024_index_1023() {
161        let result = get_unit_index(1023_f64, 1024_f64, 9);
162        assert_eq!(result, 0)
163    }
164    #[test]
165    fn test_get_unit_1024_index_1024() {
166        let result = get_unit_index(1024_f64, 1024_f64, 9);
167        assert_eq!(result, 1)
168    }
169    #[test]
170    fn test_get_unit_1000_index_999() {
171        let result = get_unit_index(999_f64, 1000_f64, 9);
172        assert_eq!(result, 0)
173    }
174    #[test]
175    fn test_get_unit_1000_index_1000() {
176        let result = get_unit_index(1000_f64, 1000_f64, 9);
177        assert_eq!(result, 1)
178    }
179    #[test]
180    fn test_get_unit_1024_index_1mb() {
181        let result = get_unit_index(1024_f64 * 1024_f64 * 1024_f64, 1024_f64, 9);
182        assert_eq!(result, 3)
183    }
184}
185
186#[cfg(test)]
187mod test_get_string {
188    use super::*;
189
190    #[test]
191    fn test_get_string_bytes() {
192        let result = get_string(1023_f64, 1024_f64, 0, 2, false);
193        assert_eq!(result, "1023.00")
194    }
195    #[test]
196    fn test_get_string_bytes_remove_decimal() {
197        let result = get_string(1023_f64, 1024_f64, 0, 2, true);
198        assert_eq!(result, "1023")
199    }
200    #[test]
201    fn test_get_string_kilobytes() {
202        let result = get_string(1024_f64, 1024_f64, 1, 2, false);
203        assert_eq!(result, "1.00")
204    }
205    #[test]
206    fn test_get_string_kilobytes2() {
207        let result = get_string(1024_f64 * 1024_f64 * 9_f64 + 123_f64, 1024_f64, 1, 3, true);
208        assert_eq!(result, "9216.120")
209    }
210    #[test]
211    fn test_get_string_kilobytes3() {
212        let result = get_string(1024_f64 * 1024_f64 * 9_f64 + 123_f64, 1000_f64, 1, 3, true);
213        assert_eq!(result, "9437.307")
214    }
215    #[test]
216    fn test_get_string_kilobytes_remove_decimal() {
217        let result = get_string(1024_f64, 1024_f64, 1, 2, true);
218        assert_eq!(result, "1")
219    }
220    #[test]
221    fn test_get_string_kilobytes_no_even() {
222        let result = get_string(1100_f64, 1024_f64, 1, 2, false);
223        assert_eq!(result, "1.07")
224    }
225    #[test]
226    fn test_get_string_kilobytes_no_even_remove_decimal() {
227        let result = get_string(1100_f64, 1024_f64, 1, 2, true);
228        assert_eq!(result, "1.07")
229    }
230    #[test]
231    fn test_get_string_kilobytes_no_even_more_decimal() {
232        let result = get_string(1100_f64, 1024_f64, 1, 3, false);
233        assert_eq!(result, "1.074")
234    }
235    #[test]
236    fn test_get_string_kilobytes_no_even_more_decimal_remove_decimal() {
237        let result = get_string(1100_f64, 1024_f64, 1, 3, true);
238        assert_eq!(result, "1.074")
239    }
240}