human_format/
lib.rs

1#![doc(html_root_url = "https://docs.rs/human_format")]
2
3//! `human_format` provides facilitates creating a formatted string, converting between numbers that are beyond typical
4//! needs for humans into a simpler string that conveys the gist of the meaning of the number.
5//!
6//! ## Setup
7//!
8//! Add the library to your dependencies listing
9//!
10//! ```toml
11//! [dependencies]
12//! human_format = "0.2"
13//! ```
14//!
15//! Add the crate reference at your crate root
16//!
17//! ```rust
18//! extern crate human_format;
19//! ```
20//!
21//! Print some human readable strings
22//!
23//! ```rust
24//! // "1.00 K"
25//! let tmpStr = human_format::Formatter::new()
26//!     .format(1000.0);
27//! # assert_eq!(tmpStr, "1.00 K");
28//!
29//! // "1.00 M"
30//! let tmpStr2 = human_format::Formatter::new()
31//!     .format(1000000.0);
32//! # assert_eq!(tmpStr2, "1.00 M");
33//!
34//! // "1.00 G"
35//! let tmpStr3 = human_format::Formatter::new()
36//!     .format(1000000000.0);
37//! # assert_eq!(tmpStr3, "1.00 G");
38//! ```
39//!
40//! If you are so inspired you can even try playing with units and customizing your `Scales`
41//!
42//! For more examples you should review the examples on github: [tests/demo.rs](https://github.com/BobGneu/human-format-rs/blob/master/tests/demo.rs)
43//!
44
45#[derive(Debug)]
46struct ScaledValue {
47    value: f64,
48    suffix: String,
49}
50
51/// Entry point to the lib. Use this to handle your formatting needs.
52#[derive(Debug)]
53pub struct Formatter {
54    decimals: usize,
55    separator: String,
56    scales: Scales,
57    forced_units: String,
58    forced_suffix: String,
59}
60
61impl Default for Formatter {
62    fn default() -> Self {
63        Formatter {
64            decimals: 2,
65            separator: " ".to_owned(),
66            scales: Scales::SI(),
67            forced_units: "".to_owned(),
68            forced_suffix: "".to_owned(),
69        }
70    }
71}
72
73/// Provide a customized scaling scheme for your own modeling.
74#[derive(Debug)]
75pub struct Scales {
76    base: u32,
77    suffixes: Vec<String>,
78}
79
80impl Formatter {
81    /// Initializes a new `Formatter` with default values.
82    pub fn new() -> Self {
83        Default::default()
84    }
85
86    /// Sets the decimals value for formatting the string.
87    pub fn with_decimals(&mut self, decimals: usize) -> &mut Self {
88        self.decimals = decimals;
89
90        self
91    }
92
93    /// Sets the separator value for formatting the string.
94    pub fn with_separator(&mut self, separator: &str) -> &mut Self {
95        self.separator = separator.to_owned();
96
97        self
98    }
99
100    /// Sets the scales value.
101    pub fn with_scales(&mut self, scales: Scales) -> &mut Self {
102        self.scales = scales;
103
104        self
105    }
106
107    /// Sets the units value.
108    pub fn with_units(&mut self, units: &str) -> &mut Self {
109        self.forced_units = units.to_owned();
110
111        self
112    }
113
114    /// Sets the expected suffix value.
115    pub fn with_suffix(&mut self, suffix: &str) -> &mut Self {
116        self.forced_suffix = suffix.to_owned();
117
118        self
119    }
120
121    /// Formats the number into a string
122    pub fn format(&self, value: f64) -> String {
123        if value < 0.0 {
124            return format!("-{}", self.format(value * -1.0));
125        }
126
127        let scaled_value = self.scales.to_scaled_value(value);
128
129        format!(
130            "{:.width$}{}{}{}",
131            scaled_value.value,
132            self.separator,
133            scaled_value.suffix,
134            self.forced_units,
135            width = self.decimals
136        )
137    }
138
139    /// Parse a string back into a float value.
140    pub fn parse(&self, value: &str) -> f64 {
141        let v: Vec<&str> = value.split(&self.separator).collect();
142
143        let result = v.first().unwrap().parse::<f64>().unwrap();
144
145        let mut suffix = v.get(1).unwrap().to_string();
146        let new_len = suffix.len() - self.forced_units.len();
147
148        suffix.truncate(new_len);
149
150        let magnitude_multiplier = self.scales.get_magnitude_multiplier(&suffix);
151
152        result * magnitude_multiplier
153    }
154
155    /// Attempt to parse a string back into a float value.
156    pub fn try_parse(&self, value: &str) -> Result<f64, String> {
157        // Remove suffix if present
158        let value = value.trim_end_matches(&self.forced_units).to_string();
159
160        // Find Suffix
161        let mut number = String::new();
162        for c in value.chars() {
163            if c.is_digit(10) || c == '.' {
164                number.push(c);
165            } else {
166                break;
167            }
168        }
169
170        let suffix = value
171            .trim_start_matches(&number)
172            .trim_start_matches(&self.separator)
173            .to_string();
174
175        let number = number.parse::<f64>().map_err(|e| e.to_string())?;
176        let magnitude_multiplier = self.scales.try_get_magnitude_multiplier(&suffix)?;
177
178        Ok(number * magnitude_multiplier)
179    }
180}
181
182impl Default for Scales {
183    fn default() -> Self {
184        Scales::SI()
185    }
186}
187
188impl Scales {
189    /// Instantiates a new `Scales` with SI keys
190    pub fn new() -> Self {
191        Scales::SI()
192    }
193
194    /// Instantiates a new `Scales` with SI keys
195    #[allow(non_snake_case)]
196    pub fn SI() -> Self {
197        Scales {
198            base: 1000,
199            suffixes: vec![
200                "".to_owned(),
201                "K".to_owned(),
202                "M".to_owned(),
203                "G".to_owned(),
204                "T".to_owned(),
205                "P".to_owned(),
206                "E".to_owned(),
207                "Z".to_owned(),
208                "Y".to_owned(),
209            ],
210        }
211    }
212
213    /// Instantiates a new `Scales` with Binary keys
214    #[allow(non_snake_case)]
215    pub fn Binary() -> Self {
216        Scales {
217            base: 1024,
218            suffixes: vec![
219                "".to_owned(),
220                "Ki".to_owned(),
221                "Mi".to_owned(),
222                "Gi".to_owned(),
223                "Ti".to_owned(),
224                "Pi".to_owned(),
225                "Ei".to_owned(),
226                "Zi".to_owned(),
227                "Yi".to_owned(),
228            ],
229        }
230    }
231
232    /// Sets the base for the `Scales`
233    pub fn with_base(&mut self, base: u32) -> &mut Self {
234        self.base = base;
235
236        self
237    }
238
239    /// Sets the suffixes listing appropriately
240    pub fn with_suffixes(&mut self, suffixes: Vec<&str>) -> &mut Self {
241        self.suffixes = Vec::new();
242
243        for suffix in suffixes {
244            // This should be to_owned to be clear about intent.
245            // https://users.rust-lang.org/t/to-string-vs-to-owned-for-string-literals/1441/6
246            self.suffixes.push(suffix.to_owned());
247        }
248
249        self
250    }
251
252    fn try_get_magnitude_multiplier(&self, value: &str) -> Result<f64, String> {
253        self.suffixes
254            .iter()
255            .enumerate()
256            .find_map(|(idx, x)| {
257                if value == x {
258                    Some((self.base as f64).powi(idx as i32))
259                } else {
260                    None
261                }
262            })
263            .ok_or_else(|| {
264                format!(
265                    "Unknown suffix: {value}, valid suffixes are: {}",
266                    self.suffixes
267                        .iter()
268                        .filter(|x| !x.trim().is_empty())
269                        .map(String::to_string)
270                        .collect::<Vec<_>>()
271                        .join(", ")
272                )
273            })
274    }
275
276    fn get_magnitude_multiplier(&self, value: &str) -> f64 {
277        for ndx in 0..self.suffixes.len() {
278            if value == self.suffixes[ndx] {
279                return (self.base as f64).powi(ndx as i32);
280            }
281        }
282
283        0.0
284    }
285
286    fn to_scaled_value(&self, value: f64) -> ScaledValue {
287        let mut index: usize = 0;
288        let base: f64 = self.base as f64;
289        let mut value = value;
290
291        loop {
292            if value < base {
293                break;
294            }
295
296            value /= base;
297            index += 1;
298        }
299
300        ScaledValue {
301            value,
302            suffix: self.suffixes[index].to_owned(),
303        }
304    }
305}