Skip to content

Commit

Permalink
Merge pull request #83 from wizard-28/port/filesize
Browse files Browse the repository at this point in the history
feat(port): `filesize`
  • Loading branch information
wizard-28 authored May 17, 2022
2 parents 46b4bec + 0f4b2b4 commit dd921fb
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Port `_export_format.py` (`export_format.rs`) in [#77](https://github.com/wizard-28/wealthy/pull/77) from [@wizard-28](https://github.com/wizard-28)
- Port `color_triplet.py` (`color_triplet.rs`) in [#78](https://github.com/wizard-28/wealthy/pull/78) from [@wizard-28](https://github.com/wizard-28)
- Port `region.py` (`region.rs`) in [#80](https://github.com/wizard-28/wealthy/pull/80) from [@wizard-28](https://github.com/wizard-28)
- Port `filesize.py` (`filesize.rs`) in [#83](https://github.com/wizard-28/wealthy/pull/83) from [@wizard-28](https://github.com/wizard-28)
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ categories = ["command-line-interface"]
[dependencies]
lazy_static = "1.4.0"
regex = "1.5.5"
separator = "0.4.1"

[dev-dependencies]
rstest = "0.13.0"
123 changes: 123 additions & 0 deletions src/filesize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#![allow(dead_code)]

use separator::Separatable;

/// Convert a filesize in to a string (powers of 1000, SI prefixes).
///
/// In this convention, `1000 B = 1 kB`.
///
/// This is typically the format used to advertise the storage
/// capacity of USB flash drives and the like (*256 MB* meaning
/// actually a storage capacity of more than *256 000 000 B*),
/// or used by **Mac OS X** since v10.6 to report file sizes.
///
/// # Arguments
///
/// * `size` - A file size.
/// * `precision` - The number of decimal places to include (default = 1).
/// * `separator` - The string to separate the value from the units (default = "
/// ").
///
/// # Returns
///
/// A string containing a abbreviated file size and units.
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
pub(crate) fn decimal(size: f32, precision: Option<usize>, separator: Option<&str>) -> String {
let base = 1000.0;

if (size - 1.0).abs() < f32::EPSILON {
return String::from("1 byte");
} else if size < base {
return format!("{} bytes", size.separated_string());
}

let suffixes = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
let mut unit = 0.0;
let mut evaluated_suffix = " ";
for (i, suffix) in suffixes.into_iter().enumerate() {
unit = base.powi(i as i32 + 2);
evaluated_suffix = suffix;
if size < unit {
break;
}
}
let precision = precision.unwrap_or(1);
let separator = separator.unwrap_or(" ");

let magnitude = format!("{:.precision$}", base as f32 * size as f32 / unit as f32)
.parse::<f32>()
.unwrap();

let mut magnitude_separated_string = magnitude.separated_string();
// HACK: Workaround for `separator` crate's float truncating behavior.
if magnitude.fract() == 0.0 && precision != 0 {
magnitude_separated_string.push_str(".0");
}

format!(
"{}{separator}{evaluated_suffix}",
magnitude_separated_string
)
}

/// Pick a unit and suffix for the given size.
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
fn pick_unit_and_suffix(size: u32, suffixes: &[&str], base: u32) -> (u32, String) {
let mut unit = 0_u32;
let mut evaluated_suffix = "";
for (i, suffix) in suffixes.iter().enumerate() {
unit = base.pow(i as u32);
evaluated_suffix = suffix;

if size < unit * base {
break;
}
}

(unit, String::from(evaluated_suffix))
}

#[cfg(test)]
mod tests {
use rstest::rstest;

use super::*;

#[rstest]
#[case(0.0, None, None, "0 bytes")]
#[case(1.0, None, None, "1 byte")]
#[case(2.0, None, None, "2 bytes")]
#[case(1000.0, None, None, "1.0 kB")]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_precision_loss)]
#[case(1.5 * (1000 * 1000) as f32, None, None, "1.5 MB")]
#[case(0.0, Some(2), None, "0 bytes")]
#[case(1111.0, Some(0), None, "1 kB")]
#[case(1111.0, Some(1), None, "1.1 kB")]
#[case(1111.0, Some(2), None, "1.11 kB")]
#[case(1111.0, None, Some(""), "1.1kB")]
fn test_decimal(
#[case] size: f32,
#[case] precision: Option<usize>,
#[case] separator: Option<&str>,
#[case] result: &str,
) {
assert_eq!(decimal(size, precision, separator), result);
}

#[rstest]
#[case(50, 1024, &(1, "bytes".to_owned()))]
#[case(2048, 1024, &(1024, "KB".to_owned()))]
fn test_pick_unit_and_suffix(
#[case] size: u32,
#[case] base: u32,
#[case] result: &(u32, String),
) {
let suffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
assert_eq!(pick_unit_and_suffix(size, &suffixes, base), *result);
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod cell_widths;
mod emoji_codes;
mod emoji_replace;
mod export_format;
mod filesize;
mod region;

pub mod color_triplet;

0 comments on commit dd921fb

Please sign in to comment.