grep_cli/
human.rs

1/// An error that occurs when parsing a human readable size description.
2///
3/// This error provides an end user friendly message describing why the
4/// description couldn't be parsed and what the expected format is.
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct ParseSizeError {
7    original: String,
8    kind: ParseSizeErrorKind,
9}
10
11#[derive(Clone, Debug, Eq, PartialEq)]
12enum ParseSizeErrorKind {
13    InvalidFormat,
14    InvalidInt(std::num::ParseIntError),
15    Overflow,
16}
17
18impl ParseSizeError {
19    fn format(original: &str) -> ParseSizeError {
20        ParseSizeError {
21            original: original.to_string(),
22            kind: ParseSizeErrorKind::InvalidFormat,
23        }
24    }
25
26    fn int(original: &str, err: std::num::ParseIntError) -> ParseSizeError {
27        ParseSizeError {
28            original: original.to_string(),
29            kind: ParseSizeErrorKind::InvalidInt(err),
30        }
31    }
32
33    fn overflow(original: &str) -> ParseSizeError {
34        ParseSizeError {
35            original: original.to_string(),
36            kind: ParseSizeErrorKind::Overflow,
37        }
38    }
39}
40
41impl std::error::Error for ParseSizeError {}
42
43impl std::fmt::Display for ParseSizeError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        use self::ParseSizeErrorKind::*;
46
47        match self.kind {
48            InvalidFormat => write!(
49                f,
50                "invalid format for size '{}', which should be a non-empty \
51                 sequence of digits followed by an optional 'K', 'M' or 'G' \
52                 suffix",
53                self.original
54            ),
55            InvalidInt(ref err) => write!(
56                f,
57                "invalid integer found in size '{}': {}",
58                self.original, err
59            ),
60            Overflow => write!(f, "size too big in '{}'", self.original),
61        }
62    }
63}
64
65impl From<ParseSizeError> for std::io::Error {
66    fn from(size_err: ParseSizeError) -> std::io::Error {
67        std::io::Error::new(std::io::ErrorKind::Other, size_err)
68    }
69}
70
71/// Parse a human readable size like `2M` into a corresponding number of bytes.
72///
73/// Supported size suffixes are `K` (for kilobyte), `M` (for megabyte) and `G`
74/// (for gigabyte). If a size suffix is missing, then the size is interpreted
75/// as bytes. If the size is too big to fit into a `u64`, then this returns an
76/// error.
77///
78/// Additional suffixes may be added over time.
79pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
80    let digits_end =
81        size.as_bytes().iter().take_while(|&b| b.is_ascii_digit()).count();
82    let digits = &size[..digits_end];
83    if digits.is_empty() {
84        return Err(ParseSizeError::format(size));
85    }
86    let value =
87        digits.parse::<u64>().map_err(|e| ParseSizeError::int(size, e))?;
88
89    let suffix = &size[digits_end..];
90    if suffix.is_empty() {
91        return Ok(value);
92    }
93    let bytes = match suffix {
94        "K" => value.checked_mul(1 << 10),
95        "M" => value.checked_mul(1 << 20),
96        "G" => value.checked_mul(1 << 30),
97        _ => return Err(ParseSizeError::format(size)),
98    };
99    bytes.ok_or_else(|| ParseSizeError::overflow(size))
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn suffix_none() {
108        let x = parse_human_readable_size("123").unwrap();
109        assert_eq!(123, x);
110    }
111
112    #[test]
113    fn suffix_k() {
114        let x = parse_human_readable_size("123K").unwrap();
115        assert_eq!(123 * (1 << 10), x);
116    }
117
118    #[test]
119    fn suffix_m() {
120        let x = parse_human_readable_size("123M").unwrap();
121        assert_eq!(123 * (1 << 20), x);
122    }
123
124    #[test]
125    fn suffix_g() {
126        let x = parse_human_readable_size("123G").unwrap();
127        assert_eq!(123 * (1 << 30), x);
128    }
129
130    #[test]
131    fn invalid_empty() {
132        assert!(parse_human_readable_size("").is_err());
133    }
134
135    #[test]
136    fn invalid_non_digit() {
137        assert!(parse_human_readable_size("a").is_err());
138    }
139
140    #[test]
141    fn invalid_overflow() {
142        assert!(parse_human_readable_size("9999999999999999G").is_err());
143    }
144
145    #[test]
146    fn invalid_suffix() {
147        assert!(parse_human_readable_size("123T").is_err());
148    }
149}