Skip to main content

humanize_rs/bytes/
mod.rs

1//! This module is used to parse a string to byte size,
2//! supports units of 2^10 like "KiB", "MiB",
3//! or units of 1000 like "KB", "MB"
4//!
5//! # Example
6//!
7//! ```
8//! use humanize_rs::bytes::{Bytes, Unit};
9//!
10//! let gigabytes1 = Bytes::new(1, Unit::GiByte);
11//! let gigabytes2 = "1 GiB".parse::<Bytes>();
12//! assert_eq!(gigabytes1, gigabytes2);
13//! assert_eq!(gigabytes2.unwrap().size(), 1 << 30);
14//! ```
15
16use super::num::Int;
17use std::fmt;
18use std::str::FromStr;
19use ParseError;
20
21const IBYTES: [u64; 7] = [1, 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50, 1 << 60];
22const BYTES: [u64; 7] = [
23    1,
24    1_000,
25    1_000_000,
26    1_000_000_000,
27    1_000_000_000_000,
28    1_000_000_000_000_000,
29    1_000_000_000_000_000_000,
30];
31
32/// Bytes units, like "KB", "KiB"
33#[derive(Debug, Copy, Clone, Eq, PartialEq)]
34pub enum Unit {
35    /// 1 Byte
36    Byte,
37
38    /// 1 << 10 Byte
39    KiByte,
40
41    /// 1 << 20 Byte
42    MiByte,
43
44    /// 1 << 30 Byte
45    GiByte,
46
47    /// 1 << 40 Byte
48    TiByte,
49
50    /// 1 << 50 Byte
51    PiByte,
52
53    /// 1 << 60 Byte
54    EiByte,
55
56    /// 1000 Byte
57    KByte,
58
59    /// 1000 KByte
60    MByte,
61
62    /// 1000 MByte
63    GByte,
64
65    /// 1000 GByte
66    TByte,
67
68    /// 1000 TByte
69    PByte,
70
71    /// 1000 PByte
72    EByte,
73}
74
75impl Unit {
76    fn size<T: Int>(&self) -> Result<T, ParseError> {
77        let v = match self {
78            Unit::Byte => <T>::from_u64(1),
79            Unit::KiByte => <T>::from_u64(IBYTES[1]),
80            Unit::MiByte => <T>::from_u64(IBYTES[2]),
81            Unit::GiByte => <T>::from_u64(IBYTES[3]),
82            Unit::TiByte => <T>::from_u64(IBYTES[4]),
83            Unit::PiByte => <T>::from_u64(IBYTES[5]),
84            Unit::EiByte => <T>::from_u64(IBYTES[6]),
85            Unit::KByte => <T>::from_u64(BYTES[1]),
86            Unit::MByte => <T>::from_u64(BYTES[2]),
87            Unit::GByte => <T>::from_u64(BYTES[3]),
88            Unit::TByte => <T>::from_u64(BYTES[4]),
89            Unit::PByte => <T>::from_u64(BYTES[5]),
90            Unit::EByte => <T>::from_u64(BYTES[6]),
91        }.ok_or(ParseError::Overflow)?;
92
93        Ok(v)
94    }
95}
96
97impl fmt::Display for Unit {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        let unit = match self {
100            Unit::Byte => "B",
101            Unit::KiByte => "KiB",
102            Unit::MiByte => "MiB",
103            Unit::GiByte => "GiB",
104            Unit::TiByte => "TiB",
105            Unit::PiByte => "PiB",
106            Unit::EiByte => "EiB",
107            Unit::KByte => "KB",
108            Unit::MByte => "MB",
109            Unit::GByte => "GB",
110            Unit::TByte => "TB",
111            Unit::PByte => "PB",
112            Unit::EByte => "EB",
113        };
114
115        f.pad(unit)
116    }
117}
118
119impl FromStr for Unit {
120    type Err = ParseError;
121
122    fn from_str(s: &str) -> Result<Self, Self::Err> {
123        match s {
124            "" | "b" => Ok(Unit::Byte),
125            "ki" | "kib" => Ok(Unit::KiByte),
126            "mi" | "mib" => Ok(Unit::MiByte),
127            "gi" | "gib" => Ok(Unit::GiByte),
128            "ti" | "tib" => Ok(Unit::TiByte),
129            "pi" | "pib" => Ok(Unit::PiByte),
130            "ei" | "eib" => Ok(Unit::EiByte),
131            "k" | "kb" => Ok(Unit::KByte),
132            "m" | "mb" => Ok(Unit::MByte),
133            "g" | "gb" => Ok(Unit::GByte),
134            "t" | "tb" => Ok(Unit::TByte),
135            "p" | "pb" => Ok(Unit::PByte),
136            "e" | "eb" => Ok(Unit::EByte),
137            _ => Err(ParseError::InvalidUnit),
138        }
139    }
140}
141
142/// Size calculated in [`Unit::Byte`]
143///
144/// [`Unit::Byte`]: ./enum.Unit.html#variant.Byte
145#[derive(Debug, Copy, Clone, Eq, PartialEq)]
146pub struct Bytes<T: Int = usize>(T);
147
148impl Bytes {
149    /// Returns a `Bytes` with a numeric value and a specific unit, or a `ParseError` if exists,
150    /// only [`ParseError::Overflow`] here.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// use humanize_rs::bytes::{Bytes, Unit};
156    ///
157    /// let megabytes = Bytes::new(1, Unit::MByte).unwrap();
158    /// ```
159    ///
160    /// [`ParseError::Overflow`]: ../enum.ParseError.html#variant.Overflow
161    pub fn new<T: Int>(value: T, unit: Unit) -> Result<Bytes<T>, ParseError> {
162        let unit_size = unit.size::<T>()?;
163        let size = value.checked_mul(unit_size).ok_or(ParseError::Overflow)?;
164
165        Ok(Bytes(size))
166    }
167}
168
169impl<T: Int> Bytes<T> {
170    /// return inner value of Bytes
171    pub fn size(&self) -> T {
172        return self.0;
173    }
174}
175
176impl<T: Int> FromStr for Bytes<T> {
177    type Err = ParseError;
178
179    fn from_str(s: &str) -> Result<Self, Self::Err> {
180        let input = s.trim();
181        if input.is_empty() {
182            return Err(ParseError::EmptyInput);
183        }
184
185        let unit_index = input
186            .chars()
187            .position(|c| c.is_alphabetic() || c.is_whitespace())
188            .unwrap_or(input.len());
189
190        if unit_index == 0 {
191            return Err(ParseError::MissingValue);
192        }
193
194        let (vstr, ustr) = input.split_at(unit_index);
195        let unit = ustr.trim().to_lowercase().parse()?;
196        let value = vstr.parse::<T>().or(Err(ParseError::InvalidValue))?;
197
198        Bytes::new(value, unit)
199    }
200}
201
202#[cfg(test)]
203mod tests;