askama/filters/
humansize.rs1use core::convert::Infallible;
2use core::fmt;
3use core::mem::MaybeUninit;
4
5use super::FastWritable;
6use crate::ascii_str::{AsciiChar, AsciiStr};
7
8#[inline]
26pub fn filesizeformat(b: f32) -> Result<FilesizeFormatFilter, Infallible> {
27 Ok(FilesizeFormatFilter(b))
28}
29
30#[derive(Debug, Clone, Copy)]
31pub struct FilesizeFormatFilter(f32);
32
33impl fmt::Display for FilesizeFormatFilter {
34 #[inline]
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 Ok(self.write_into(f)?)
37 }
38}
39
40impl FastWritable for FilesizeFormatFilter {
41 fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
42 if self.0 < 1e3 {
43 (self.0 as u32).write_into(dest)?;
44 Ok(dest.write_str(" B")?)
45 } else if let Some((prefix, factor)) = SI_PREFIXES
46 .iter()
47 .copied()
48 .find_map(|(prefix_factor, max)| (self.0 < max).then_some(prefix_factor))
49 {
50 let scaled = (self.0 * factor) as u32;
52 (scaled / 100).write_into(dest)?;
53 format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest)
54 } else {
55 too_big(self.0, dest)
56 }
57 }
58}
59
60fn format_frac(buffer: &mut MaybeUninit<[AsciiChar; 8]>, prefix: AsciiChar, scaled: u32) -> &str {
62 let buffer = buffer.write(AsciiStr::new_sized("..0 kB"));
64 buffer[4] = prefix;
65
66 let frac = scaled % 100;
67 let buffer = if frac == 0 {
68 &buffer[3..6]
69 } else {
70 let digits = AsciiChar::two_digits(frac);
71 if digits[1] == AsciiChar::new(b'0') {
72 buffer[2] = digits[0];
74 &buffer[1..6]
75 } else {
76 [buffer[1], buffer[2]] = digits;
78 &buffer[0..6]
79 }
80 };
81 AsciiStr::from_slice(buffer).as_str()
82}
83
84#[cold]
85fn too_big<W: fmt::Write + ?Sized>(value: f32, dest: &mut W) -> crate::Result<()> {
86 Ok(write!(dest, "{:.0} QB", value / 1e30)?)
88}
89
90const SI_PREFIXES: &[((AsciiChar, f32), f32)] = &[
92 ((AsciiChar::new(b'k'), 1e-1), 1e6),
93 ((AsciiChar::new(b'M'), 1e-4), 1e9),
94 ((AsciiChar::new(b'G'), 1e-7), 1e12),
95 ((AsciiChar::new(b'T'), 1e-10), 1e15),
96 ((AsciiChar::new(b'P'), 1e-13), 1e18),
97 ((AsciiChar::new(b'E'), 1e-16), 1e21),
98 ((AsciiChar::new(b'Z'), 1e-19), 1e24),
99 ((AsciiChar::new(b'Y'), 1e-22), 1e27),
100 ((AsciiChar::new(b'R'), 1e-25), 1e30),
101 ((AsciiChar::new(b'Q'), 1e-28), 1e33),
102];
103
104#[test]
105#[cfg(feature = "alloc")]
106fn test_filesizeformat() {
107 use alloc::string::ToString;
108
109 assert_eq!(filesizeformat(0.).unwrap().to_string(), "0 B");
110 assert_eq!(filesizeformat(999.).unwrap().to_string(), "999 B");
111 assert_eq!(filesizeformat(1000.).unwrap().to_string(), "1 kB");
112 assert_eq!(filesizeformat(1023.).unwrap().to_string(), "1.02 kB");
113 assert_eq!(filesizeformat(1024.).unwrap().to_string(), "1.02 kB");
114 assert_eq!(filesizeformat(1100.).unwrap().to_string(), "1.1 kB");
115 assert_eq!(filesizeformat(9_499_014.).unwrap().to_string(), "9.49 MB");
116 assert_eq!(
117 filesizeformat(954_548_589.2).unwrap().to_string(),
118 "954.54 MB"
119 );
120}