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 KiloByte,
57 MegaByte,
58 GigaByte,
59 TeraByte,
60 PetaByte,
61 ExaByte,
62 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 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 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 "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 "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 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 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 assert!(parse("1 000 B").is_err());
233 }
234
235 #[test]
236 fn to_and_from_str() {
237 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}