Skip to content

Commit

Permalink
feat: display type
Browse files Browse the repository at this point in the history
  • Loading branch information
robjtede committed Feb 13, 2025
1 parent 0dcc0ac commit 5f599dc
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 106 deletions.
2 changes: 1 addition & 1 deletion examples/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn main() -> io::Result<()> {

if md.is_file() {
let file_size = md.len();
let file_size = bytesize::ByteSize::b(file_size);
let file_size = bytesize::ByteSize::b(file_size).display().iec_short();

println!("{file_size}\t{file_name}");
} else {
Expand Down
236 changes: 236 additions & 0 deletions src/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
use core::{fmt, write};

use crate::ByteSize;

/// Format / style to use when displaying a [`ByteSize`].
#[derive(Debug, Clone, Copy)]
pub(crate) enum Format {
Iec,
IecShort,
Si,
SiShort,
}

impl Format {
fn unit(self) -> u64 {
match self {
Format::Iec | Format::IecShort => crate::KIB,
Format::Si | Format::SiShort => crate::KB,
}
}

fn unit_base(self) -> f64 {
match self {
Format::Iec | Format::IecShort => crate::LN_KIB,
Format::Si | Format::SiShort => crate::LN_KB,
}
}

fn unit_prefixes(self) -> &'static [u8] {
match self {
Format::Iec | Format::IecShort => crate::UNITS_IEC.as_bytes(),
Format::Si | Format::SiShort => crate::UNITS_SI.as_bytes(),
}
}

fn unit_separator(self) -> &'static str {
match self {
Format::Iec | Format::Si => " ",
Format::IecShort | Format::SiShort => "",
}
}

fn unit_suffix(self) -> &'static str {
match self {
Format::Iec => "iB",
Format::Si => "B",
Format::IecShort | Format::SiShort => "",
}
}
}

/// Formatting display wrapper for [`ByteSize`].
///
/// Supports various styles, see methods. By default, the [`iec()`](Self::iec()) style is used.
///
/// # Examples
///
/// ```
/// # use bytesize::ByteSize;
/// assert_eq!(
/// "1.0 MiB",
/// ByteSize::mib(1).display().iec().to_string(),
/// );
///
/// assert_eq!(
/// "42.0k",
/// ByteSize::kb(42).display().si_short().to_string(),
/// );
/// ```
#[derive(Debug, Clone)]
pub struct Display {
pub(crate) byte_size: ByteSize,
pub(crate) format: Format,
}

impl Display {
/// Format using IEC (binary) units.
///
/// E.g., `4.2 MiB`.
#[must_use]
#[doc(alias = "binary")]
pub fn iec(mut self) -> Self {
self.format = Format::Iec;
self
}

/// Format using SI (decimal) units.
///
/// E.g., `4.2 MB`.
///
/// Designed to produce output compatible with `sort -h`.
#[must_use]
#[doc(alias = "binary")]
pub fn iec_short(mut self) -> Self {
self.format = Format::IecShort;
self
}

/// Format using a short style and IEC (binary) units.
///
/// E.g., `4.2M`.
#[must_use]
#[doc(alias = "decimal")]
pub fn si(mut self) -> Self {
self.format = Format::Si;
self
}

/// Format using a short style and SI (decimal) units.
///
/// E.g., `4.2M`.
#[must_use]
#[doc(alias = "decimal")]
pub fn si_short(mut self) -> Self {
self.format = Format::SiShort;
self
}
}

impl fmt::Display for Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bytes = self.byte_size.as_u64();

let unit = self.format.unit();
let unit_base = self.format.unit_base();

let unit_prefixes = self.format.unit_prefixes();
let unit_separator = self.format.unit_separator();
let unit_suffix = self.format.unit_suffix();

if bytes < unit {
write!(f, "{bytes}{unit_separator}B")?;
} else {
let size = bytes as f64;
let exp = match (size.ln() / unit_base) as usize {
0 => 1,
e => e,
};

let unit_prefix = unit_prefixes[exp - 1] as char;

write!(
f,
"{:.1}{unit_separator}{unit_prefix}{unit_suffix}",
(size / unit.pow(exp as u32) as f64),
)?;
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn to_string_iec() {
let display = Display {
byte_size: ByteSize::gib(1),
format: Format::Iec,
};
assert_eq!("1.0 GiB", display.to_string());

let display = Display {
byte_size: ByteSize::gb(1),
format: Format::Iec,
};
assert_eq!("953.7 MiB", display.to_string());
}

#[test]
fn to_string_si() {
let display = Display {
byte_size: ByteSize::gib(1),
format: Format::Si,
};
assert_eq!("1.1 GB", display.to_string());

let display = Display {
byte_size: ByteSize::gb(1),
format: Format::Si,
};
assert_eq!("1.0 GB", display.to_string());
}

#[test]
fn to_string_short() {
let display = Display {
byte_size: ByteSize::gib(1),
format: Format::IecShort,
};
assert_eq!("1.0G", display.to_string());

let display = Display {
byte_size: ByteSize::gb(1),
format: Format::IecShort,
};
assert_eq!("953.7M", display.to_string());
}

#[track_caller]
fn assert_to_string(expected: &str, byte_size: ByteSize, format: Format) {
assert_eq!(expected, Display { byte_size, format }.to_string());
}

#[test]
fn test_to_string_as() {
assert_to_string("215 B", ByteSize::b(215), Format::Iec);
assert_to_string("215 B", ByteSize::b(215), Format::Si);

assert_to_string("1.0 KiB", ByteSize::kib(1), Format::Iec);
assert_to_string("1.0 kB", ByteSize::kib(1), Format::Si);

assert_to_string("293.9 KiB", ByteSize::kb(301), Format::Iec);
assert_to_string("301.0 kB", ByteSize::kb(301), Format::Si);

assert_to_string("1.0 MiB", ByteSize::mib(1), Format::Iec);
assert_to_string("1.0 MB", ByteSize::mib(1), Format::Si);

assert_to_string("1.9 GiB", ByteSize::mib(1907), Format::Iec);
assert_to_string("2.0 GB", ByteSize::mib(1908), Format::Si);

assert_to_string("399.6 MiB", ByteSize::mb(419), Format::Iec);
assert_to_string("419.0 MB", ByteSize::mb(419), Format::Si);

assert_to_string("482.4 GiB", ByteSize::gb(518), Format::Iec);
assert_to_string("518.0 GB", ByteSize::gb(518), Format::Si);

assert_to_string("741.2 TiB", ByteSize::tb(815), Format::Iec);
assert_to_string("815.0 TB", ByteSize::tb(815), Format::Si);

assert_to_string("540.9 PiB", ByteSize::pb(609), Format::Iec);
assert_to_string("609.0 PB", ByteSize::pb(609), Format::Si);
}
}
Loading

0 comments on commit 5f599dc

Please sign in to comment.