From d07fb736307003799999fd14e2563edbf92cff88 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 1 Apr 2024 20:57:22 +0200 Subject: [PATCH] ls,uucore: extract display `human_readable()` helper from ls This commit extract the `display_size()` helper from `ls` into `uucore` as `human_readable` to be similar to the gnulib helper so that the human readable display of sizes can be shared between ls, du, df. --- Cargo.lock | 1 + src/uu/ls/src/ls.rs | 41 +------------- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features/format/human.rs | 63 +++++++++++++++++++++ src/uucore/src/lib/features/format/mod.rs | 1 + 5 files changed, 68 insertions(+), 39 deletions(-) create mode 100644 src/uucore/src/lib/features/format/human.rs diff --git a/Cargo.lock b/Cargo.lock index 705593af7..e5e479b48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3325,6 +3325,7 @@ dependencies = [ "md-5", "memchr", "nix", + "number_prefix", "once_cell", "os_display", "sha1", diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0221ae096..829ddb456 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -12,7 +12,6 @@ use clap::{ use glob::{MatchOptions, Pattern}; use lscolors::{LsColors, Style}; -use number_prefix::NumberPrefix; use std::{cell::OnceCell, num::IntErrorKind}; use std::{collections::HashSet, io::IsTerminal}; @@ -37,6 +36,7 @@ use std::{ use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; use uucore::error::USimpleError; +use uucore::format::human::{human_readable, SizeFormat}; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] use uucore::fsxattr::has_acl; #[cfg(any( @@ -313,13 +313,6 @@ enum Sort { Width, } -#[derive(PartialEq)] -enum SizeFormat { - Bytes, - Binary, // Powers of 1024, --human-readable, -h - Decimal, // Powers of 1000, --si -} - #[derive(PartialEq, Eq)] enum Files { All, @@ -3038,30 +3031,6 @@ fn display_date(metadata: &Metadata, config: &Config) -> String { } } -// There are a few peculiarities to how GNU formats the sizes: -// 1. One decimal place is given if and only if the size is smaller than 10 -// 2. It rounds sizes up. -// 3. The human-readable format uses powers for 1024, but does not display the "i" -// that is commonly used to denote Kibi, Mebi, etc. -// 4. Kibi and Kilo are denoted differently ("k" and "K", respectively) -fn format_prefixed(prefixed: &NumberPrefix) -> String { - match prefixed { - NumberPrefix::Standalone(bytes) => bytes.to_string(), - NumberPrefix::Prefixed(prefix, bytes) => { - // Remove the "i" from "Ki", "Mi", etc. if present - let prefix_str = prefix.symbol().trim_end_matches('i'); - - // Check whether we get more than 10 if we round up to the first decimal - // because we want do display 9.81 as "9.9", not as "10". - if (10.0 * bytes).ceil() >= 100.0 { - format!("{:.0}{}", bytes.ceil(), prefix_str) - } else { - format!("{:.1}{}", (10.0 * bytes).ceil() / 10.0, prefix_str) - } - } - } -} - #[allow(dead_code)] enum SizeOrDeviceId { Size(String), @@ -3104,13 +3073,7 @@ fn display_len_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId { } fn display_size(size: u64, config: &Config) -> String { - // NOTE: The human-readable behavior deviates from the GNU ls. - // The GNU ls uses binary prefixes by default. - match config.size_format { - SizeFormat::Binary => format_prefixed(&NumberPrefix::binary(size as f64)), - SizeFormat::Decimal => format_prefixed(&NumberPrefix::decimal(size as f64)), - SizeFormat::Bytes => size.to_string(), - } + human_readable(size, config.size_format) } #[cfg(unix)] diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 635ee1403..b13903bc6 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -20,6 +20,7 @@ path = "src/lib/lib.rs" [dependencies] clap = { workspace = true } uucore_procs = { workspace = true } +number_prefix = { workspace = true } dns-lookup = { version = "2.0.4", optional = true } dunce = { version = "1.0.4", optional = true } wild = "2.2" diff --git a/src/uucore/src/lib/features/format/human.rs b/src/uucore/src/lib/features/format/human.rs new file mode 100644 index 000000000..8767af90e --- /dev/null +++ b/src/uucore/src/lib/features/format/human.rs @@ -0,0 +1,63 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! `human`-size formating +//! +//! Format sizes like gnulibs human_readable() would + +use number_prefix::NumberPrefix; + +#[derive(Copy, Clone, PartialEq)] +pub enum SizeFormat { + Bytes, + Binary, // Powers of 1024, --human-readable, -h + Decimal, // Powers of 1000, --si +} + +// There are a few peculiarities to how GNU formats the sizes: +// 1. One decimal place is given if and only if the size is smaller than 10 +// 2. It rounds sizes up. +// 3. The human-readable format uses powers for 1024, but does not display the "i" +// that is commonly used to denote Kibi, Mebi, etc. +// 4. Kibi and Kilo are denoted differently ("k" and "K", respectively) +fn format_prefixed(prefixed: &NumberPrefix) -> String { + match prefixed { + NumberPrefix::Standalone(bytes) => bytes.to_string(), + NumberPrefix::Prefixed(prefix, bytes) => { + // Remove the "i" from "Ki", "Mi", etc. if present + let prefix_str = prefix.symbol().trim_end_matches('i'); + + // Check whether we get more than 10 if we round up to the first decimal + // because we want do display 9.81 as "9.9", not as "10". + if (10.0 * bytes).ceil() >= 100.0 { + format!("{:.0}{}", bytes.ceil(), prefix_str) + } else { + format!("{:.1}{}", (10.0 * bytes).ceil() / 10.0, prefix_str) + } + } + } +} + +pub fn human_readable(size: u64, sfmt: SizeFormat) -> String { + match sfmt { + SizeFormat::Binary => format_prefixed(&NumberPrefix::binary(size as f64)), + SizeFormat::Decimal => format_prefixed(&NumberPrefix::decimal(size as f64)), + SizeFormat::Bytes => size.to_string(), + } +} + +#[cfg(test)] +#[test] +fn test_human_readable() { + let test_cases = [ + (133456345, SizeFormat::Binary, "128M"), + (12 * 1024 * 1024, SizeFormat::Binary, "12M"), + (8500, SizeFormat::Binary, "8.4K"), + ]; + + for &(size, sfmt, expected_str) in &test_cases { + assert_eq!(human_readable(size, sfmt), expected_str); + } +} diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 8f662080d..b82b5f62a 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -32,6 +32,7 @@ mod argument; mod escape; +pub mod human; pub mod num_format; pub mod num_parser; mod spec;