1use 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
26pub trait Format {
29 fn divisor(&self, size: u64) -> u64;
34
35 fn name(&self, size: u64) -> &'static str;
40}
41
42#[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#[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
91pub 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
105pub trait FmtSize {
107 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}