fmtsize/
lib.rs

1//! # fmtsize
2//!
3//! `fmtsize` provides human-readable formatting for things like file sizes. It
4//! attempts to find the largest shorthand size possible for a given value,
5//! although it's limited to "gigabytes." Someday we may upgrade to terabytes. :)
6//! 
7//! ```
8//! # use fmtsize::{Conventional, FmtSize};
9//! println!("{}", 492_752_310_u64.fmt_size(Conventional)); // 469.93 MB
10//! ```
11
12use std::fmt::{self, Display};
13
14mod conventional {
15    pub const KILOBYTE: u64 = 1 << 10;
16    pub const MEGABYTE: u64 = 1 << 20;
17    pub const GIGABYTE: u64 = 1 << 30;
18}
19
20mod decimal {
21    pub const KILOBYTE: u64 = 1000;
22    pub const MEGABYTE: u64 = 1_000_000;
23    pub const GIGABYTE: u64 = 1_000_000_000;
24}
25
26/// Used to format values in accordance with
27/// some set of named sizes and constants.
28pub trait Format {
29    /// The appropriate divisor for a given value size.
30    ///
31    /// E.g., to derive the number of megabytes in a given file, divide
32    /// file size by the size in bytes of one megabyte.
33    fn divisor(&self, size: u64) -> u64;
34
35    /// The appropriate name for a given value size.
36    ///
37    /// For instance, something larger than a single megabyte and smaller than
38    /// one gigabyte will be called "megabytes."
39    fn name(&self, size: u64) -> &'static str;
40}
41
42/// Old-school formatting: a megabyte is 1024 kilobytes, dammit!
43#[derive(Copy, Clone, Debug, Default)]
44pub struct Conventional;
45
46impl Format for Conventional {
47    fn divisor(&self, size: u64) -> u64 {
48        use conventional::*;
49        match size {
50            size if size < MEGABYTE => KILOBYTE,
51            size if size < GIGABYTE => MEGABYTE,
52            _ => GIGABYTE,
53        }
54    }
55
56    fn name(&self, size: u64) -> &'static str {
57        use conventional::*;
58        match size {
59            size if size < MEGABYTE => "KB",
60            size if size < GIGABYTE => "MB",
61            _ => "GB",
62        }
63    }
64}
65
66/// Nonsense formatting. "That hard drive is totally 1 TB! You're
67/// thinking of a TiB, which is totally different...."
68#[derive(Copy, Clone, Debug, Default)]
69pub struct Decimal;
70
71impl Format for Decimal {
72    fn divisor(&self, size: u64) -> u64 {
73        use decimal::*;
74        match size {
75            size if size < MEGABYTE => KILOBYTE,
76            size if size < GIGABYTE => MEGABYTE,
77            _ => GIGABYTE,
78        }
79    }
80
81    fn name(&self, size: u64) -> &'static str {
82        use conventional::*;
83        match size {
84            size if size < MEGABYTE => "KB",
85            size if size < GIGABYTE => "MB",
86            _ => "GB",
87        }
88    }
89}
90
91/// Lazy memory size formatter.
92pub struct ByteSizeFormatter<F = Conventional> {
93    size: u64,
94    fmt: F,
95}
96
97impl<F: Format> Display for ByteSizeFormatter<F> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let divisor = self.fmt.divisor(self.size) as f32;
100        let size = self.size as f32 / divisor;
101        write!(f, "{:.2} {}", size, self.fmt.name(self.size))
102    }
103}
104
105/// Allows formatting of integral values (u64) as human-readable sizes.
106pub trait FmtSize {
107    /// Format a memory size value according to a given format provider.
108    ///
109    /// The formatter resulting from this call is lazy.
110    fn fmt_size<F: Format>(self, fmt: F) -> ByteSizeFormatter<F>;
111}
112
113impl FmtSize for u64 {
114    fn fmt_size<F: Format>(self, fmt: F) -> ByteSizeFormatter<F> {
115        ByteSizeFormatter { size: self, fmt }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::{Conventional, FmtSize};
122
123    #[test]
124    fn it_works() {
125        let expected = "1.00 MB";
126        let actual = 1_048_576.fmt_size(Conventional).to_string();
127        assert_eq!(expected, actual);
128    }
129}