Version 2.0.0

This commit is contained in:
LeopoldArkham 2022-09-12 21:32:17 +02:00
parent 44802b23bb
commit 328bde1bdc
19 changed files with 589 additions and 279 deletions

View file

@ -1,19 +1,20 @@
[package]
name = "humansize"
version = "1.1.1"
version = "2.0.0"
authors = ["Leopold Arkham <leopold.arkham@gmail.com>"]
edition = "2021"
readme = "README.md"
description = "A configurable crate to easily represent file sizes in a human-readable format."
description = "A configurable crate to easily represent sizes in a human-readable format."
repository = "https://github.com/LeopoldArkham/humansize"
homepage = "https://github.com/LeopoldArkham/humansize"
documentation = "https://docs.rs/humansize"
keywords = ["file", "size", "sizes", "humanize", "bytes", "file size", "size formatting"]
keywords = ["size", "formatting", "humanize", "file-size"]
categories = ["value-formatting"]
license = "MIT/Apache-2.0"
[features]
no_alloc = []
impl_style = []
[dependencies]
libm = "0.2.5"

125
README.md
View file

@ -1,48 +1,116 @@
# **Humansize** ![travis badge](https://travis-ci.org/LeopoldArkham/humansize.svg?branch=master)
[Documentation](https://docs.rs/humansize/0.1.0/humansize/)
[Documentation](https://docs.rs/humansize/latest/humansize/)
## Features
Humansize is a humanization library for information size that is:
- Simple & convenient to use
- Customizable
- Supports byte or bit sizes
- `no-std`
- Optionally non-allocating
- Optionally accepts signed values
## How to use it...
Humansize lets you easily represent file sizes in a human-friendly format.
You can specify your own formatting style or pick among the three defaults provided
by the library:
* Decimal (kilo = 1000, unit format is `kB`)
* Binary (kilo = 1024, unit format is `KiB`)
* Windows/Conventional (kilo = 1024, unit format is `kB`)
## How to use it
Cargo.Toml:
```
Add humansize as a dependency to your project's `cargo.toml`:
```toml
[dependencies]
humansize = "1.1.1"
...
humansize = "2.0.0"
```
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.
### ... to easily format a size:
```rust,no_run
extern crate humansize;
use humansize::{FileSize, file_size_opts as options};
1. Import the `format_size` function as well as your preferred set of defaults:
- `DECIMAL` (SI)
- `BINARY` (IEC)
- `WINDOWS` (IEC values but SI units)
2. Call `format_size` with an unsigned integer
fn main() {
let size = 1000;
println!("Size is {}", size.file_size(options::DECIMAL).unwrap());
```rust
use humansize::{format_size, DECIMAL};
println!("Size is {}", size.file_size(options::BINARY).unwrap());
let size = 1_000_000u64;
let res: String = format_size(size, DECIMAL);
assert_eq!(&res, "1 MB");
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 `FormatSizeOptions` struct
and pass that to the method. See the `custom_options.rs` file in the example folder.
### ... to format many sizes:
To improve reusability, you can use `create_format`, which returns a formatter function akin to `format_size` but with the options argument curried so it doesn't need to be specified again:
```rust
use humansize::{make_format, DECIMAL};
let formatter = make_format(DECIMAL);
assert_eq!(formatter(1_000_000u64), "1 MB");
assert_eq!(formatter(1_000_000_000u64), "1 GB");
//...
```
### ... to avoid allocation:
Specify the `no_alloc` feature flag in your project's `cargo.toml`:
```toml
[dependencies]
...
humansize = { version = "2.0.0", features = ["no_alloc"] }
```
This excludes all allocating code from compilation. You may now use the library's internal `Formatter` struct, which implements `core::fmt::display` so that you can `write!` it to a custom buffer of your choice:
```rust
use humansize::{Formatter, DECIMAL};
let formatter = Formatter::new(1_000_000, DECIMAL);
assert_eq!(format!({}, formatter), '1 MB');
```
### ... with the `impl` style API:
For stylistic reasons, you may prefer to use the impl-style API of earlier versions of the crate.
To do so, specify the `impl-style` feature flag in your project's `cargo.toml`:
```toml
[dependencies]
...
humansize = { version = "2.0.0", features = ["impl_style"] }
```
Enabling this feature makes two methods available:
- `format_size` on unsigned integers types
- `format_size_i` on signed integer types.
To use it, bring the FormatSize trait into scope and call its method on an integer type:
```rust
use humansize::{FormatSize, Decimal};
assert_eq!(1_000_000u64.format_size(DECIMAL), "1 MB");
assert_eq!(-1_000_000).format_size_i(DECIMAL), "-1 MB");
```
### ... to further customize the output:
Humansize exports three default option sets:
* `Decimal`: kilo = 1000, unit format is `XB`.
* `Binary`: kilo = 1024, unit format is `XiB`.
* `WINDOWS` (Windows): kilo = 1024, unit format is `XB`.
The formatting can be further customized by providing providing your own option set. See the documentation of the `FormatSizeOptions` struct to see all the addressable parameters, and [this example](examples/custom_options.rs) for its usage.
### ... to accept negative values:
The solutions presented above only accept unsigned integer types as input (`usize`, `8`, `u16`, `u32` and `u64`). If however accepting negative values is correct for your application, a signed alternative exists for each of them that will accept signed integer types, and format them accordingly if negative:
- `format_size` : `format_size_i`
- `create_format` : `create_format_i`
- `FormatSize` trait : `FormatSizeI` trait
- `SizeFormatter` : `ISizeFormatter`
```rust
use humansize::{format_size_i, make_format_i, DECIMAL};
assert_eq!(&format_size_i(-1_000_000, DECIMAL), "-1 MB");
let signed_formatter = make_format_i(DECIMAL);
assert_eq!(&signed_formatter(-1_000_000), "-1 MB");
```
## License
@ -53,7 +121,6 @@ This project is licensed under either of
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution

View file

@ -1,27 +1,11 @@
extern crate humansize;
use humansize::{format_size, format_size_i, FixedAt, FormatSizeOptions, Kilo, DECIMAL};
use humansize::{format_size, FormatSizeOptions, DECIMAL};
fn main() {
// Declare a fully custom option struct
let custom_options = FormatSizeOptions {
kilo: Kilo::Binary,
units: Kilo::Decimal,
decimal_places: 3,
decimal_zeroes: 1,
fixed_at: FixedAt::No,
long_units: true,
space: false,
suffix: "",
};
// Create a new FormatSizeOptions struct starting from one of the defaults
let custom_options = FormatSizeOptions::from(DECIMAL).decimal_places(5);
// Then use it
println!("{}", format_size(3024usize, custom_options));
// Or use only some custom parameters and adopt the rest from an existing config
let semi_custom_options = FormatSizeOptions {
decimal_zeroes: 3,
..DECIMAL
};
println!("{}", format_size_i(1000, semi_custom_options));
}

View file

@ -1,13 +1,18 @@
extern crate humansize;
//Import the trait and the options module
use humansize::{format_size, BINARY, CONVENTIONAL, DECIMAL};
use humansize::{format_size, format_size_i, BINARY, WINDOWS, DECIMAL, Formatter, IFormatter};
fn main() {
// Call the file_size method on any non-negative integer with the option set you require
println!("{}", format_size(5456usize, BINARY));
println!("{}", format_size(1024usize, BINARY));
println!("{}", format_size(1000usize, DECIMAL));
println!("{}", format_size(1_023_654_123_654_u64, DECIMAL));
println!("{}", format_size(123456789usize, CONVENTIONAL));
println!("{}", format_size(1024usize, DECIMAL));
println!("{}", format_size(1000usize, WINDOWS));
println!("{}", format_size(1_023_654_123_654_u64, BINARY));
println!("{}", format_size(123456789usize, DECIMAL));
println!("{}", format_size_i(-123456789, WINDOWS));
println!("{}", Formatter::new(1234u32, BINARY));
println!("{}", IFormatter::new(1234, BINARY));
}

View file

@ -0,0 +1,7 @@
[package]
name = "impl-style"
version = "0.1.0"
edition = "2021"
[dependencies]
humansize = { path = "../..", features = ["impl_style"] }

View file

View file

@ -0,0 +1,9 @@
// This sub-crate exists to make sure that everything works well with the `impl-style` flag enabled
use humansize::{FormatSize, FormatSizeI, DECIMAL};
#[test]
fn test_impl_style() {
assert_eq!(1000u64.format_size(DECIMAL), "1 kB");
assert_eq!((-1000).format_size_i(DECIMAL), "-1 kB");
}

View file

@ -1,11 +0,0 @@
// This sub-crate exists to make sure that everything works well with the `no_alloc` flag enabled
use std::io::Write;
use humansize::{Formatter, DECIMAL};
#[test]
fn test() {
let mut result: [u8; 4] = [0, 0, 0, 0];
write!(&mut result[..], "{}", Formatter::new(1000usize, DECIMAL)).unwrap();
assert_eq!(core::str::from_utf8(&result).unwrap(), "1 kB");
}

View file

@ -0,0 +1,27 @@
// This sub-crate exists to make sure that everything works well with the `no_alloc` flag enabled
use core::fmt::Write;
use humansize::{Formatter, DECIMAL};
struct Buffer<const N: usize>([u8; N], usize);
impl<const N: usize> Write for Buffer<N> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let space_left = self.0.len() - self.1;
if space_left >= s.len() {
self.0[self.1..][..s.len()].copy_from_slice(s.as_bytes());
self.1 += s.len();
Ok(())
} else {
Err(core::fmt::Error)
}
}
}
#[test]
fn test() {
let mut result = Buffer([0u8; 4], 0);
write!(&mut result, "{}", Formatter::new(1000usize, DECIMAL)).unwrap();
assert_eq!(core::str::from_utf8(&result.0).unwrap(), "1 kB");
}

View file

@ -1,16 +1,13 @@
use alloc::string::String;
use crate::options::FormatSizeOptions;
use crate::numeric_traits::*;
use crate::options::FormatSizeOptions;
use crate::IFormatter;
pub fn format_size_i(input: impl ToF64, options: impl AsRef<FormatSizeOptions>) -> String {
format!(
"{}",
IFormatter {
value: input,
options
}
IFormatter::new(input, options)
)
}

95
src/formatters.rs Normal file
View file

@ -0,0 +1,95 @@
use libm::{fabs, modf};
use crate::{ToF64, FormatSizeOptions, Kilo, BaseUnit, Unsigned, scales, utils::f64_eq};
pub struct IFormatter<T: ToF64, O: AsRef<FormatSizeOptions>> {
value: T,
options: O,
}
impl<V: ToF64, O: AsRef<FormatSizeOptions>> IFormatter<V, O> {
pub fn new(value: V, options: O) -> Self {
IFormatter { value, options }
}
}
impl<T: ToF64, O: AsRef<FormatSizeOptions>> core::fmt::Display for IFormatter<T, O> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let opts = self.options.as_ref();
let divider = opts.kilo.value();
let mut size: f64 = self.value.to_f64();
let mut scale_idx = 0;
if let Some(val) = opts.fixed_at {
while scale_idx != val as usize {
size /= divider;
scale_idx += 1;
}
} else {
while fabs(size) >= divider {
size /= divider;
scale_idx += 1;
}
}
let mut scale = match (opts.units, opts.long_units, opts.base_unit) {
(Kilo::Decimal, false, BaseUnit::Byte) => scales::SCALE_DECIMAL[scale_idx],
(Kilo::Decimal, true, BaseUnit::Byte) => scales::SCALE_DECIMAL_LONG[scale_idx],
(Kilo::Binary, false, BaseUnit::Byte) => scales::SCALE_BINARY[scale_idx],
(Kilo::Binary, true, BaseUnit::Byte) => scales::SCALE_BINARY_LONG[scale_idx],
(Kilo::Decimal, false, BaseUnit::Bit) => scales::SCALE_DECIMAL_BIT[scale_idx],
(Kilo::Decimal, true, BaseUnit::Bit) => scales::SCALE_DECIMAL_BIT_LONG[scale_idx],
(Kilo::Binary, false, BaseUnit::Bit) => scales::SCALE_BINARY_BIT[scale_idx],
(Kilo::Binary, true, BaseUnit::Bit) => scales::SCALE_BINARY_BIT_LONG[scale_idx],
};
// Remove "s" from the scale if the size is 1.x
let (fpart, ipart) = modf(size);
if f64_eq(ipart, 1.0)
&& (opts.long_units || (opts.base_unit == BaseUnit::Bit && scale_idx == 0))
{
scale = &scale[0..scale.len() - 1];
}
let places = if f64_eq(fpart, 0.0) {
opts.decimal_zeroes
} else {
opts.decimal_places
};
let space = if opts.space_after_value { " " } else { "" };
write!(f, "{:.*}{}{}{}", places, size, space, scale, opts.suffix)
}
}
impl<'a, U: ToF64 + Unsigned + Copy, O: AsRef<FormatSizeOptions>> From<&'a Formatter<U, O>>
for IFormatter<U, &'a O>
{
fn from(source: &'a Formatter<U, O>) -> Self {
IFormatter {
value: source.value,
options: &source.options,
}
}
}
pub struct Formatter<T: ToF64 + Unsigned, O: AsRef<FormatSizeOptions>> {
value: T,
options: O,
}
impl<V: ToF64 + Unsigned, O: AsRef<FormatSizeOptions>> Formatter<V, O> {
pub fn new(value: V, options: O) -> Self {
Formatter { value, options }
}
}
impl<T: ToF64 + Unsigned + Copy, O: AsRef<FormatSizeOptions> + Copy> core::fmt::Display
for Formatter<T, O>
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", IFormatter::from(self))
}
}

23
src/impl_style.rs Normal file
View file

@ -0,0 +1,23 @@
use alloc::string::String;
use crate::{IFormatter, Formatter, FormatSizeOptions, ToF64, Unsigned, Signed};
pub trait FormatSize<T> {
fn format_size(&self, opts: FormatSizeOptions) -> String;
}
pub trait FormatSizeI<T> {
fn format_size_i(&self, opts: FormatSizeOptions) -> String;
}
impl<T: ToF64 + Unsigned + Copy> FormatSize<T> for T {
fn format_size(&self, opts: FormatSizeOptions) -> String {
format!("{}", Formatter::new(*self, opts))
}
}
impl<T: ToF64 + Signed + Copy> FormatSizeI<T> for T {
fn format_size_i(&self, opts: FormatSizeOptions) -> String {
format!("{}", IFormatter::new(*self, opts))
}
}

View file

@ -2,36 +2,114 @@
/*!
# **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:
## Features
Humansize is a humanization library for information size that is:
- Simple & convenient to use
- Customizable
- Supports byte or bit sizes
- `no-std`
- Optionally non-allocating
- Optionally accepts signed values
* Decimal (kilo = 1000, unit format is `kB`)
* Binary (kilo = 1024, unit format is `KiB`)
* Windows/Conventional (kilo = 1024, unit format is `kB`)
## How to use it...
## 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::format_size;
fn main() {
let size = 1000usize;
println!("Size is {}", format_size(size, humansize::DECIMAL));
println!("Size is {}", format_size(size, humansize::BINARY));
println!("Size is {}", format_size(size, humansize::CONVENTIONAL));
}
Add humansize as a dependency to your project's `cargo.toml`:
```toml
[dependencies]
...
humansize = "2.0.0"
```
If you wish to customize the way sizes are displayed, you may create your own custom `FormatSizeOptions` struct
and pass that to the method. See the `custom_options.rs` file in the example folder.
### ... to easily format a size:
1. Import the `format_size` function as well as your preferred set of defaults:
- `DECIMAL` (SI)
- `BINARY` (IEC)
- `WINDOWS` (IEC values but SI units)
2. Call `format_size` with an unsigned integer
```rust
use humansize::{format_size, DECIMAL};
let size = 1_000_000u64;
let res: String = format_size(size, DECIMAL);
assert_eq!(&res, "1 MB");
```
### ... to format many sizes:
To improve reusability, you can use `create_format`, which returns a formatter function akin to `format_size` but with the options argument curried so it doesn't need to be specified again:
```rust
use humansize::{make_format, DECIMAL};
let formatter = make_format(DECIMAL);
assert_eq!(formatter(1_000_000u64), "1 MB");
assert_eq!(formatter(1_000_000_000u64), "1 GB");
//...
```
### ... to avoid allocation:
Specify the `no_alloc` feature flag in your project's `cargo.toml`:
```toml
[dependencies]
...
humansize = { version = "2.0.0", features = ["no_alloc"] }
```
This excludes all allocating code from compilation. You may now use the library's internal `Formatter` struct, which implements `core::fmt::display` so that you can `write!` it to a custom buffer of your choice:
```rust
use humansize::{IFormatter, DECIMAL};
let formatter = IFormatter::new(1_000_000, DECIMAL);
assert_eq!(format!("{}", formatter), "1 MB");
```
### ... with the `impl` style API:
For stylistic reasons, you may prefer to use the impl-style API of earlier versions of the crate.
To do so, specify the `impl-style` feature flag in your project's `cargo.toml`:
```toml
[dependencies]
...
humansize = { version = "2.0.0", features = ["impl_style"] }
```
Enabling this feature makes two methods available:
- `format_size` on unsigned integers types
- `format_size_i` on signed integer types.
To use it, bring the FormatSize trait into scope and call its method on an integer type:
```ignore
use humansize::{FormatSize, FormatSizeI DECIMAL};
assert_eq!(1_000_000u64.format_size(DECIMAL), "1 MB");
assert_eq!((-1_000_000).format_size_i(DECIMAL), "-1 MB");
```
### ... to further customize the output:
Humansize exports three default option sets:
* `Decimal`: kilo = 1000, unit format is `XB`.
* `Binary`: kilo = 1024, unit format is `XiB`.
* `WINDOWS` (Windows): kilo = 1024, unit format is `XB`.
The formatting can be further customized by providing providing your own option set. See the documentation of the `FormatSizeOptions` struct to see all the addressable parameters, and [this example](examples/custom_options.rs) for its usage.
### ... to accept negative values:
The solutions presented above only accept unsigned integer types as input (`usize`, `8`, `u16`, `u32` and `u64`). If however accepting negative values is correct for your application, a signed alternative exists for each of them that will accept signed integer types, and format them accordingly if negative:
- `format_size` : `format_size_i`
- `create_format` : `create_format_i`
- `FormatSize` trait : `FormatSizeI` trait
- `SizeFormatter` : `ISizeFormatter`
```rust
use humansize::{format_size_i, make_format_i, DECIMAL};
assert_eq!(&format_size_i(-1_000_000, DECIMAL), "-1 MB");
let signed_formatter = make_format_i(DECIMAL);
assert_eq!(&signed_formatter(-1_000_000), "-1 MB");
```
*/
#[macro_use]
@ -39,110 +117,24 @@ and pass that to the method. See the `custom_options.rs` file in the example fol
extern crate alloc;
extern crate libm;
use core::f64;
use libm::{fabs, modf};
mod options;
pub use options::{FixedAt, FormatSizeOptions, Kilo, BINARY, CONVENTIONAL, DECIMAL};
pub use options::{BaseUnit, FixedAt, FormatSizeOptions, Kilo, BINARY, WINDOWS, DECIMAL};
mod scales;
mod numeric_traits;
use numeric_traits::{ToF64, Unsigned};
pub use numeric_traits::{ToF64, Unsigned, Signed};
mod utils;
mod scales;
#[cfg(not(feature = "no_alloc"))]
mod allocating;
#[cfg(not(feature = "no_alloc"))]
pub use allocating::*;
fn f64_eq(left: f64, right: f64) -> bool {
left == right || fabs(left - right) <= f64::EPSILON
}
#[cfg(feature = "impl_style")]
mod impl_style;
#[cfg(feature = "impl_style")]
pub use impl_style::{FormatSize, FormatSizeI};
pub struct IFormatter<T: ToF64, O: AsRef<FormatSizeOptions>> {
pub value: T,
pub options: O,
}
impl<V: ToF64, O: AsRef<FormatSizeOptions>> IFormatter<V, O> {
pub fn new(value: V, options: O) -> Self {
IFormatter { value, options }
}
}
impl<T: ToF64, O: AsRef<FormatSizeOptions>> core::fmt::Display for IFormatter<T, O> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let opts = self.options.as_ref();
let divider = opts.kilo.value();
let mut size: f64 = self.value.to_f64();
let mut scale_idx = 0;
match opts.fixed_at {
FixedAt::No => {
while fabs(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) => scales::SCALE_DECIMAL[scale_idx],
(Kilo::Decimal, true) => scales::SCALE_DECIMAL_LONG[scale_idx],
(Kilo::Binary, false) => scales::SCALE_BINARY[scale_idx],
(Kilo::Binary, true) => scales::SCALE_BINARY_LONG[scale_idx],
};
// Remove "s" from the scale if the size is 1.x
let (fpart, ipart) = modf(size);
if opts.long_units && f64_eq(ipart, 1.0) {
scale = &scale[0..scale.len() - 1];
}
let places = if f64_eq(fpart, 0.0) {
opts.decimal_zeroes
} else {
opts.decimal_places
};
let space = if opts.space { " " } else { "" };
write!(f, "{:.*}{}{}{}", places, size, space, scale, opts.suffix)
}
}
impl<'a, U: ToF64 + Unsigned + Copy, O: AsRef<FormatSizeOptions>> From<&'a Formatter<U, O>>
for IFormatter<U, &'a O>
{
fn from(source: &'a Formatter<U, O>) -> Self {
IFormatter {
value: source.value,
options: &source.options,
}
}
}
pub struct Formatter<T: ToF64 + Unsigned, O: AsRef<FormatSizeOptions>> {
value: T,
options: O,
}
impl<V: ToF64 + Unsigned, O: AsRef<FormatSizeOptions>> Formatter<V, O> {
pub fn new(value: V, options: O) -> Self {
Formatter { value, options }
}
}
impl<T: ToF64 + Unsigned + Copy, O: AsRef<FormatSizeOptions> + Copy> core::fmt::Display
for Formatter<T, O>
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", IFormatter::from(self))
}
}
mod formatters;
pub use formatters::{Formatter, IFormatter};

View file

@ -23,3 +23,14 @@ macro_rules! impl_unsigned {
}
impl_unsigned!(for usize u8 u16 u32 u64);
pub trait Signed {}
macro_rules! impl_unsigned {
(for $($t:ty)*) => ($(
impl Signed for $t {}
)*)
}
impl_unsigned!(for isize i8 i16 i32 i64);

View file

@ -1,38 +1,41 @@
use super::{FixedAt, FormatSizeOptions, Kilo};
use super::{BaseUnit, FormatSizeOptions, Kilo};
/// Options to display sizes in the SI format.
pub const BINARY: FormatSizeOptions = FormatSizeOptions {
base_unit: BaseUnit::Byte,
kilo: Kilo::Binary,
units: Kilo::Binary,
decimal_places: 2,
decimal_zeroes: 0,
fixed_at: FixedAt::No,
fixed_at: None,
long_units: false,
space: true,
space_after_value: true,
suffix: "",
};
/// Options to display sizes in the SI (decimal) format.
pub const DECIMAL: FormatSizeOptions = FormatSizeOptions {
base_unit: BaseUnit::Byte,
kilo: Kilo::Decimal,
units: Kilo::Decimal,
decimal_places: 2,
decimal_zeroes: 0,
fixed_at: FixedAt::No,
fixed_at: None,
long_units: false,
space: true,
space_after_value: true,
suffix: "",
};
/// 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: FormatSizeOptions = FormatSizeOptions {
/// Options to display sizes in the "WINDOWS" format.
/// Uses 1024 as the value of the `Kilo`, but displays decimal-style units (`kB`, not `KiB`).
pub const WINDOWS: FormatSizeOptions = FormatSizeOptions {
base_unit: BaseUnit::Byte,
kilo: Kilo::Binary,
units: Kilo::Decimal,
decimal_places: 2,
decimal_zeroes: 0,
fixed_at: FixedAt::No,
fixed_at: None,
long_units: false,
space: true,
space_after_value: true,
suffix: "",
};

View file

@ -1,15 +1,16 @@
//! Describes the struct that holds the options needed by the `file_size` method.
//! Describes the struct that holds the options needed by the formatting functions.
//! The three most common formats are provided as constants to be used easily
mod defaults;
pub use self::defaults::*;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
/// Holds the standard to use when displaying the size.
pub enum Kilo {
/// The decimal scale and units. SI standard.
#[default]
Decimal,
/// The binary scale and units
/// The binary scale and units.
Binary,
}
@ -25,7 +26,7 @@ impl Kilo {
#[derive(Debug, Copy, Clone)]
/// Forces a certain representation of the resulting file size.
pub enum FixedAt {
Byte,
Base,
Kilo,
Mega,
Giga,
@ -34,12 +35,22 @@ pub enum FixedAt {
Exa,
Zetta,
Yotta,
No,
}
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub enum BaseUnit {
Bit,
#[default]
Byte,
}
/// Holds the options for the `file_size` method.
#[derive(Debug, Clone, Copy)]
pub struct FormatSizeOptions {
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct FormatSizeOptionsBuilder {
/// Whether the value being formatted represents an amount of bits or bytes.
pub base_unit: BaseUnit,
/// The scale (binary/decimal) to divide against.
pub kilo: Kilo,
@ -53,18 +64,101 @@ pub struct FormatSizeOptions {
pub decimal_zeroes: usize,
/// Whether to force a certain representation and if so, which one.
pub fixed_at: FixedAt,
pub fixed_at: Option<FixedAt>,
/// Whether to use the full unit (e.g. `Kilobyte`) or its abbreviation (`kB`).
pub long_units: bool,
/// Whether to place a space between value and units.
pub space: bool,
pub space_after_value: bool,
/// An optional suffix which will be appended after the unit. Useful to represent speeds (e.g. `1 kB/s)
pub suffix: &'static str,
}
/// Holds the options for the `file_size` method.
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct FormatSizeOptions {
/// Whether the value being formatted represents an amount of bits or bytes.
pub base_unit: BaseUnit,
/// The scale (binary/decimal) to divide against.
pub kilo: 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: Option<FixedAt>,
/// Whether to use the full unit (e.g. `Kilobyte`) or its abbreviation (`kB`).
pub long_units: bool,
/// Whether to place a space between value and units.
pub space_after_value: bool,
/// An optional suffix which will be appended after the unit. Useful to represent speeds (e.g. `1 kB/s)
pub suffix: &'static str,
}
impl FormatSizeOptions {
pub fn from(from: FormatSizeOptions) -> FormatSizeOptions {
FormatSizeOptions { ..from }
}
pub fn base_unit(mut self, base_unit: BaseUnit) -> FormatSizeOptions {
self.base_unit = base_unit;
self
}
pub fn kilo(mut self, kilo: Kilo) -> FormatSizeOptions {
self.kilo = kilo;
self
}
pub fn units(mut self, units: Kilo) -> FormatSizeOptions {
self.units = units;
self
}
pub fn decimal_places(mut self, decimal_places: usize) -> FormatSizeOptions {
self.decimal_places = decimal_places;
self
}
pub fn decimal_zeroes(mut self, decimal_zeroes: usize) -> FormatSizeOptions {
self.decimal_zeroes = decimal_zeroes;
self
}
pub fn fixed_at(mut self, fixed_at: Option<FixedAt>) -> FormatSizeOptions {
self.fixed_at = fixed_at;
self
}
pub fn long_units(mut self, long_units: bool) -> FormatSizeOptions {
self.long_units = long_units;
self
}
pub fn space_after_value(mut self, insert_space: bool) -> FormatSizeOptions {
self.space_after_value = insert_space;
self
}
pub fn suffix(mut self, suffix: &'static str) -> FormatSizeOptions {
self.suffix = suffix;
self
}
}
impl AsRef<FormatSizeOptions> for FormatSizeOptions {
fn as_ref(&self) -> &FormatSizeOptions {
self

View file

@ -26,3 +26,28 @@ pub(crate) static SCALE_BINARY_LONG: [&str; 9] = [
"Zebibytes",
"Yobibytes",
];
pub(crate) static SCALE_DECIMAL_BIT: [&str; 9] = [
"bits", "kbit", "Mbit", "Gbit", "Tbit", "Pbit", "Ebit", "Zbit", "Ybit",
];
pub(crate) static SCALE_DECIMAL_BIT_LONG: [&str; 9] = [
"Bits",
"Kilobits",
"Megabits",
"Gigabits",
"Terabits",
"Petabits",
"Exabits",
"Zettabits",
"Yottabits",
];
pub(crate) static SCALE_BINARY_BIT: [&str; 9] = [
"bits", "Kibit", "Mibit", "Gibit", "Tibit", "Pibit", "Eibit", "Zibit", "Yibit",
];
pub(crate) static SCALE_BINARY_BIT_LONG: [&str; 9] = [
"bits", "Kibibits", "Mebibits", "Gibibits", "Tebibits", "Pebibits", "Exbibits", "Zebibits",
"Yobibits",
];

5
src/utils.rs Normal file
View file

@ -0,0 +1,5 @@
use libm::fabs;
pub(crate) fn f64_eq(left: f64, right: f64) -> bool {
left == right || fabs(left - right) <= f64::EPSILON
}

View file

@ -1,5 +1,5 @@
use humansize::{
format_size, format_size_i, FixedAt, FormatSizeOptions, BINARY, CONVENTIONAL, DECIMAL,
format_size, format_size_i, BaseUnit, FixedAt, FormatSizeOptions, BINARY, WINDOWS, DECIMAL,
};
#[test]
@ -11,82 +11,67 @@ fn test_sizes() {
assert_eq!(format_size(1023u32, BINARY), "1023 B");
assert_eq!(format_size(1023u32, DECIMAL), "1.02 kB");
assert_eq!(format_size(1024u32, BINARY), "1 KiB");
assert_eq!(format_size(1024u32, CONVENTIONAL), "1 kB");
assert_eq!(format_size(1024u32, WINDOWS), "1 kB");
let semi_custom_options = FormatSizeOptions {
space: false,
..DECIMAL
};
assert_eq!(format_size(1000u32, semi_custom_options), "1kB");
let custom_options = FormatSizeOptions::from(DECIMAL).space_after_value(false);
assert_eq!(format_size(1000u32, custom_options), "1kB");
let semi_custom_options2 = FormatSizeOptions {
suffix: "/s",
..BINARY
};
assert_eq!(format_size(999u32, semi_custom_options2), "999 B/s");
let custom_options = FormatSizeOptions::from(BINARY).suffix("/s");
assert_eq!(format_size(999u32, custom_options), "999 B/s");
let semi_custom_options3 = FormatSizeOptions {
suffix: "/day",
space: false,
..DECIMAL
};
assert_eq!(format_size(1000u32, semi_custom_options3), "1kB/day");
let custom_options = FormatSizeOptions::from(DECIMAL).suffix("/day").space_after_value(false);
assert_eq!(format_size(1000u32, custom_options), "1kB/day");
let semi_custom_options4 = FormatSizeOptions {
fixed_at: FixedAt::Byte,
..BINARY
};
assert_eq!(format_size(2048u32, semi_custom_options4), "2048 B");
let semi_custom_options5 = FormatSizeOptions {
fixed_at: FixedAt::Kilo,
..BINARY
};
let custom_options = FormatSizeOptions::from(BINARY).fixed_at(Some(FixedAt::Base));
assert_eq!(format_size(2048u32, custom_options), "2048 B");
let custom_options = FormatSizeOptions::from(BINARY).fixed_at(Some(FixedAt::Base)).long_units(true);
assert_eq!(format_size(2048u32, custom_options), "2048 Bytes");
let custom_options = FormatSizeOptions::from(BINARY).fixed_at(Some(FixedAt::Kilo));
assert_eq!(
format_size(16584975u32, semi_custom_options5),
format_size(16584975u32, custom_options),
"16196.26 KiB"
);
assert_eq!(
format_size_i(-16584975, semi_custom_options5),
format_size_i(-16584975, custom_options),
"-16196.26 KiB"
);
let semi_custom_options6 = FormatSizeOptions {
fixed_at: FixedAt::Tera,
decimal_places: 10,
..BINARY
};
let custom_options = FormatSizeOptions::from(BINARY).fixed_at(Some(FixedAt::Tera)).decimal_places(10);
assert_eq!(
format_size(15284975u32, semi_custom_options6),
format_size(15284975u32, custom_options),
"0.0000139016 TiB"
);
let semi_custom_options7 = FormatSizeOptions { ..DECIMAL };
assert_eq!((format_size_i(-5500, &semi_custom_options7)), "-5.50 kB");
assert_eq!((format_size(5500u32, &semi_custom_options7)), "5.50 kB");
assert_eq!((format_size_i(-5500, DECIMAL)), "-5.50 kB");
assert_eq!((format_size(5500u32, DECIMAL)), "5.50 kB");
let custom_options = FormatSizeOptions::from(DECIMAL).base_unit(BaseUnit::Bit);
assert_eq!((format_size(1usize, custom_options)), "1 bit");
assert_eq!((format_size(150usize, custom_options)), "150 bits");
assert_eq!((format_size(1000usize, custom_options)), "1 kbit");
}
#[test]
fn use_custom_option_struct_twice() {
let options = FormatSizeOptions {
long_units: true,
..DECIMAL
};
let options = FormatSizeOptions::from(DECIMAL).long_units(true);
assert_eq!(format_size(1500u32, &options), "1.50 Kilobyte",);
assert_eq!(format_size(2500u32, &options), "2.50 Kilobytes",);
assert_eq!(format_size_i(-2500000, &options), "-2.50 Megabytes",);
}
#[test]
fn pluralization_works() {
let options = FormatSizeOptions {
long_units: true,
decimal_zeroes: 2,
..DECIMAL
};
let options = FormatSizeOptions::from(DECIMAL).long_units(true).decimal_zeroes(2);
assert_eq!(format_size(1u32, &options), "1.00 Byte",);
@ -111,22 +96,13 @@ fn pluralization_works() {
#[test]
fn max_value_decimal() {
let options = FormatSizeOptions {
long_units: true,
decimal_places: 7,
..DECIMAL
};
assert_eq!(format_size(std::u64::MAX, &options), "18.4467441 Exabytes",);
let options = FormatSizeOptions::from(DECIMAL).decimal_places(7).long_units(true);
assert_eq!(format_size(core::u64::MAX, &options), "18.4467441 Exabytes",);
}
#[test]
fn max_value_binary() {
let options = FormatSizeOptions {
long_units: true,
decimal_places: 7,
..BINARY
};
let options = FormatSizeOptions::from(BINARY).decimal_places(7).long_units(true);
assert_eq!(format_size(std::u64::MAX, &options), "16 Exbibytes",);
assert_eq!(format_size(core::u64::MAX, &options), "16 Exbibytes",);
}