use std::fmt::{self, Display, Formatter};
mod suffix;
pub use suffix::Suffix;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FileSize(u64);
impl FileSize {
pub fn new(amount: f64, suffix: Suffix) -> Option<Self> {
let integer = (amount * (suffix as u64 as f64)) as u64;
Some(Self::from_bytes(integer))
}
pub const fn from_bytes(bytes: u64) -> Self {
Self(bytes)
}
pub const fn bytes(self) -> u64 {
self.0
}
pub const fn mul(self, rhs: u64) -> Self {
Self::from_bytes(self.bytes() * rhs)
}
pub fn fuzzy_matches(self, tomatch: Self, precision: u8) -> bool {
if self.bytes() < 10 {
return self == tomatch;
}
let bytes = self.bytes();
let offset = 10u64.pow(bytes.ilog10() - (precision as u32) - 1);
(bytes..bytes + offset).contains(&tomatch.bytes())
}
}
impl PartialEq<u64> for FileSize {
fn eq(&self, rhs: &u64) -> bool {
self.bytes() == *rhs
}
}
impl PartialOrd<u64> for FileSize {
fn partial_cmp(&self, rhs: &u64) -> Option<std::cmp::Ordering> {
self.bytes().partial_cmp(rhs)
}
}
impl Display for FileSize {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let is_byte = f.alternate();
let bytes = self.bytes();
let base = if is_byte { Suffix::KibiByte } else { Suffix::KiloByte } as u64;
let mut pow = 0;
while pow < Suffix::MAX_EXPONENT && base.pow(pow + 1) < bytes {
pow += 1;
}
let ratio = (bytes as f64) / (base.pow(pow) as f64);
let unit = Suffix::unit_for(pow, is_byte);
let binary = if is_byte { "i" } else { "" };
write!(
f,
"{ratio:.*}{unit}{binary}B",
std::cmp::min(f.precision().unwrap_or(0), 3 * (pow as usize))
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn foo() {
for x in 1..10_001 {
let _ = FileSize::new(x as f64, Suffix::KiloByte, None).unwrap().to_string();
}
assert_eq!("12kB", FileSize::new(12.0, Suffix::KiloByte, None).unwrap().to_string());
assert_eq!("1B", FileSize::new(1.0, Suffix::None, None).unwrap().to_string());
}
}