bytesize/
parse.rs

1use alloc::{format, string::String};
2use core::str;
3
4use super::ByteSize;
5
6impl str::FromStr for ByteSize {
7    type Err = String;
8
9    fn from_str(value: &str) -> Result<Self, Self::Err> {
10        if let Ok(v) = value.parse::<u64>() {
11            return Ok(Self(v));
12        }
13        let number = take_while(value, |c| c.is_ascii_digit() || c == '.');
14        match number.parse::<f64>() {
15            Ok(v) => {
16                let suffix = skip_while(&value[number.len()..], char::is_whitespace);
17                match suffix.parse::<Unit>() {
18                    Ok(u) => Ok(Self((v * u) as u64)),
19                    Err(error) => Err(format!(
20                        "couldn't parse {suffix:?} into a known SI unit, {error}"
21                    )),
22                }
23            }
24            Err(error) => Err(format!("couldn't parse {value:?} into a ByteSize, {error}")),
25        }
26    }
27}
28
29fn take_while<P>(s: &str, mut predicate: P) -> &str
30where
31    P: FnMut(char) -> bool,
32{
33    let offset = s
34        .chars()
35        .take_while(|ch| predicate(*ch))
36        .map(|ch| ch.len_utf8())
37        .sum();
38    &s[..offset]
39}
40
41fn skip_while<P>(s: &str, mut predicate: P) -> &str
42where
43    P: FnMut(char) -> bool,
44{
45    let offset: usize = s
46        .chars()
47        .skip_while(|ch| predicate(*ch))
48        .map(|ch| ch.len_utf8())
49        .sum();
50    &s[(s.len() - offset)..]
51}
52
53enum Unit {
54    Byte,
55    // power of tens
56    KiloByte,
57    MegaByte,
58    GigaByte,
59    TeraByte,
60    PetaByte,
61    ExaByte,
62    // power of twos
63    KibiByte,
64    MebiByte,
65    GibiByte,
66    TebiByte,
67    PebiByte,
68    ExbiByte,
69}
70
71impl Unit {
72    fn factor(&self) -> u64 {
73        match self {
74            Self::Byte => 1,
75            // decimal units
76            Self::KiloByte => crate::KB,
77            Self::MegaByte => crate::MB,
78            Self::GigaByte => crate::GB,
79            Self::TeraByte => crate::TB,
80            Self::PetaByte => crate::PB,
81            Self::ExaByte => crate::EB,
82            // binary units
83            Self::KibiByte => crate::KIB,
84            Self::MebiByte => crate::MIB,
85            Self::GibiByte => crate::GIB,
86            Self::TebiByte => crate::TIB,
87            Self::PebiByte => crate::PIB,
88            Self::ExbiByte => crate::EIB,
89        }
90    }
91}
92
93mod impl_ops {
94    use super::Unit;
95    use core::ops;
96
97    impl ops::Add<u64> for Unit {
98        type Output = u64;
99
100        fn add(self, other: u64) -> Self::Output {
101            self.factor() + other
102        }
103    }
104
105    impl ops::Add<Unit> for u64 {
106        type Output = u64;
107
108        fn add(self, other: Unit) -> Self::Output {
109            self + other.factor()
110        }
111    }
112
113    impl ops::Mul<u64> for Unit {
114        type Output = u64;
115
116        fn mul(self, other: u64) -> Self::Output {
117            self.factor() * other
118        }
119    }
120
121    impl ops::Mul<Unit> for u64 {
122        type Output = u64;
123
124        fn mul(self, other: Unit) -> Self::Output {
125            self * other.factor()
126        }
127    }
128
129    impl ops::Add<f64> for Unit {
130        type Output = f64;
131
132        fn add(self, other: f64) -> Self::Output {
133            self.factor() as f64 + other
134        }
135    }
136
137    impl ops::Add<Unit> for f64 {
138        type Output = f64;
139
140        fn add(self, other: Unit) -> Self::Output {
141            other.factor() as f64 + self
142        }
143    }
144
145    impl ops::Mul<f64> for Unit {
146        type Output = f64;
147
148        fn mul(self, other: f64) -> Self::Output {
149            self.factor() as f64 * other
150        }
151    }
152
153    impl ops::Mul<Unit> for f64 {
154        type Output = f64;
155
156        fn mul(self, other: Unit) -> Self::Output {
157            other.factor() as f64 * self
158        }
159    }
160}
161
162impl str::FromStr for Unit {
163    type Err = String;
164
165    fn from_str(unit: &str) -> Result<Self, Self::Err> {
166        match unit.to_lowercase().as_str() {
167            "b" => Ok(Self::Byte),
168            // power of tens
169            "k" | "kb" => Ok(Self::KiloByte),
170            "m" | "mb" => Ok(Self::MegaByte),
171            "g" | "gb" => Ok(Self::GigaByte),
172            "t" | "tb" => Ok(Self::TeraByte),
173            "p" | "pb" => Ok(Self::PetaByte),
174            "e" | "eb" => Ok(Self::ExaByte),
175            // power of twos
176            "ki" | "kib" => Ok(Self::KibiByte),
177            "mi" | "mib" => Ok(Self::MebiByte),
178            "gi" | "gib" => Ok(Self::GibiByte),
179            "ti" | "tib" => Ok(Self::TebiByte),
180            "pi" | "pib" => Ok(Self::PebiByte),
181            "ei" | "eib" => Ok(Self::ExbiByte),
182            _ => Err(format!("couldn't parse unit of {unit:?}")),
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use alloc::string::ToString as _;
190
191    use super::*;
192
193    #[test]
194    fn when_ok() {
195        // shortcut for writing test cases
196        fn parse(s: &str) -> u64 {
197            s.parse::<ByteSize>().unwrap().0
198        }
199
200        assert_eq!("0".parse::<ByteSize>().unwrap().0, 0);
201        assert_eq!(parse("0"), 0);
202        assert_eq!(parse("500"), 500);
203        assert_eq!(parse("1K"), Unit::KiloByte * 1);
204        assert_eq!(parse("1Ki"), Unit::KibiByte * 1);
205        assert_eq!(parse("1.5Ki"), (1.5 * Unit::KibiByte) as u64);
206        assert_eq!(parse("1KiB"), 1 * Unit::KibiByte);
207        assert_eq!(parse("1.5KiB"), (1.5 * Unit::KibiByte) as u64);
208        assert_eq!(parse("3 MB"), Unit::MegaByte * 3);
209        assert_eq!(parse("4 MiB"), Unit::MebiByte * 4);
210        assert_eq!(parse("6 GB"), 6 * Unit::GigaByte);
211        assert_eq!(parse("4 GiB"), 4 * Unit::GibiByte);
212        assert_eq!(parse("88TB"), 88 * Unit::TeraByte);
213        assert_eq!(parse("521TiB"), 521 * Unit::TebiByte);
214        assert_eq!(parse("8 PB"), 8 * Unit::PetaByte);
215        assert_eq!(parse("8P"), 8 * Unit::PetaByte);
216        assert_eq!(parse("12 PiB"), 12 * Unit::PebiByte);
217    }
218
219    #[test]
220    fn when_err() {
221        // shortcut for writing test cases
222        fn parse(s: &str) -> Result<ByteSize, String> {
223            s.parse::<ByteSize>()
224        }
225
226        assert!(parse("").is_err());
227        assert!(parse("a124GB").is_err());
228        assert!(parse("1.3 42.0 B").is_err());
229        assert!(parse("1.3 ... B").is_err());
230        // The original implementation did not account for the possibility that users may
231        // use whitespace to visually separate digits, thus treat it as an error
232        assert!(parse("1 000 B").is_err());
233    }
234
235    #[test]
236    fn to_and_from_str() {
237        // shortcut for writing test cases
238        fn parse(s: &str) -> u64 {
239            s.parse::<ByteSize>().unwrap().0
240        }
241
242        assert_eq!(parse(&parse("128GB").to_string()), 128 * Unit::GigaByte);
243        assert_eq!(
244            parse(&ByteSize(parse("128.000 GiB")).to_string()),
245            128 * Unit::GibiByte,
246        );
247    }
248}