unc_units_core/
util.rs

1use crate::prefixes::{from_magnitude, MAGNITUDES, PATTERNS};
2use num_format::{Locale, ToFormattedString};
3use regex::{Regex, RegexSet};
4
5pub fn get_match(s: &str) -> Option<usize> {
6    let set = RegexSet::new(&PATTERNS).unwrap();
7    let matches: Vec<usize> = set.matches(s.trim_end()).into_iter().collect();
8    if matches.len() != 1 {
9        return None;
10    }
11    matches.get(0).copied()
12}
13
14pub fn get_magnitude(s: &str) -> i8 {
15    get_match(s).map_or(0, |m| MAGNITUDES[m])
16}
17
18pub fn clean(x: &str) -> String {
19    Regex::new(r"(?:[,_])|^(0|\s)+\b|(?i:\s|[μa-z])+$")
20        .unwrap()
21        .replace_all(x, "")
22        .to_string()
23}
24
25pub fn parse(with_units: &str, magnitude: i8) -> Option<String> {
26    let maginitude = magnitude + get_magnitude(with_units);
27    let num_str = clean(with_units);
28    let mut parts = num_str.split('.');
29    let whole_part = parts.next().unwrap();
30    let fraction_part = parts.next().unwrap_or("");
31
32    // For now these are options but will be refactor into errors
33    // Means more than one `.`s
34    if parts.next().is_some() {
35        return None;
36    }
37    if maginitude == 0 && !fraction_part.is_empty() {
38        return None;
39    }
40    if fraction_part.len() as i8 > maginitude {
41        return None;
42    }
43
44    return Some(format!(
45        "{}{}{}",
46        whole_part,
47        fraction_part,
48        "0".repeat(maginitude as usize - fraction_part.len())
49    ));
50}
51
52pub fn to_human(num: u128, base_unit: &str, maginitude: i8, adjust_magnitude: i8) -> String {
53    let nomination = u128::pow(10, maginitude as u32);
54    let quotient = num / nomination;
55    let remainder = num % nomination;
56
57    if quotient > 0 {
58        let int = quotient.to_formatted_string(&Locale::en);
59        let remainder_str = remainder.to_string();
60        let fraction = if remainder == 0 {
61            "".to_string()
62        } else {
63            let pad = (maginitude as isize - remainder_str.len() as isize).max(0) as usize;
64            format!(".{}{}", "0".repeat(pad), remainder_str)
65                .trim_end_matches('0')
66                .to_string()
67        };
68        let prefix = from_magnitude(adjust_magnitude).unwrap();
69        return format!("{}{} {}{}", int, fraction, prefix, base_unit);
70    }
71
72    to_human(num, base_unit, maginitude - 3, adjust_magnitude - 3)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    #[test]
79    fn it_works() {
80        assert_eq!(get_match("y").unwrap(), 19);
81        assert_eq!(get_magnitude("y"), -24);
82        assert_eq!(get_match("yocto").unwrap(), 19);
83        assert_eq!(get_magnitude("yocto"), -24);
84        assert_eq!(get_magnitude("1yocto"), -24);
85    }
86
87    #[test]
88    fn parse_test() {
89        assert_eq!(parse("1000m", 24).unwrap(), "1000000000000000000000000");
90    }
91}