use derive_more::From;
#[derive(Debug, Default, Clone, From)]
pub struct PrettySizeOptions {
#[from]
lowest_unit: SizeUnit,
}
impl From<&str> for PrettySizeOptions {
fn from(val: &str) -> Self {
SizeUnit::new(val).into()
}
}
impl From<&String> for PrettySizeOptions {
fn from(val: &String) -> Self {
SizeUnit::new(val).into()
}
}
impl From<String> for PrettySizeOptions {
fn from(val: String) -> Self {
SizeUnit::new(&val).into()
}
}
#[derive(Debug, Clone, Default)]
pub enum SizeUnit {
#[default]
B,
KB,
MB,
GB,
TB,
}
impl SizeUnit {
pub fn new(val: &str) -> Self {
match val.to_uppercase().as_str() {
"B" => Self::B,
"KB" => Self::KB,
"MB" => Self::MB,
"GB" => Self::GB,
"TB" => Self::TB,
_ => Self::B,
}
}
#[inline]
pub fn idx(&self) -> usize {
match self {
Self::B => 0,
Self::KB => 1,
Self::MB => 2,
Self::GB => 3,
Self::TB => 4,
}
}
}
impl From<&str> for SizeUnit {
fn from(val: &str) -> Self {
Self::new(val)
}
}
impl From<&String> for SizeUnit {
fn from(val: &String) -> Self {
Self::new(val)
}
}
impl From<String> for SizeUnit {
fn from(val: String) -> Self {
Self::new(&val)
}
}
pub fn pretty_size(size_in_bytes: u64) -> String {
pretty_size_with_options(size_in_bytes, PrettySizeOptions::default())
}
pub fn pretty_size_with_options(size_in_bytes: u64, options: impl Into<PrettySizeOptions>) -> String {
let options = options.into();
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
let min_unit_idx = options.lowest_unit.idx();
let mut size = size_in_bytes as f64;
for _ in 0..min_unit_idx {
size /= 1000.0;
}
let mut unit_idx = min_unit_idx;
while size >= 1000.0 && unit_idx < UNITS.len() - 1 {
size /= 1000.0;
unit_idx += 1;
}
let unit_str = UNITS[unit_idx];
if unit_idx == 0 {
let number_str = format!("{size_in_bytes:>6}");
format!("{number_str} {unit_str} ")
} else {
let number_str = format!("{size:>6.2}");
format!("{number_str} {unit_str}")
}
}
#[cfg(test)]
mod tests {
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
use super::*;
#[test]
fn test_pretty_size() -> Result<()> {
let cases = [
(777, " 777 B "),
(8777, " 8.78 KB"),
(88777, " 88.78 KB"),
(888777, "888.78 KB"),
(888700, "888.70 KB"),
(200000, "200.00 KB"),
(2_000_000, " 2.00 MB"),
(900_000_000, "900.00 MB"),
(2_345_678_900, " 2.35 GB"),
(1_234_567_890_123, " 1.23 TB"),
(2_345_678_900_123_456, " 2.35 PB"),
(0, " 0 B "),
];
for &(input, expected) in &cases {
let actual = pretty_size(input);
assert_eq!(actual, expected, "input: {input}");
}
Ok(())
}
#[test]
fn test_pretty_size_with_lowest_unit() -> Result<()> {
let options = PrettySizeOptions::from("MB");
let cases = [
(88777, " 0.09 MB"),
(888777, " 0.89 MB"),
(1_234_567, " 1.23 MB"),
];
for &(input, expected) in &cases {
let actual = pretty_size_with_options(input, options.clone());
assert_eq!(actual, expected, "input: {input}");
}
Ok(())
}
}