Skip to content

Commit bb7d375

Browse files
committed
feat(df): add thousands separator support with locale-aware formatting
Adds thousands separator support to df command with locale-aware formatting: - Implements get_thousands_separator() with ICU library integration - Adds French locale support (narrow no-break space U+202F) - Adds English locale support (comma separator) - Fixes macOS POSIX locale detection - Ensures POSIX/C standard compliance for default behavior Changes include: - Core thousands separator implementation in uucore - df-specific integration in blocks.rs, df.rs, and table.rs
1 parent efa1aa7 commit bb7d375

File tree

12 files changed

+708
-37
lines changed

12 files changed

+708
-37
lines changed

.vscode/cspell.dictionaries/workspace.wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ getcwd
363363
# * other
364364
weblate
365365
algs
366+
largefile
367+
verylargefile
366368

367369
# translation tests
368370
CLICOLOR

Cargo.lock

Lines changed: 93 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ regex = "1.10.4"
362362
rstest = "0.26.0"
363363
rust-ini = "0.21.0"
364364
same-file = "1.0.6"
365+
serial_test = "3.1"
365366
self_cell = "1.0.4"
366367
# FIXME we use the exact version because the new 0.5.3 requires an MSRV of 1.88
367368
selinux = "=0.5.2"

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(),

0 commit comments

Comments
 (0)