use crate::OPT_BLOCKSIZE;
use clap::ArgMatches;
use std::{env, fmt};
use uucore::{
display::Quotable,
parse_size::{parse_size, ParseSizeError},
};
const IEC_BASES: [u128; 10] = [
1,
1_024,
1_048_576,
1_073_741_824,
1_099_511_627_776,
1_125_899_906_842_624,
1_152_921_504_606_846_976,
1_180_591_620_717_411_303_424,
1_208_925_819_614_629_174_706_176,
1_237_940_039_285_380_274_899_124_224,
];
const SUFFIXES: [char; 9] = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const SI_BASES: [u128; 10] = [
1,
1_000,
1_000_000,
1_000_000_000,
1_000_000_000_000,
1_000_000_000_000_000,
1_000_000_000_000_000_000,
1_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000,
1_000_000_000_000_000_000_000_000_000,
];
const SI_SUFFIXES: [&str; 9] = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
fn to_magnitude_and_suffix_1024(n: u128) -> Result<String, ()> {
for i in 0..IEC_BASES.len() - 1 {
if n < IEC_BASES[i + 1] {
return Ok(format!("{}{}", n / IEC_BASES[i], SUFFIXES[i]));
}
}
Err(())
}
fn to_magnitude_and_suffix_not_powers_of_1024(n: u128) -> Result<String, ()> {
let mut i = 0;
while SI_BASES[i + 1] - SI_BASES[i] < n && i < SI_SUFFIXES.len() {
i += 1;
}
let quot = n / SI_BASES[i];
let rem = n % SI_BASES[i];
let suffix = SI_SUFFIXES[i];
if rem == 0 {
Ok(format!("{}{}", quot, suffix))
} else {
let tenths_place = rem / (SI_BASES[i] / 10);
if rem % (SI_BASES[i] / 10) == 0 {
Ok(format!("{}.{}{}", quot, tenths_place, suffix))
} else if tenths_place + 1 == 10 || quot >= 10 {
Ok(format!("{}{}", quot + 1, suffix))
} else {
Ok(format!("{}.{}{}", quot, tenths_place + 1, suffix))
}
}
}
fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> {
if n % 1024 == 0 && n % 1000 != 0 {
to_magnitude_and_suffix_1024(n)
} else {
to_magnitude_and_suffix_not_powers_of_1024(n)
}
}
pub(crate) enum SizeFormat {
HumanReadable(HumanReadable),
StaticBlockSize,
}
impl Default for SizeFormat {
fn default() -> Self {
Self::StaticBlockSize
}
}
#[derive(Clone, Copy)]
pub(crate) enum HumanReadable {
Decimal,
Binary,
}
#[derive(Debug, PartialEq)]
pub(crate) enum BlockSize {
Bytes(u64),
}
impl BlockSize {
pub(crate) fn as_u64(&self) -> u64 {
match *self {
Self::Bytes(n) => n,
}
}
}
impl Default for BlockSize {
fn default() -> Self {
if env::var("POSIXLY_CORRECT").is_ok() {
Self::Bytes(512)
} else {
Self::Bytes(1024)
}
}
}
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
if matches.is_present(OPT_BLOCKSIZE) {
let s = matches.value_of(OPT_BLOCKSIZE).unwrap();
let bytes = parse_size(s)?;
if bytes > 0 {
Ok(BlockSize::Bytes(bytes))
} else {
Err(ParseSizeError::ParseFailure(format!("{}", s.quote())))
}
} else {
Ok(Default::default())
}
}
impl fmt::Display for BlockSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Bytes(n) => match to_magnitude_and_suffix(*n as u128) {
Ok(s) => write!(f, "{}", s),
Err(_) => Err(fmt::Error),
},
}
}
}
#[cfg(test)]
mod tests {
use std::env;
use crate::blocks::{to_magnitude_and_suffix, BlockSize};
#[test]
fn test_to_magnitude_and_suffix_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1024).unwrap(), "1K");
assert_eq!(to_magnitude_and_suffix(2048).unwrap(), "2K");
assert_eq!(to_magnitude_and_suffix(4096).unwrap(), "4K");
assert_eq!(to_magnitude_and_suffix(1024 * 1024).unwrap(), "1M");
assert_eq!(to_magnitude_and_suffix(2 * 1024 * 1024).unwrap(), "2M");
assert_eq!(to_magnitude_and_suffix(1024 * 1024 * 1024).unwrap(), "1G");
assert_eq!(
to_magnitude_and_suffix(34 * 1024 * 1024 * 1024).unwrap(),
"34G"
);
}
#[test]
fn test_to_magnitude_and_suffix_not_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B");
assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B");
assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB");
assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB");
assert_eq!(to_magnitude_and_suffix(10_001).unwrap(), "11kB");
assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB");
assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB");
assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB");
assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB");
assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB");
assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB");
assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB");
assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB");
assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB");
assert_eq!(to_magnitude_and_suffix(1_000_000_001).unwrap(), "1.1GB");
}
#[test]
fn test_to_magnitude_and_suffix_multiples_of_1000_and_1024() {
assert_eq!(to_magnitude_and_suffix(128_000).unwrap(), "128kB");
assert_eq!(to_magnitude_and_suffix(1000 * 1024).unwrap(), "1.1MB");
assert_eq!(to_magnitude_and_suffix(1_000_000_000_000).unwrap(), "1TB");
}
#[test]
fn test_block_size_display() {
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
}
#[test]
fn test_default_block_size() {
assert_eq!(BlockSize::Bytes(1024), BlockSize::default());
env::set_var("POSIXLY_CORRECT", "1");
assert_eq!(BlockSize::Bytes(512), BlockSize::default());
env::remove_var("POSIXLY_CORRECT");
}
}