//! # **Humansize** | |
//! | |
//! Humansize lets you easily represent file sizes in a human-friendly format. | |
//! You can specify your own formatting style, pick among the three defaults provided | |
//! by the library: | |
//! | |
//! * Decimal (Multiples of 1000, `KB` units) | |
//! * Binary (Multiples of 1024, `KiB` units) | |
//! * Conventional (Multiples of 1024, `KB` units) | |
//! | |
//! ## How to use it | |
//! | |
//! Simply import the `FileSize` trait and the options module and call the | |
//! file_size method on any positive integer, using one of the three standards | |
//! provided by the options module. | |
//! | |
//! ```rust | |
//! extern crate humansize; | |
//! use humansize::{FileSize, file_size_opts as options}; | |
//! | |
//! fn main() { | |
//! let size = 1000; | |
//! println!("Size is {}", size.file_size(options::DECIMAL).unwrap()); | |
//! | |
//! println!("Size is {}", size.file_size(options::BINARY).unwrap()); | |
//! | |
//! println!("Size is {}", size.file_size(options::CONVENTIONAL).unwrap()); | |
//! } | |
//! ``` | |
//! | |
//! If you wish to customize the way sizes are displayed, you may create your own custom `FileSizeOpts` struct | |
//! and pass that to the method. See the `custom_options.rs` file in the example folder. | |
static SCALE_DECIMAL: [&str; 9] = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; | |
static SCALE_DECIMAL_LONG: [&str; 9] = [ | |
"Bytes", | |
"Kilobytes", | |
"Megabytes", | |
"Gigabytes", | |
"Terabytes", | |
"Petabytes", | |
"Exabytes", | |
"Zettabytes", | |
"Yottabytes", | |
]; | |
static SCALE_BINARY: [&str; 9] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; | |
static SCALE_BINARY_LONG: [&str; 9] = [ | |
"Bytes", | |
"Kibibytes", | |
"Mebibytes", | |
"Gibibytes", | |
"Tebibytes", | |
"Pebibytes", | |
"Exbibytes", | |
"Zebibytes", | |
"Yobibytes", | |
]; | |
pub mod file_size_opts { | |
//! Describes the struct that holds the options needed by the `file_size` method. | |
//! The three most common formats are provided as constants to be used easily | |
#[derive(Debug, PartialEq, Copy, Clone)] | |
/// Holds the standard to use when displying the size. | |
pub enum Kilo { | |
/// The decimal scale and units | |
Decimal, | |
/// The binary scale and units | |
Binary, | |
} | |
#[derive(Debug, Copy, Clone)] | |
/// Forces a certain representation of the resulting file size. | |
pub enum FixedAt { | |
Byte, | |
Kilo, | |
Mega, | |
Giga, | |
Tera, | |
Peta, | |
Exa, | |
Zetta, | |
Yotta, | |
No, | |
} | |
/// Holds the options for the `file_size` method. | |
#[derive(Debug)] | |
pub struct FileSizeOpts { | |
/// The scale (binary/decimal) to divide against. | |
pub divider: Kilo, | |
/// The unit set to display. | |
pub units: Kilo, | |
/// The amount of decimal places to display if the decimal part is non-zero. | |
pub decimal_places: usize, | |
/// The amount of zeroes to display if the decimal part is zero. | |
pub decimal_zeroes: usize, | |
/// Whether to force a certain representation and if so, which one. | |
pub fixed_at: FixedAt, | |
/// Whether to use the full suffix or its abbreveation. | |
pub long_units: bool, | |
/// Whether to place a space between value and units. | |
pub space: bool, | |
/// An optional suffix which will be appended after the unit. | |
pub suffix: &'static str, | |
/// Whether to allow negative numbers as input. If `False`, negative values will return an error. | |
pub allow_negative: bool, | |
} | |
impl AsRef<FileSizeOpts> for FileSizeOpts { | |
fn as_ref(&self) -> &FileSizeOpts { | |
self | |
} | |
} | |
/// Options to display sizes in the binary format. | |
pub const BINARY: FileSizeOpts = FileSizeOpts { | |
divider: Kilo::Binary, | |
units: Kilo::Binary, | |
decimal_places: 2, | |
decimal_zeroes: 0, | |
fixed_at: FixedAt::No, | |
long_units: false, | |
space: true, | |
suffix: "", | |
allow_negative: false, | |
}; | |
/// Options to display sizes in the decimal format. | |
pub const DECIMAL: FileSizeOpts = FileSizeOpts { | |
divider: Kilo::Decimal, | |
units: Kilo::Decimal, | |
decimal_places: 2, | |
decimal_zeroes: 0, | |
fixed_at: FixedAt::No, | |
long_units: false, | |
space: true, | |
suffix: "", | |
allow_negative: false, | |
}; | |
/// Options to display sizes in the "conventional" format. | |
/// This 1024 as the value of the `Kilo`, but displays decimal-style units (`KB`, not `KiB`). | |
pub const CONVENTIONAL: FileSizeOpts = FileSizeOpts { | |
divider: Kilo::Binary, | |
units: Kilo::Decimal, | |
decimal_places: 2, | |
decimal_zeroes: 0, | |
fixed_at: FixedAt::No, | |
long_units: false, | |
space: true, | |
suffix: "", | |
allow_negative: false, | |
}; | |
} | |
/// The trait for the `file_size`method | |
pub trait FileSize { | |
/// Formats self according to the parameters in `opts`. `opts` can either be one of the | |
/// three defaults providedby the `file_size_opts` module, or be custom-defined according | |
/// to your needs | |
/// | |
/// # Errors | |
/// Will fail by default if called on a negative number. Override this behavior by setting | |
/// `allow_negative` to `True` in a custom options struct. | |
/// | |
/// # Examples | |
/// ```rust | |
/// use humansize::{FileSize, file_size_opts as options}; | |
/// | |
/// let size = 5128; | |
/// println!("Size is {}", size.file_size(options::DECIMAL).unwrap()); | |
/// ``` | |
/// | |
fn file_size<T: AsRef<FileSizeOpts>>(&self, opts: T) -> Result<String, String>; | |
} | |
fn f64_eq(left: f64, right: f64) -> bool { | |
left == right || (left - right).abs() <= std::f64::EPSILON | |
} | |
use self::file_size_opts::*; | |
macro_rules! impl_file_size_u { | |
(for $($t:ty)*) => ($( | |
impl FileSize for $t { | |
fn file_size<T: AsRef<FileSizeOpts>>(&self, _opts: T) -> Result<String, String> { | |
let opts = _opts.as_ref(); | |
let divider = match opts.divider { | |
Kilo::Decimal => 1000.0, | |
Kilo::Binary => 1024.0 | |
}; | |
let mut size: f64 = *self as f64; | |
let mut scale_idx = 0; | |
match opts.fixed_at { | |
FixedAt::No => { | |
while size >= divider { | |
size /= divider; | |
scale_idx += 1; | |
} | |
} | |
val => { | |
while scale_idx != val as usize { | |
size /= divider; | |
scale_idx += 1; | |
} | |
} | |
} | |
let mut scale = match (opts.units, opts.long_units) { | |
(Kilo::Decimal, false) => SCALE_DECIMAL[scale_idx], | |
(Kilo::Decimal, true) => SCALE_DECIMAL_LONG[scale_idx], | |
(Kilo::Binary, false) => SCALE_BINARY[scale_idx], | |
(Kilo::Binary, true) => SCALE_BINARY_LONG[scale_idx] | |
}; | |
// Remove "s" from the scale if the size is 1.x | |
if opts.long_units && f64_eq(size.trunc(), 1.0) { scale = &scale[0 .. scale.len()-1]; } | |
let places = if f64_eq(size.fract(), 0.0) { | |
opts.decimal_zeroes | |
} else { | |
opts.decimal_places | |
}; | |
let space = if opts.space {" "} else {""}; | |
Ok(format!("{:.*}{}{}{}", places, size, space, scale, opts.suffix)) | |
} | |
} | |
)*) | |
} | |
macro_rules! impl_file_size_i { | |
(for $($t:ty)*) => ($( | |
impl FileSize for $t { | |
fn file_size<T: AsRef<FileSizeOpts>>(&self, _opts: T) -> Result<String, String> { | |
let opts = _opts.as_ref(); | |
if *self < 0 && !opts.allow_negative { | |
return Err("Tried calling file_size on a negative value".to_owned()); | |
} else { | |
let sign = if *self < 0 { | |
"-" | |
} else { | |
"" | |
}; | |
Ok(format!("{}{}", sign, (self.abs() as u64).file_size(opts)?)) | |
} | |
} | |
} | |
)*) | |
} | |
impl_file_size_u!(for usize u8 u16 u32 u64); | |
impl_file_size_i!(for isize i8 i16 i32 i64); | |
#[test] | |
fn test_sizes() { | |
assert_eq!(0.file_size(BINARY).unwrap(), "0 B"); | |
assert_eq!(999.file_size(BINARY).unwrap(), "999 B"); | |
assert_eq!(1000.file_size(BINARY).unwrap(), "1000 B"); | |
assert_eq!(1000.file_size(DECIMAL).unwrap(), "1 KB"); | |
assert_eq!(1023.file_size(BINARY).unwrap(), "1023 B"); | |
assert_eq!(1023.file_size(DECIMAL).unwrap(), "1.02 KB"); | |
assert_eq!(1024.file_size(BINARY).unwrap(), "1 KiB"); | |
assert_eq!(1024.file_size(CONVENTIONAL).unwrap(), "1 KB"); | |
let semi_custom_options = file_size_opts::FileSizeOpts { | |
space: false, | |
..file_size_opts::DECIMAL | |
}; | |
assert_eq!(1000.file_size(semi_custom_options).unwrap(), "1KB"); | |
let semi_custom_options2 = file_size_opts::FileSizeOpts { | |
suffix: "/s", | |
..file_size_opts::BINARY | |
}; | |
assert_eq!(999.file_size(semi_custom_options2).unwrap(), "999 B/s"); | |
let semi_custom_options3 = file_size_opts::FileSizeOpts { | |
suffix: "/day", | |
space: false, | |
..file_size_opts::DECIMAL | |
}; | |
assert_eq!(1000.file_size(semi_custom_options3).unwrap(), "1KB/day"); | |
let semi_custom_options4 = file_size_opts::FileSizeOpts { | |
fixed_at: file_size_opts::FixedAt::Byte, | |
..file_size_opts::BINARY | |
}; | |
assert_eq!(2048.file_size(semi_custom_options4).unwrap(), "2048 B"); | |
let semi_custom_options5 = file_size_opts::FileSizeOpts { | |
fixed_at: file_size_opts::FixedAt::Kilo, | |
..file_size_opts::BINARY | |
}; | |
assert_eq!( | |
16584975.file_size(semi_custom_options5).unwrap(), | |
"16196.26 KiB" | |
); | |
let semi_custom_options6 = file_size_opts::FileSizeOpts { | |
fixed_at: file_size_opts::FixedAt::Tera, | |
decimal_places: 10, | |
..file_size_opts::BINARY | |
}; | |
assert_eq!( | |
15284975.file_size(semi_custom_options6).unwrap(), | |
"0.0000139016 TiB" | |
); | |
let semi_custom_options7 = file_size_opts::FileSizeOpts { | |
allow_negative: true, | |
..file_size_opts::DECIMAL | |
}; | |
assert_eq!( | |
(-5500).file_size(&semi_custom_options7).unwrap(), | |
"-5.50 KB" | |
); | |
assert_eq!((5500).file_size(&semi_custom_options7).unwrap(), "5.50 KB"); | |
} |