sizef/
lib.rs

1#![no_std]
2use core::fmt::{self, Debug, Display, Formatter, Write};
3mod constants;
4mod convenience;
5pub use constants::*;
6pub use convenience::*;
7
8pub struct Size(BYTES);
9
10impl Display for Size {
11    /// Formats the contained size with non-SI units(KiB, real powers of 2), into the first unit it
12    /// converts to as non-zero
13    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
14        self.fmt_with_units(f, UNITS.iter().copied())
15    }
16}
17
18impl Debug for Size {
19    /// Formats the contained size with SI units, into the first unit it converts to as non-zero
20    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
21        self.fmt_with_units(f, UNITS_SI.iter().copied())
22    }
23}
24
25impl<'a> Size {
26    fn fmt_with_units<I>(&self, f: &mut Formatter<'_>, mut units: I) -> fmt::Result
27    where
28        I: Iterator<Item = ByteUnit<'a>> + core::iter::DoubleEndedIterator,
29    {
30        let bytes = self.0;
31        let Some(ByteUnit { size, name }) = units.rfind(|unit| bytes >= unit.size) else {
32            return f.write_char('0');
33        };
34        let converted = bytes / size;
35        f.write_fmt(format_args!("{converted}{name}"))
36    }
37}
38
39pub struct LongSize(BYTES);
40
41impl Display for LongSize {
42    /// Formats the contained size with non-SI units(KiB, real powers of 2),
43    /// into every unit it converts into which ends up non-zero
44    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
45        self.fmt_with_units(f, UNITS.iter().copied())
46    }
47}
48
49impl Debug for LongSize {
50    /// Formats the contained size with SI units, printing every non-zero unit it converts into
51    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
52        self.fmt_with_units(f, UNITS_SI.iter().copied())
53    }
54}
55
56impl<'a> LongSize {
57    fn fmt_with_units<I>(&self, f: &mut Formatter<'_>, units: I) -> fmt::Result
58    where
59        I: Iterator<Item = ByteUnit<'a>> + core::iter::DoubleEndedIterator,
60    {
61        let mut bytes = self.0;
62        let mut units = units
63            // Iterate from the end of the units(largest unit) towards the smallest unit
64            .rev();
65        // Write first unit without a space at the beginning
66        let first_unit = 'firstunit: {
67            for unit in &mut units {
68                if bytes >= unit.size {
69                    break 'firstunit Some(unit);
70                }
71            }
72            None
73        };
74        let Some(ByteUnit { size, name }) = first_unit else {
75            return f.write_char('0');
76        };
77        let converted = bytes / size;
78        bytes -= converted * size;
79        write!(f, "{converted}{name}")?;
80        for ByteUnit { size, name } in &mut units {
81            // Filter to only include units that are bigger than the size we're trying to format
82            if bytes < size {
83                continue;
84            }
85            let converted = bytes / size;
86            bytes -= converted * size;
87            write!(f, " {converted}{name}")?
88        }
89
90        Ok(())
91    }
92}
93
94pub struct DecimalSize(BYTES);
95
96impl Display for DecimalSize {
97    /// Formats the contained size with non-SI units(KiB, real powers of 2), into the first unit it
98    /// converts to as non-zero
99    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
100        self.fmt_with_units(f, UNITS.iter().copied())
101    }
102}
103
104impl Debug for DecimalSize {
105    /// Formats the contained size with SI units, into the first unit it converts to as non-zero
106    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
107        self.fmt_with_units(f, UNITS_SI.iter().copied())
108    }
109}
110
111impl<'a> DecimalSize {
112    fn fmt_with_units<I>(&self, f: &mut Formatter<'_>, mut units: I) -> fmt::Result
113    where
114        I: Iterator<Item = ByteUnit<'a>> + core::iter::DoubleEndedIterator,
115    {
116        let bytes = self.0;
117        let Some(smallest) = units.next() else {
118            return f.write_char('0');
119        };
120        let ByteUnit { size, name } = units
121            // Iterate from the end of the units(largest unit) towards the smallest unit
122            .rfind(|unit| bytes >= unit.size)
123            .unwrap_or(smallest);
124
125        let converted = bytes as f32 / size as f32;
126
127        let round = converted % 1.0 < 0.1;
128        if round {
129            f.write_fmt(format_args!("{converted:.0}{name}"))
130        } else {
131            f.write_fmt(format_args!("{converted:.1}{name}"))
132        }
133    }
134}
135
136#[derive(Clone, Copy, Debug)]
137pub struct ByteUnit<'a> {
138    size: BYTES,
139    name: &'a str,
140}
141
142impl<'a> ByteUnit<'a> {
143    pub const fn new(size: BYTES, name: &'a str) -> Self {
144        Self { size, name }
145    }
146}
147
148mod tests;