mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-09-15 19:36:16 +00:00
Merge pull request #6226 from mvo5/du-h-precision
du: give `-h` output the same precision as GNU coreutils
This commit is contained in:
commit
7cee2c5a2b
9 changed files with 113 additions and 59 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3325,6 +3325,7 @@ dependencies = [
|
||||||
"md-5",
|
"md-5",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nix",
|
"nix",
|
||||||
|
"number_prefix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"os_display",
|
"os_display",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
|
|
@ -19,7 +19,7 @@ chrono = { workspace = true }
|
||||||
# For the --exclude & --exclude-from options
|
# For the --exclude & --exclude-from options
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
uucore = { workspace = true }
|
uucore = { workspace = true, features = ["format"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows-sys = { workspace = true, features = [
|
windows-sys = { workspace = true, features = [
|
||||||
|
|
|
@ -75,9 +75,6 @@ const ABOUT: &str = help_about!("du.md");
|
||||||
const AFTER_HELP: &str = help_section!("after help", "du.md");
|
const AFTER_HELP: &str = help_section!("after help", "du.md");
|
||||||
const USAGE: &str = help_usage!("du.md");
|
const USAGE: &str = help_usage!("du.md");
|
||||||
|
|
||||||
// TODO: Support Z & Y (currently limited by size of u64)
|
|
||||||
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
|
||||||
|
|
||||||
struct TraversalOptions {
|
struct TraversalOptions {
|
||||||
all: bool,
|
all: bool,
|
||||||
separate_dirs: bool,
|
separate_dirs: bool,
|
||||||
|
@ -117,7 +114,8 @@ enum Time {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum SizeFormat {
|
enum SizeFormat {
|
||||||
Human(u64),
|
HumanDecimal,
|
||||||
|
HumanBinary,
|
||||||
BlockSize(u64),
|
BlockSize(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,18 +547,14 @@ impl StatPrinter {
|
||||||
return size.to_string();
|
return size.to_string();
|
||||||
}
|
}
|
||||||
match self.size_format {
|
match self.size_format {
|
||||||
SizeFormat::Human(multiplier) => {
|
SizeFormat::HumanDecimal => uucore::format::human::human_readable(
|
||||||
if size == 0 {
|
size,
|
||||||
return "0".to_string();
|
uucore::format::human::SizeFormat::Decimal,
|
||||||
}
|
),
|
||||||
for &(unit, power) in &UNITS {
|
SizeFormat::HumanBinary => uucore::format::human::human_readable(
|
||||||
let limit = multiplier.pow(power);
|
size,
|
||||||
if size >= limit {
|
uucore::format::human::SizeFormat::Binary,
|
||||||
return format!("{:.1}{}", (size as f64) / (limit as f64), unit);
|
),
|
||||||
}
|
|
||||||
}
|
|
||||||
format!("{size}B")
|
|
||||||
}
|
|
||||||
SizeFormat::BlockSize(block_size) => div_ceil(size, block_size).to_string(),
|
SizeFormat::BlockSize(block_size) => div_ceil(size, block_size).to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -688,9 +682,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
});
|
});
|
||||||
|
|
||||||
let size_format = if matches.get_flag(options::HUMAN_READABLE) {
|
let size_format = if matches.get_flag(options::HUMAN_READABLE) {
|
||||||
SizeFormat::Human(1024)
|
SizeFormat::HumanBinary
|
||||||
} else if matches.get_flag(options::SI) {
|
} else if matches.get_flag(options::SI) {
|
||||||
SizeFormat::Human(1000)
|
SizeFormat::HumanDecimal
|
||||||
} else if matches.get_flag(options::BYTES) {
|
} else if matches.get_flag(options::BYTES) {
|
||||||
SizeFormat::BlockSize(1)
|
SizeFormat::BlockSize(1)
|
||||||
} else if matches.get_flag(options::BLOCK_SIZE_1K) {
|
} else if matches.get_flag(options::BLOCK_SIZE_1K) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ lscolors = { workspace = true }
|
||||||
uucore = { workspace = true, features = [
|
uucore = { workspace = true, features = [
|
||||||
"colors",
|
"colors",
|
||||||
"entries",
|
"entries",
|
||||||
|
"format",
|
||||||
"fs",
|
"fs",
|
||||||
"fsxattr",
|
"fsxattr",
|
||||||
"quoting-style",
|
"quoting-style",
|
||||||
|
|
|
@ -12,7 +12,6 @@ use clap::{
|
||||||
use glob::{MatchOptions, Pattern};
|
use glob::{MatchOptions, Pattern};
|
||||||
use lscolors::{LsColors, Style};
|
use lscolors::{LsColors, Style};
|
||||||
|
|
||||||
use number_prefix::NumberPrefix;
|
|
||||||
use std::{cell::OnceCell, num::IntErrorKind};
|
use std::{cell::OnceCell, num::IntErrorKind};
|
||||||
use std::{collections::HashSet, io::IsTerminal};
|
use std::{collections::HashSet, io::IsTerminal};
|
||||||
|
|
||||||
|
@ -37,6 +36,7 @@ use std::{
|
||||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use uucore::error::USimpleError;
|
use uucore::error::USimpleError;
|
||||||
|
use uucore::format::human::{human_readable, SizeFormat};
|
||||||
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
|
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
|
||||||
use uucore::fsxattr::has_acl;
|
use uucore::fsxattr::has_acl;
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
|
@ -313,13 +313,6 @@ enum Sort {
|
||||||
Width,
|
Width,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum SizeFormat {
|
|
||||||
Bytes,
|
|
||||||
Binary, // Powers of 1024, --human-readable, -h
|
|
||||||
Decimal, // Powers of 1000, --si
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
enum Files {
|
enum Files {
|
||||||
All,
|
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<f64>) -> 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)]
|
#[allow(dead_code)]
|
||||||
enum SizeOrDeviceId {
|
enum SizeOrDeviceId {
|
||||||
Size(String),
|
Size(String),
|
||||||
|
@ -3104,13 +3073,7 @@ fn display_len_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_size(size: u64, config: &Config) -> String {
|
fn display_size(size: u64, config: &Config) -> String {
|
||||||
// NOTE: The human-readable behavior deviates from the GNU ls.
|
human_readable(size, config.size_format)
|
||||||
// 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(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
|
@ -20,6 +20,7 @@ path = "src/lib/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
uucore_procs = { workspace = true }
|
uucore_procs = { workspace = true }
|
||||||
|
number_prefix = { workspace = true }
|
||||||
dns-lookup = { version = "2.0.4", optional = true }
|
dns-lookup = { version = "2.0.4", optional = true }
|
||||||
dunce = { version = "1.0.4", optional = true }
|
dunce = { version = "1.0.4", optional = true }
|
||||||
wild = "2.2"
|
wild = "2.2"
|
||||||
|
|
65
src/uucore/src/lib/features/format/human.rs
Normal file
65
src/uucore/src/lib/features/format/human.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// spell-checker:ignore gnulibs sfmt
|
||||||
|
|
||||||
|
//! `human`-size formatting
|
||||||
|
//!
|
||||||
|
//! 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<f64>) -> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
mod argument;
|
mod argument;
|
||||||
mod escape;
|
mod escape;
|
||||||
|
pub mod human;
|
||||||
pub mod num_format;
|
pub mod num_format;
|
||||||
pub mod num_parser;
|
pub mod num_parser;
|
||||||
mod spec;
|
mod spec;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (paths) atim sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir testfile
|
// spell-checker:ignore (paths) atim sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist fpath testdir testfile
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
@ -543,6 +543,34 @@ fn test_du_h_flag_empty_file() {
|
||||||
.stdout_only("0\tempty.txt\n");
|
.stdout_only("0\tempty.txt\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_du_h_precision() {
|
||||||
|
let test_cases = [
|
||||||
|
(133456345, "128M"),
|
||||||
|
(12 * 1024 * 1024, "12M"),
|
||||||
|
(8500, "8.4K"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(test_len, expected_output) in &test_cases {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let fpath = at.plus("test.txt");
|
||||||
|
std::fs::File::create(&fpath)
|
||||||
|
.expect("cannot create test file")
|
||||||
|
.set_len(test_len)
|
||||||
|
.expect("cannot truncate test len to size");
|
||||||
|
ucmd.arg("-h")
|
||||||
|
.arg("--apparent-size")
|
||||||
|
.arg(&fpath)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(format!(
|
||||||
|
"{}\t{}\n",
|
||||||
|
expected_output,
|
||||||
|
&fpath.to_string_lossy()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "touch")]
|
#[cfg(feature = "touch")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_time() {
|
fn test_du_time() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue