bytesize/
parse.rs

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