1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
//! This crate provides a mechanism to store numbers and display them
//! as a multiple of the chosen power of 1000 bytes or 1024 bytes,
//! e.g. in Megabytes or Mebibytes for human readability.
//!
//! Example:
//!
//! ```rust
//! let mut bb = BytesConfig::default();
//! let b = bb.bytes(5247 as u16);
//! println!("{}", b); // Prints "5.25 KB"
//! ```
//!
//! The number is stored internally in the same type as was provided to initialize
//! the struct: `u16` in this example.
//!
//! The `Bytes` structs are displayed using the preferences held in the `BytesConfig` struct
//! that created them:
//!
//! ```rust
//! bb.set_precision(1);
//! println!("{}", b); // Prints "5.3 KB"
//! ```
//!
//! See example for more details.
use std::cell::Cell;
use std::fmt::{Display, Formatter, Result};
use std::sync::{Arc, Mutex};
// Re-export contents of bytesbuilder module
mod config;
pub use config::*;
mod test;
/// Marker trait used to indicate to the compiler what types are allowed to initialize the struct.
pub trait Unsigned {}
impl Unsigned for u8 {}
impl Unsigned for u16 {}
impl Unsigned for u32 {}
impl Unsigned for u64 {}
// Arrays with units to display for each power of the base being used.
/// Array with the list of unit names to use in the power of 1000 system
pub const GABYTES: [&str; 7] = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
/// Array with the list of unit names to use in the power of 1024 system
pub const BIBYTES: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
/// The struct used to store the number of bytes.
/// Trait bounds specify what traits are required from the type used to store the number of bytes.
/// `Into<u64>` in particular is required as the number of bytes is temporarily converted to `u64`
/// (the only type that we are sure can store any number stored in another type without overflowing)
/// to compare it.
#[derive(Default, Clone)]
pub struct Bytes<U: Unsigned + Display + PartialOrd + Into<u64> + Copy> {
config: Arc<Mutex<Cell<BytesParam>>>,
bytes: U,
}
impl<U: Unsigned + Display + PartialOrd + Into<u64> + Copy> Bytes<U> {
/// Used by BytesConfig struct to instanciate Byte
fn new(config: Arc<Mutex<Cell<BytesParam>>>, byte: U) -> Self {
Self {
config,
bytes: byte,
}
}
/// Return a copy of the value in the type that was used to store it internally
pub fn value(self) -> U {
self.bytes.clone()
}
/// Return value in the type that was used to store it internally and consume Byte
pub fn into_inner(self) -> U {
self.bytes
}
// TODO Arithmetics
// TODO Override BytesConfig preferences
}
// implementation of the Display trait used by the `println!()` macro for instance.
impl<U> Display for Bytes<U>
where
U: Unsigned + Display + PartialOrd + Into<u64> + Copy,
{
fn fmt(&self, f: &mut Formatter) -> Result {
// Obtain base from associated BytesBuilder
let base: u64 = match (*self.config).lock().unwrap().get().base {
BytesBase::Gabyte => 1000,
BytesBase::Bibyte => 1024,
};
// Obtain number of decimals to display from associated BytesBuilder
let precision: usize = (*self.config).lock().unwrap().get().precision;
let prec_factor: f64 = 10.0_f64.powi(precision as i32);
// Obtain padding from associated BytesBuilder
let aligned: bool = (*self.config).lock().unwrap().get().aligned;
// Convert value into known type (u64 is the only one that can fit all others)
// because we can't do comparisons with generic types.
let b: u64 = self.bytes.into();
for e in 0_u32..7_u32 {
let divisor = base.pow(e);
// Divide value by upper limit of unit
let quotient = b / divisor;
let remainder = b % divisor;
let mut result: f64 = quotient as f64 + remainder as f64 / divisor as f64;
// Round result to the specified precision
result = (result * prec_factor).round() / prec_factor;
// If we are looking at bytes
if e == 0 {
// If result value is in the correct range
if result < base as f64 {
let unit = if base == 1000 {
GABYTES[e as usize]
} else {
BIBYTES[e as usize]
};
// Display value without decimal zeros (e.g. not "235.00 B" but "235 B")
if aligned {
let width = 4 + precision;
return write!(f, "{:width$} {}", result, unit);
} else {
return write!(f, "{:.0} {}", result as usize, unit);
}
}
} else {
// if result value is in the correct range
if result < base as f64 {
let unit = if base == 1000 {
GABYTES[e as usize]
} else {
BIBYTES[e as usize]
};
if aligned {
// Aligned to maximum possible width wich is width of "999." + precision
let width = 4 + precision;
return write!(f, "{:width$.precision$} {}", result, unit);
} else {
return write!(f, "{:.precision$} {}", result, unit);
}
}
}
}
// Could not format value (should never happen)
Err(std::fmt::Error)
}
}