Skip to content

Commit ca4b994

Browse files
committed
feat(df): add thousands separator support with leading quote
Implement thousands separator support for df using GNU coreutils-compatible leading single quote syntax (--block-size='1K). Changes: - Add extract_thousands_separator_flag() to parse_size.rs - Add format_with_thousands_separator() to human.rs with locale support - Add BlockSizeConfig struct to df/blocks.rs to hold separator flag - Update df to use new block size configuration - Add 4 integration tests for thousands separator functionality - Fix clippy warnings (inlined format args, unnecessary qualifications) Fixes PR #9090
1 parent cda1f5e commit ca4b994

14 files changed

Lines changed: 311 additions & 70 deletions

File tree

pool/coreutils

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit f529c331232e514e652b9990a88c27810f56113a

src/uu/df/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ path = "src/df.rs"
1919

2020
[dependencies]
2121
clap = { workspace = true }
22-
uucore = { workspace = true, features = ["libc", "fsext", "parser-size", "fs"] }
22+
uucore = { workspace = true, features = [
23+
"libc",
24+
"fsext",
25+
"parser-size",
26+
"fs",
27+
"format",
28+
] }
2329
unicode-width = { workspace = true }
2430
thiserror = { workspace = true }
2531
fluent = { workspace = true }

src/uu/df/src/blocks.rs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use std::{env, fmt};
99

1010
use uucore::{
1111
display::Quotable,
12-
parser::parse_size::{ParseSizeError, parse_size_non_zero_u64, parse_size_u64},
12+
parser::parse_size::{
13+
ParseSizeError, extract_thousands_separator_flag, parse_size_non_zero_u64, parse_size_u64,
14+
},
1315
};
1416

1517
/// The first ten powers of 1024.
@@ -160,6 +162,13 @@ pub(crate) enum BlockSize {
160162
Bytes(u64),
161163
}
162164

165+
/// Configuration for block size display, including thousands separator flag.
166+
#[derive(Debug, PartialEq)]
167+
pub(crate) struct BlockSizeConfig {
168+
pub(crate) block_size: BlockSize,
169+
pub(crate) use_thousands_separator: bool,
170+
}
171+
163172
impl BlockSize {
164173
/// Returns the associated value
165174
pub(crate) fn as_u64(&self) -> u64 {
@@ -191,29 +200,47 @@ impl Default for BlockSize {
191200
}
192201
}
193202

194-
pub(crate) fn read_block_size(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
203+
pub(crate) fn read_block_size(matches: &ArgMatches) -> Result<BlockSizeConfig, ParseSizeError> {
195204
if matches.contains_id(OPT_BLOCKSIZE) {
196205
let s = matches.get_one::<String>(OPT_BLOCKSIZE).unwrap();
197-
let bytes = parse_size_u64(s)?;
206+
let (cleaned, use_thousands) = extract_thousands_separator_flag(s);
207+
let bytes = parse_size_u64(cleaned)?;
198208

199209
if bytes > 0 {
200-
Ok(BlockSize::Bytes(bytes))
210+
Ok(BlockSizeConfig {
211+
block_size: BlockSize::Bytes(bytes),
212+
use_thousands_separator: use_thousands,
213+
})
201214
} else {
202215
Err(ParseSizeError::ParseFailure(format!("{}", s.quote())))
203216
}
204217
} else if matches.get_flag(OPT_PORTABILITY) {
205-
Ok(BlockSize::default())
206-
} else if let Some(bytes) = block_size_from_env() {
207-
Ok(BlockSize::Bytes(bytes))
218+
Ok(BlockSizeConfig {
219+
block_size: BlockSize::default(),
220+
use_thousands_separator: false,
221+
})
222+
} else if let Some((bytes, use_thousands)) = block_size_from_env() {
223+
Ok(BlockSizeConfig {
224+
block_size: BlockSize::Bytes(bytes),
225+
use_thousands_separator: use_thousands,
226+
})
208227
} else {
209-
Ok(BlockSize::default())
228+
Ok(BlockSizeConfig {
229+
block_size: BlockSize::default(),
230+
use_thousands_separator: false,
231+
})
210232
}
211233
}
212234

213-
fn block_size_from_env() -> Option<u64> {
235+
fn block_size_from_env() -> Option<(u64, bool)> {
214236
for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
215237
if let Ok(env_size) = env::var(env_var) {
216-
return parse_size_non_zero_u64(&env_size).ok();
238+
let (cleaned, use_thousands) = extract_thousands_separator_flag(&env_size);
239+
if let Ok(size) = parse_size_non_zero_u64(cleaned) {
240+
return Some((size, use_thousands));
241+
}
242+
// If env var is set but invalid, return None (don't check other env vars)
243+
return None;
217244
}
218245
}
219246

src/uu/df/src/df.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use std::io::stdout;
2525
use std::path::Path;
2626
use thiserror::Error;
2727

28-
use crate::blocks::{BlockSize, read_block_size};
28+
use crate::blocks::{BlockSize, BlockSizeConfig, read_block_size};
2929
use crate::columns::{Column, ColumnError};
3030
use crate::filesystem::Filesystem;
3131
use crate::filesystem::FsError;
@@ -62,7 +62,7 @@ struct Options {
6262
show_local_fs: bool,
6363
show_all_fs: bool,
6464
human_readable: Option<HumanReadable>,
65-
block_size: BlockSize,
65+
block_size_config: BlockSizeConfig,
6666
header_mode: HeaderMode,
6767

6868
/// Optional list of filesystem types to include in the output table.
@@ -92,7 +92,10 @@ impl Default for Options {
9292
Self {
9393
show_local_fs: Default::default(),
9494
show_all_fs: Default::default(),
95-
block_size: BlockSize::default(),
95+
block_size_config: BlockSizeConfig {
96+
block_size: BlockSize::default(),
97+
use_thousands_separator: false,
98+
},
9699
human_readable: Option::default(),
97100
header_mode: HeaderMode::default(),
98101
include: Option::default(),
@@ -160,7 +163,7 @@ impl Options {
160163
show_local_fs: matches.get_flag(OPT_LOCAL),
161164
show_all_fs: matches.get_flag(OPT_ALL),
162165
sync: matches.get_flag(OPT_SYNC),
163-
block_size: read_block_size(matches).map_err(|e| match e {
166+
block_size_config: read_block_size(matches).map_err(|e| match e {
164167
ParseSizeError::InvalidSuffix(s) => OptionsError::InvalidSuffix(s),
165168
ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
166169
matches.get_one::<String>(OPT_BLOCKSIZE).unwrap().to_owned(),

src/uu/df/src/table.rs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
//! collection of data rows ([`Row`]), one per filesystem.
1010
use unicode_width::UnicodeWidthStr;
1111

12-
use crate::blocks::{SuffixType, to_magnitude_and_suffix};
12+
use crate::Options;
13+
use crate::blocks::{BlockSize, SuffixType, to_magnitude_and_suffix};
1314
use crate::columns::{Alignment, Column};
1415
use crate::filesystem::Filesystem;
15-
use crate::{BlockSize, Options};
16+
use uucore::format::human::format_with_thousands_separator;
1617
use uucore::fsext::{FsUsage, MountInfo};
1718
use uucore::translate;
1819

@@ -308,12 +309,14 @@ impl<'a> RowFormatter<'a> {
308309
let size = bytes_column.scaled;
309310
let s = if let Some(h) = self.options.human_readable {
310311
let size = if self.is_total_row {
311-
let BlockSize::Bytes(d) = self.options.block_size;
312+
let BlockSize::Bytes(d) = self.options.block_size_config.block_size;
312313
d * size
313314
} else {
314315
bytes_column.bytes
315316
};
316317
to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h), true)
318+
} else if self.options.block_size_config.use_thousands_separator {
319+
format_with_thousands_separator(size)
317320
} else {
318321
size.to_string()
319322
};
@@ -421,13 +424,13 @@ impl Header {
421424
HeaderMode::PosixPortability => {
422425
format!(
423426
"{}{}",
424-
options.block_size.as_u64(),
427+
options.block_size_config.block_size.as_u64(),
425428
translate!("df-blocks-suffix")
426429
)
427430
}
428431
_ => format!(
429432
"{}{}",
430-
options.block_size.to_header(),
433+
options.block_size_config.block_size.to_header(),
431434
translate!("df-blocks-suffix")
432435
),
433436
},
@@ -490,7 +493,7 @@ impl Table {
490493
// showing all filesystems, then print the data as a row in
491494
// the output table.
492495
if options.show_all_fs || filesystem.usage.blocks > 0 {
493-
let row = Row::from_filesystem(filesystem, &options.block_size);
496+
let row = Row::from_filesystem(filesystem, &options.block_size_config.block_size);
494497
let fmt = RowFormatter::new(&row, options, false);
495498
let values = fmt.get_cells();
496499
if options.show_total {
@@ -578,7 +581,7 @@ mod tests {
578581
use crate::blocks::HumanReadable;
579582
use crate::columns::Column;
580583
use crate::table::{BytesCell, Cell, Header, HeaderMode, Row, RowFormatter, Table};
581-
use crate::{BlockSize, Options};
584+
use crate::{BlockSize, BlockSizeConfig, Options};
582585

583586
fn init() {
584587
unsafe {
@@ -692,7 +695,10 @@ mod tests {
692695
fn test_header_with_block_size_1024() {
693696
init();
694697
let options = Options {
695-
block_size: BlockSize::Bytes(3 * 1024),
698+
block_size_config: BlockSizeConfig {
699+
block_size: BlockSize::Bytes(3 * 1024),
700+
use_thousands_separator: false,
701+
},
696702
..Default::default()
697703
};
698704
assert_eq!(
@@ -772,7 +778,10 @@ mod tests {
772778
fn test_row_formatter() {
773779
init();
774780
let options = Options {
775-
block_size: BlockSize::Bytes(1),
781+
block_size_config: BlockSizeConfig {
782+
block_size: BlockSize::Bytes(1),
783+
use_thousands_separator: false,
784+
},
776785
..Default::default()
777786
};
778787
let row = Row {
@@ -798,7 +807,10 @@ mod tests {
798807
init();
799808
let options = Options {
800809
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
801-
block_size: BlockSize::Bytes(1),
810+
block_size_config: BlockSizeConfig {
811+
block_size: BlockSize::Bytes(1),
812+
use_thousands_separator: false,
813+
},
802814
..Default::default()
803815
};
804816
let row = Row {
@@ -825,7 +837,10 @@ mod tests {
825837
init();
826838
let options = Options {
827839
columns: COLUMNS_WITH_INODES.to_vec(),
828-
block_size: BlockSize::Bytes(1),
840+
block_size_config: BlockSizeConfig {
841+
block_size: BlockSize::Bytes(1),
842+
use_thousands_separator: false,
843+
},
829844
..Default::default()
830845
};
831846
let row = Row {
@@ -851,7 +866,10 @@ mod tests {
851866
init();
852867
let options = Options {
853868
columns: vec![Column::Size, Column::Itotal],
854-
block_size: BlockSize::Bytes(100),
869+
block_size_config: BlockSizeConfig {
870+
block_size: BlockSize::Bytes(100),
871+
use_thousands_separator: false,
872+
},
855873
..Default::default()
856874
};
857875
let row = Row {
@@ -953,7 +971,10 @@ mod tests {
953971
init();
954972
fn get_formatted_values(bytes: u64, bytes_used: u64, bytes_avail: u64) -> Vec<Cell> {
955973
let options = Options {
956-
block_size: BlockSize::Bytes(1000),
974+
block_size_config: BlockSizeConfig {
975+
block_size: BlockSize::Bytes(1000),
976+
use_thousands_separator: false,
977+
},
957978
columns: vec![Column::Size, Column::Used, Column::Avail],
958979
..Default::default()
959980
};

src/uu/du/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ du-error-printing-thread-panicked = Printing thread panicked.
6969
du-error-invalid-suffix = invalid suffix in --{ $option } argument { $value }
7070
du-error-invalid-argument = invalid --{ $option } argument { $value }
7171
du-error-argument-too-large = --{ $option } argument { $value } too large
72+
du-error-builder-config = invalid configuration for { $option }: { $error }
7273
du-error-hyphen-file-name-not-allowed = when reading file names from standard input, no file name of '-' allowed
7374
7475
# Verbose/status messages

src/uu/du/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ du-error-printing-thread-panicked = Le thread d'affichage a paniqué.
6969
du-error-invalid-suffix = suffixe invalide dans l'argument --{ $option } { $value }
7070
du-error-invalid-argument = argument --{ $option } invalide { $value }
7171
du-error-argument-too-large = argument --{ $option } { $value } trop grand
72+
du-error-builder-config = configuration invalide pour { $option }: { $error }
7273
du-error-hyphen-file-name-not-allowed = le nom de fichier '-' n'est pas autorisé lors de la lecture de l'entrée standard
7374
7475
# Messages verbeux/de statut

src/uu/du/src/du.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1503,7 +1503,7 @@ fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String
15031503
translate!("du-error-argument-too-large", "option" => option, "value" => s.quote())
15041504
}
15051505
ParseSizeError::BuilderConfig(e) => {
1506-
format!("invalid configuration for {option}: {e}")
1506+
translate!("du-error-builder-config", "option" => option, "error" => e)
15071507
}
15081508
}
15091509
}

src/uu/od/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ od-error-overflow = Numerical result out of range
5959
od-error-invalid-suffix = invalid suffix in {$option} argument {$value}
6060
od-error-invalid-argument = invalid {$option} argument {$value}
6161
od-error-argument-too-large = {$option} argument {$value} too large
62+
od-error-builder-config = invalid configuration for {$option}: {$error}
6263
od-error-skip-past-end = tried to skip past end of input
6364
6465
# Help messages

src/uu/od/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ od-error-parse-failed = échec de l'analyse
5959
od-error-invalid-suffix = suffixe invalide dans l'argument {$option} {$value}
6060
od-error-invalid-argument = argument {$option} invalide {$value}
6161
od-error-argument-too-large = argument {$option} {$value} trop grand
62+
od-error-builder-config = configuration invalide pour {$option}: {$error}
6263
od-error-skip-past-end = tentative d'ignorer au-delà de la fin de l'entrée
6364
6465
# Messages d'aide

0 commit comments

Comments
 (0)