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 {}