human_size/
lib.rs

1#![warn(
2    anonymous_parameters,
3    bare_trait_objects,
4    missing_debug_implementations,
5    missing_docs,
6    trivial_casts,
7    trivial_numeric_casts,
8    unused_extern_crates,
9    unused_import_braces,
10    unused_qualifications,
11    unused_results,
12    variant_size_differences
13)]
14#![forbid(unsafe_code)]
15
16//! The `human_size` crate represents sizes for humans.
17//!
18//! The main type is [`SpecificSize`], which (as the name might suggests)
19//! represents a size in specific multiple. Alternatively [`Size`] can be used
20//! to represent a size with a generic multiple (not defined at compile type).
21//!
22//! [`SpecificSize`]: struct.SpecificSize.html
23//! [`Size`]: type.Size.html
24//!
25//! # Example
26//!
27//! Below is small example that parses a size from a string and prints it.
28//!
29//! ```
30//! # extern crate human_size;
31//! # fn main() {
32//! use human_size::{Size, SpecificSize, Kilobyte};
33//!
34//! let size1: Size = "10000 B".parse().unwrap();
35//! assert_eq!(size1.to_string(), "10000 B");
36//!
37//! // Or using a specific multiple.
38//! let size2: SpecificSize<Kilobyte> = "10000 B".parse().unwrap();
39//! assert_eq!(size2.to_string(), "10 kB");
40//!
41//! // Generic and specific sizes can be compared.
42//! assert_eq!(size1, size2);
43//! # }
44//! ```
45//!
46//! # Notes
47//!
48//! Internally `f64` is used to represent the size, so when comparing sizes with
49//! different multiples be wary of rounding errors related to usage of floating
50//! point numbers.
51
52use std::cmp::Ordering;
53use std::error::Error;
54use std::fmt;
55use std::str::FromStr;
56
57pub mod multiples;
58
59pub use multiples::*;
60
61/// Size with a generic [`Multiple`].
62///
63/// # Notes
64///
65/// The size of `Size` is 16 bytes, but using a specific multiple, e.g.
66/// `SpecificSize<Byte>`, requires only 8 bytes.
67///
68/// [`Multiple`]: trait.Multiple.html
69pub type Size = SpecificSize<Any>;
70
71/// `SpecificSize` represents a size in bytes with a multiple.
72///
73/// `SpecificSize` can be created using the `new` function, or parsed from a
74/// string using the [`FromStr`] trait.
75///
76/// ```
77/// # extern crate human_size;
78/// # fn main() {
79/// use human_size::{SpecificSize, Size, Byte, Any};
80///
81/// let size1 = SpecificSize::new(1000, Byte).unwrap();
82/// assert_eq!(size1.to_string(), "1000 B");
83///
84/// // `Size` is a type alias for `SpecificSize<Any>`.
85/// let size2: Size = "1 kB".parse().unwrap();
86/// assert_eq!(size2.to_string(), "1 kB");
87///
88/// // Even though the multiples are different we can still compare them.
89/// assert_eq!(size1, size2);
90/// # }
91/// ```
92///
93/// Creating a `SpecificSize` with a specific [`Multiple`], e.g. [`Kilobyte`],
94/// only uses 8 bytes. Using the generic mulitple, i.e. [`Any`], it can
95/// represent all multiples but requires an extra 8 bytes for a total of 16
96/// bytes.
97///
98/// ```
99/// # extern crate human_size;
100/// # fn main() {
101/// use std::mem;
102///
103/// use human_size::{SpecificSize, Size, Byte, Any};
104///
105/// assert_eq!(mem::size_of::<SpecificSize<Byte>>(), 8);
106/// assert_eq!(mem::size_of::<Size>(), 16);
107/// # }
108/// ```
109///
110/// # Notes
111///
112/// When comparing sizes with one another it is to possible compare different
113/// multiples, see the first example above. However due to a lack of precision
114/// in floating point numbers equality ignores a difference less then
115/// `0.00000001`, after applying the multiple. See the `PartialEq`
116/// implementation (via \[src\] to the right) for details.
117///
118/// The same is true for converting to and from multiples, here again the lack
119/// of precision of floating points can be a cause of bugs.
120///
121/// [`FromStr`]: https://doc.rust-lang.org/nightly/core/str/trait.FromStr.html
122/// [`Multiple`]: trait.Multiple.html
123/// [`Kilobyte`]: multiples/struct.Kilobyte.html
124/// [`Any`]: multiples/enum.Any.html
125#[derive(Copy, Clone, Debug)]
126pub struct SpecificSize<M = Any> {
127    value: f64,
128    multiple: M,
129}
130
131impl<M: Multiple> SpecificSize<M> {
132    /// Create a new `SpecificSize` with the given value and multiple. If the
133    /// `value` is [not normal] this will return an error, however zero is
134    /// allowed. If the `value` is normal the result can be safely unwraped.
135    ///
136    /// ```
137    /// # extern crate human_size;
138    /// # fn main() {
139    /// use std::f64;
140    /// use human_size::{SpecificSize, Kilobyte, InvalidValueError};
141    ///
142    /// let size = SpecificSize::new(100, Kilobyte).unwrap();
143    /// assert_eq!(size.to_string(), "100 kB");
144    ///
145    /// let res = SpecificSize::new(f64::NAN, Kilobyte);
146    /// assert_eq!(res, Err(InvalidValueError)); // NAN is not a valid number.
147    /// # }
148    /// ```
149    ///
150    /// [not normal]: https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.is_normal
151    pub fn new<V>(value: V, multiple: M) -> Result<SpecificSize<M>, InvalidValueError>
152    where
153        V: Into<f64>,
154    {
155        let value = value.into();
156        if is_valid_value(value) {
157            Ok(SpecificSize { value, multiple })
158        } else {
159            Err(InvalidValueError)
160        }
161    }
162
163    /// Conversion between sizes with different multiples.
164    ///
165    /// This allows a size with one multiple to be converted into a size with
166    /// another multiple.
167    ///
168    /// ```
169    /// # extern crate human_size;
170    /// # fn main() {
171    /// use human_size::{SpecificSize, Byte, Kilobyte};
172    ///
173    /// let size = SpecificSize::new(1, Kilobyte).unwrap();
174    /// let size2: SpecificSize<Byte> = size.into();
175    ///
176    /// assert_eq!(size, size2);
177    /// assert_eq!(size.to_string(), "1 kB");
178    /// assert_eq!(size2.to_string(), "1000 B");
179    /// # }
180    /// ```
181    ///
182    /// # Notes
183    ///
184    /// Normally this would be done by implementing the `From` or `Into` trait.
185    /// However currently this is not possible due to the blanket implementation
186    /// in the standard library. Maybe once specialisation is available this can
187    /// be resolved.
188    pub fn into<M2>(self) -> SpecificSize<M2>
189    where
190        M2: Multiple,
191    {
192        let (value, any) = M::into_any(self);
193        M2::from_any(value, any)
194    }
195
196    /// Returns the size in current the multiple.
197    ///
198    /// ```
199    /// # extern crate human_size;
200    /// # fn main() {
201    /// use human_size::{SpecificSize, Kilobyte};
202    ///
203    /// let size = SpecificSize::new(1, Kilobyte).unwrap();
204    ///
205    /// assert_eq!(size.value(), 1.0);
206    /// # }
207    /// ```
208    pub fn value(self) -> f64 {
209        self.value
210    }
211
212    /// Returns the multiple.
213    ///
214    /// ```
215    /// # extern crate human_size;
216    /// # fn main() {
217    /// use human_size::{SpecificSize, Any, Kilobyte};
218    ///
219    /// let size1 = SpecificSize::new(1, Kilobyte).unwrap();
220    /// let size2 = SpecificSize::new(1, Any::Kilobyte).unwrap();
221    ///
222    /// assert_eq!(size1.multiple(), Kilobyte);
223    /// assert_eq!(size2.multiple(), Any::Kilobyte);
224    /// # }
225    /// ```
226    pub fn multiple(self) -> M {
227        self.multiple
228    }
229
230    /// Returns the size as bytes.
231    ///
232    /// # Notes
233    ///
234    /// Be careful of truncation for large file size.
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// # extern crate human_size;
240    /// # fn main() {
241    /// use human_size::{SpecificSize, Any, Kilobyte};
242    ///
243    /// let size1 = SpecificSize::new(1, Kilobyte).unwrap();
244    /// let size2 = SpecificSize::new(8, Any::Kilobyte).unwrap();
245    ///
246    /// assert_eq!(size1.to_bytes(), 1000);
247    /// assert_eq!(size2.to_bytes(), 8000);
248    /// # }
249    /// ```
250    pub fn to_bytes(self) -> u64 {
251        let (value, any) = M::into_any(self);
252        Byte::from_any(value, any).value as u64
253    }
254}
255
256/// Check if the provided `value` is valid.
257fn is_valid_value(value: f64) -> bool {
258    use std::num::FpCategory::*;
259    matches!(value.classify(), Normal | Zero)
260}
261
262impl<M: Multiple> FromStr for SpecificSize<M> {
263    type Err = ParsingError;
264
265    fn from_str(input: &str) -> Result<SpecificSize<M>, Self::Err> {
266        let input = input.trim();
267        if input.is_empty() {
268            return Err(ParsingError::EmptyInput);
269        }
270
271        let multiple_index = input
272            .chars()
273            .position(|c| !(c.is_numeric() || c == '.'))
274            .ok_or(ParsingError::MissingMultiple)?;
275        if multiple_index == 0 {
276            return Err(ParsingError::MissingValue);
277        }
278
279        let (value, multiple) = &input.split_at(multiple_index);
280        let value = value.parse().map_err(|_| ParsingError::InvalidValue)?;
281
282        if is_valid_value(value) {
283            let multiple = multiple.trim().parse()?;
284            Ok(M::from_any(value, multiple))
285        } else {
286            Err(ParsingError::InvalidValue)
287        }
288    }
289}
290
291#[cfg(feature = "serde")]
292impl<'de, M> serde::Deserialize<'de> for SpecificSize<M>
293where
294    M: Multiple,
295{
296    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
297    where
298        D: serde::Deserializer<'de>,
299    {
300        use std::marker::PhantomData;
301
302        use serde::de::{Error, Visitor};
303
304        struct SpecificSizeVisitor<M>(PhantomData<M>);
305
306        impl<'de, M: Multiple> Visitor<'de> for SpecificSizeVisitor<M> {
307            type Value = SpecificSize<M>;
308
309            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
310                formatter.write_str("size")
311            }
312
313            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
314            where
315                E: Error,
316            {
317                s.parse().map_err(Error::custom)
318            }
319        }
320
321        deserializer.deserialize_str(SpecificSizeVisitor(PhantomData))
322    }
323}
324
325#[cfg(feature = "serde")]
326impl<M> serde::Serialize for SpecificSize<M>
327where
328    M: Multiple + fmt::Display,
329{
330    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
331    where
332        S: serde::Serializer,
333    {
334        // NOTE: this is not the best method as this allocates a string that get
335        // dropped after using it. We could try to use the
336        // `serialize_display_bounded_length` macro from serde.
337        serializer.serialize_str(&*self.to_string())
338    }
339}
340
341/*
342TODO: Needs specialisation.
343impl<M1: Multiple, M2: Multiple> From<SpecificSize<M2>> for SpecificSize<M1> {
344    fn from(size: SpecificSize<M2>) -> Self {
345        let (value, any) = M2::into_any(size);
346        M1::from_any(value, any)
347    }
348}
349*/
350
351/*
352TODO: Enable to specialisation for the same M.
353impl<M> PartialEq for SpecificSize<M> {
354    fn eq(&self, other: &Self) -> bool {
355        self.value == other.value
356    }
357}
358*/
359
360/// The allowed margin to consider two floats still equal, after applying the
361/// multiple. Keep in sync with the Notes section of `SpecificSize`.
362const CMP_MARGIN: f64 = 0.000_000_01;
363
364impl<LM, RM> PartialEq<SpecificSize<RM>> for SpecificSize<LM>
365where
366    LM: Multiple + Copy,
367    RM: Multiple + Copy,
368{
369    fn eq(&self, other: &SpecificSize<RM>) -> bool {
370        // Ah... floating points...
371        // To negate the loss in accuracy we check if the difference between the
372        // values is really low and consider that the same.
373        let (left, right) = into_same_multiples(*self, *other);
374        let diff = left - right;
375        diff.abs() < CMP_MARGIN
376    }
377}
378
379impl<M> Eq for SpecificSize<M> where M: Multiple + Copy {}
380
381/*
382TODO: Enable to specialisation for the same M.
383impl<M> PartialOrd for SpecificSize<M> {
384    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
385        self.value.partial_cmp(&other.value)
386    }
387}
388*/
389
390impl<LM, RM> PartialOrd<SpecificSize<RM>> for SpecificSize<LM>
391where
392    LM: Multiple + Copy,
393    RM: Multiple + Copy,
394{
395    fn partial_cmp(&self, other: &SpecificSize<RM>) -> Option<Ordering> {
396        let (left, right) = into_same_multiples(*self, *other);
397        left.partial_cmp(&right)
398    }
399}
400
401/// Convert the provided `left` and `right` sizes into the same multiples,
402/// returning the values. For example if left is `1 Kilobyte`, and right is
403/// `1000 Byte`, it will return `(1, 1)` (in the multiple of Kilobyte).
404fn into_same_multiples<LM, RM>(left: SpecificSize<LM>, right: SpecificSize<RM>) -> (f64, f64)
405where
406    LM: Multiple,
407    RM: Multiple,
408{
409    let (left_value, left_multiple) = LM::into_any(left);
410    let (right_value, right_multiple) = RM::into_any(right);
411    let multiply = left_multiple.multiple_of_bytes() / right_multiple.multiple_of_bytes();
412    (left_value * multiply, right_value)
413}
414
415impl<M: fmt::Display> fmt::Display for SpecificSize<M> {
416    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
417        if let Some(precision) = f.precision() {
418            write!(f, "{:.*} {}", precision, self.value, self.multiple)
419        } else {
420            write!(f, "{} {}", self.value, self.multiple)
421        }
422    }
423}
424
425/// Trait to convert a [`SpecificSize`] to and from different multiples.
426///
427/// [`SpecificSize`]: struct.SpecificSize.html
428pub trait Multiple: Sized {
429    /// Create a new [`SpecificSize`] from a `value` and `multiple`, the
430    /// provided `value` must always valid (see [`SpecificSize::new`]).
431    ///
432    /// [`SpecificSize`]: struct.SpecificSize.html
433    /// [`SpecificSize::new`]: struct.SpecificSize.html#method.new
434    fn from_any(value: f64, multiple: Any) -> SpecificSize<Self>;
435
436    /// The opposite of `from_any`, converting self into the value and the
437    /// generic multiple.
438    fn into_any(size: SpecificSize<Self>) -> (f64, Any);
439}
440
441/// The error returned when trying to create a new [`SpecificSize`] with an
442/// invalid value.
443///
444/// [`SpecificSize`]: struct.SpecificSize.html
445#[derive(Copy, Clone, Debug, Eq, PartialEq)]
446pub struct InvalidValueError;
447
448impl fmt::Display for InvalidValueError {
449    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
450        ParsingError::InvalidValue.fmt(f)
451    }
452}
453
454impl Error for InvalidValueError {}
455
456/// The error returned when trying to parse a [`SpecificSize`], using the
457/// [`FromStr`] trait.
458///
459/// [`SpecificSize`]: struct.SpecificSize.html
460/// [`FromStr`]: https://doc.rust-lang.org/nightly/core/str/trait.FromStr.html
461#[derive(Copy, Clone, Debug, Eq, PartialEq)]
462pub enum ParsingError {
463    /// The provided string is empty, i.e. "".
464    EmptyInput,
465    /// The provided string is missing a value, e.g. "B".
466    MissingValue,
467    /// The value is invalid, see [`SpecificSize::new`].
468    ///
469    /// [`SpecificSize::new`]: struct.SpecificSize.html#method.new
470    InvalidValue,
471    /// The value is missing the multiple of bytes, e.g. "100".
472    MissingMultiple,
473    /// The multiple in the string is invalid, e.g. "100 invalid".
474    InvalidMultiple,
475}
476
477impl fmt::Display for ParsingError {
478    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
479        f.pad(match *self {
480            ParsingError::EmptyInput => "input is empty",
481            ParsingError::MissingValue => "no value",
482            ParsingError::InvalidValue => "invalid value",
483            ParsingError::MissingMultiple => "no multiple",
484            ParsingError::InvalidMultiple => "invalid multiple",
485        })
486    }
487}
488
489impl Error for ParsingError {}