1use alloc::{borrow::ToOwned as _, format, string::String};
2use core::{fmt, 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
53#[non_exhaustive]
66#[derive(Debug, Clone, PartialEq)]
67pub enum Unit {
68 Byte,
70
71 KiloByte,
74
75 MegaByte,
77
78 GigaByte,
80
81 TeraByte,
83
84 PetaByte,
86
87 ExaByte,
89
90 KibiByte,
93
94 MebiByte,
96
97 GibiByte,
99
100 TebiByte,
102
103 PebiByte,
105
106 ExbiByte,
108}
109
110impl Unit {
111 fn factor(&self) -> u64 {
112 match self {
113 Self::Byte => 1,
114 Self::KiloByte => crate::KB,
116 Self::MegaByte => crate::MB,
117 Self::GigaByte => crate::GB,
118 Self::TeraByte => crate::TB,
119 Self::PetaByte => crate::PB,
120 Self::ExaByte => crate::EB,
121 Self::KibiByte => crate::KIB,
123 Self::MebiByte => crate::MIB,
124 Self::GibiByte => crate::GIB,
125 Self::TebiByte => crate::TIB,
126 Self::PebiByte => crate::PIB,
127 Self::ExbiByte => crate::EIB,
128 }
129 }
130}
131
132mod impl_ops {
133 use super::Unit;
134 use core::ops;
135
136 impl ops::Add<u64> for Unit {
137 type Output = u64;
138
139 fn add(self, other: u64) -> Self::Output {
140 self.factor() + other
141 }
142 }
143
144 impl ops::Add<Unit> for u64 {
145 type Output = u64;
146
147 fn add(self, other: Unit) -> Self::Output {
148 self + other.factor()
149 }
150 }
151
152 impl ops::Mul<u64> for Unit {
153 type Output = u64;
154
155 fn mul(self, other: u64) -> Self::Output {
156 self.factor() * other
157 }
158 }
159
160 impl ops::Mul<Unit> for u64 {
161 type Output = u64;
162
163 fn mul(self, other: Unit) -> Self::Output {
164 self * other.factor()
165 }
166 }
167
168 impl ops::Add<f64> for Unit {
169 type Output = f64;
170
171 fn add(self, other: f64) -> Self::Output {
172 self.factor() as f64 + other
173 }
174 }
175
176 impl ops::Add<Unit> for f64 {
177 type Output = f64;
178
179 fn add(self, other: Unit) -> Self::Output {
180 other.factor() as f64 + self
181 }
182 }
183
184 impl ops::Mul<f64> for Unit {
185 type Output = f64;
186
187 fn mul(self, other: f64) -> Self::Output {
188 self.factor() as f64 * other
189 }
190 }
191
192 impl ops::Mul<Unit> for f64 {
193 type Output = f64;
194
195 fn mul(self, other: Unit) -> Self::Output {
196 other.factor() as f64 * self
197 }
198 }
199}
200
201impl str::FromStr for Unit {
202 type Err = UnitParseError;
203
204 fn from_str(unit: &str) -> Result<Self, Self::Err> {
205 match () {
206 _ if unit.eq_ignore_ascii_case("b") => Ok(Self::Byte),
207 _ if unit.eq_ignore_ascii_case("k") | unit.eq_ignore_ascii_case("kb") => {
208 Ok(Self::KiloByte)
209 }
210 _ if unit.eq_ignore_ascii_case("m") | unit.eq_ignore_ascii_case("mb") => {
211 Ok(Self::MegaByte)
212 }
213 _ if unit.eq_ignore_ascii_case("g") | unit.eq_ignore_ascii_case("gb") => {
214 Ok(Self::GigaByte)
215 }
216 _ if unit.eq_ignore_ascii_case("t") | unit.eq_ignore_ascii_case("tb") => {
217 Ok(Self::TeraByte)
218 }
219 _ if unit.eq_ignore_ascii_case("p") | unit.eq_ignore_ascii_case("pb") => {
220 Ok(Self::PetaByte)
221 }
222 _ if unit.eq_ignore_ascii_case("e") | unit.eq_ignore_ascii_case("eb") => {
223 Ok(Self::ExaByte)
224 }
225 _ if unit.eq_ignore_ascii_case("ki") | unit.eq_ignore_ascii_case("kib") => {
226 Ok(Self::KibiByte)
227 }
228 _ if unit.eq_ignore_ascii_case("mi") | unit.eq_ignore_ascii_case("mib") => {
229 Ok(Self::MebiByte)
230 }
231 _ if unit.eq_ignore_ascii_case("gi") | unit.eq_ignore_ascii_case("gib") => {
232 Ok(Self::GibiByte)
233 }
234 _ if unit.eq_ignore_ascii_case("ti") | unit.eq_ignore_ascii_case("tib") => {
235 Ok(Self::TebiByte)
236 }
237 _ if unit.eq_ignore_ascii_case("pi") | unit.eq_ignore_ascii_case("pib") => {
238 Ok(Self::PebiByte)
239 }
240 _ if unit.eq_ignore_ascii_case("ei") | unit.eq_ignore_ascii_case("eib") => {
241 Ok(Self::ExbiByte)
242 }
243 _ => Err(UnitParseError(to_string_truncate(unit))),
244 }
245 }
246}
247
248fn to_string_truncate(unit: &str) -> String {
250 const MAX_UNIT_LEN: usize = 3;
251
252 if unit.len() > MAX_UNIT_LEN {
253 if unit.is_char_boundary(3) {
256 format!("{}...", &unit[..3])
257 } else if unit.is_char_boundary(4) {
258 format!("{}...", &unit[..4])
259 } else if unit.is_char_boundary(5) {
260 format!("{}...", &unit[..5])
261 } else if unit.is_char_boundary(6) {
262 format!("{}...", &unit[..6])
263 } else {
264 unreachable!("char boundary will be within 4 bytes")
265 }
266 } else {
267 unit.to_owned()
268 }
269}
270
271#[derive(Debug)]
273pub struct UnitParseError(String);
274
275impl fmt::Display for UnitParseError {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 write!(f, "Failed to parse unit \"{}\"", self.0)
278 }
279}
280
281#[cfg(feature = "std")]
282impl std::error::Error for UnitParseError {}
283
284#[cfg(test)]
285mod tests {
286 use alloc::string::ToString as _;
287
288 use super::*;
289
290 #[test]
291 fn truncating_error_strings() {
292 assert_eq!("", to_string_truncate(""));
293 assert_eq!("b", to_string_truncate("b"));
294 assert_eq!("ob", to_string_truncate("ob"));
295 assert_eq!("foo", to_string_truncate("foo"));
296
297 assert_eq!("foo...", to_string_truncate("foob"));
298 assert_eq!("foo...", to_string_truncate("foobar"));
299 }
300
301 #[test]
302 fn when_ok() {
303 fn parse(s: &str) -> u64 {
305 s.parse::<ByteSize>().unwrap().0
306 }
307
308 assert_eq!("0".parse::<ByteSize>().unwrap().0, 0);
309 assert_eq!(parse("0"), 0);
310 assert_eq!(parse("500"), 500);
311 assert_eq!(parse("1K"), Unit::KiloByte * 1);
312 assert_eq!(parse("1Ki"), Unit::KibiByte * 1);
313 assert_eq!(parse("1.5Ki"), (1.5 * Unit::KibiByte) as u64);
314 assert_eq!(parse("1KiB"), 1 * Unit::KibiByte);
315 assert_eq!(parse("1.5KiB"), (1.5 * Unit::KibiByte) as u64);
316 assert_eq!(parse("3 MB"), Unit::MegaByte * 3);
317 assert_eq!(parse("4 MiB"), Unit::MebiByte * 4);
318 assert_eq!(parse("6 GB"), 6 * Unit::GigaByte);
319 assert_eq!(parse("4 GiB"), 4 * Unit::GibiByte);
320 assert_eq!(parse("88TB"), 88 * Unit::TeraByte);
321 assert_eq!(parse("521TiB"), 521 * Unit::TebiByte);
322 assert_eq!(parse("8 PB"), 8 * Unit::PetaByte);
323 assert_eq!(parse("8P"), 8 * Unit::PetaByte);
324 assert_eq!(parse("12 PiB"), 12 * Unit::PebiByte);
325 }
326
327 #[test]
328 fn when_err() {
329 fn parse(s: &str) -> Result<ByteSize, String> {
331 s.parse::<ByteSize>()
332 }
333
334 assert!(parse("").is_err());
335 assert!(parse("a124GB").is_err());
336 assert!(parse("1.3 42.0 B").is_err());
337 assert!(parse("1.3 ... B").is_err());
338 assert!(parse("1 000 B").is_err());
341 }
342
343 #[test]
344 fn to_and_from_str() {
345 fn parse(s: &str) -> u64 {
347 s.parse::<ByteSize>().unwrap().0
348 }
349
350 assert_eq!(parse(&parse("128GB").to_string()), 128 * Unit::GigaByte);
351 assert_eq!(
352 parse(&ByteSize(parse("128.000 GiB")).to_string()),
353 128 * Unit::GibiByte,
354 );
355 }
356}