bsize/display.rs
1// Copyright 2026 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use core::fmt;
16
17use crate::BSize;
18
19/// Display wrapper for [`BSize`].
20///
21/// Supports various styles, see methods. By default, the [`binary`] style is used.
22///
23/// [`binary`]: Display::binary
24///
25/// # Examples
26///
27/// ```
28/// # use bsize::BSize;
29/// assert_eq!(
30/// "1.0 MiB",
31/// BSize::<u64>::mib(1).display().binary().to_string(),
32/// );
33///
34/// assert_eq!(
35/// "42.0 kB",
36/// BSize::<u64>::kb(42).display().decimal().to_string(),
37/// );
38/// ```
39#[derive(Debug, Clone)]
40pub struct Display {
41 size: u64,
42 mode: DisplayMode,
43}
44
45#[derive(Debug, Clone)]
46enum DisplayMode {
47 Binary,
48 Decimal,
49}
50
51impl Display {
52 /// Format using binary units (e.g., `11.8MiB`)
53 pub fn binary(mut self) -> Self {
54 self.mode = DisplayMode::Binary;
55 self
56 }
57
58 /// Format using decimal units (e.g., `11.8MB`)
59 pub fn decimal(mut self) -> Self {
60 self.mode = DisplayMode::Decimal;
61 self
62 }
63
64 fn new(size: u64) -> Self {
65 Self {
66 size,
67 mode: DisplayMode::Binary,
68 }
69 }
70}
71
72impl fmt::Display for Display {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 let bytes = self.size;
75
76 let unit = match self.mode {
77 DisplayMode::Binary => 1024,
78 DisplayMode::Decimal => 1000,
79 };
80
81 let unit_prefixes = match self.mode {
82 DisplayMode::Binary => b"KMGTPE",
83 DisplayMode::Decimal => b"kMGTPE",
84 };
85 let unit_suffix = match self.mode {
86 DisplayMode::Binary => "iB",
87 DisplayMode::Decimal => "B",
88 };
89 let unit_separator = " ";
90 let precision = f.precision().unwrap_or(1);
91
92 if bytes < unit {
93 write!(f, "{bytes}{unit_separator}B")?;
94 } else {
95 let size = bytes as f64;
96
97 let mut ideal_prefix = 0usize;
98 let mut ideal_size = size;
99 loop {
100 ideal_prefix += 1;
101 ideal_size /= unit as f64;
102
103 if ideal_size < unit as f64 {
104 break;
105 }
106 }
107 let exp = ideal_prefix;
108
109 let unit_prefix = unit_prefixes[exp - 1] as char;
110
111 write!(
112 f,
113 "{:.precision$}{unit_separator}{unit_prefix}{unit_suffix}",
114 size / unit.pow(exp as u32) as f64,
115 )?;
116 }
117
118 Ok(())
119 }
120}
121
122macro_rules! impl_display {
123 ($($ty:ty),* $(,)?) => {
124 $(
125 impl BSize<$ty> {
126 /// Returns a display wrapper.
127 pub fn display(self) -> Display {
128 Display::new(self.0 as u64)
129 }
130 }
131 )*
132 };
133}
134
135impl_display!(u8, u16, u32, u64, usize);