From 5a8fad84430906bc05fc0f46f2afe9cbf1329499 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 17 May 2025 17:46:18 +0200 Subject: [PATCH 001/139] shred: implement and test feature --random-source --- src/uu/shred/src/shred.rs | 68 +++++++++++++++++++++++++++++-------- tests/by-util/test_shred.rs | 41 ++++++++++++++++++++++ 2 files changed, 95 insertions(+), 14 deletions(-) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 92cbb206155..a34ab41192f 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -10,7 +10,7 @@ use clap::{Arg, ArgAction, Command}; use libc::S_IWUSR; use rand::{Rng, SeedableRng, rngs::StdRng, seq::SliceRandom}; use std::fs::{self, File, OpenOptions}; -use std::io::{self, Seek, Write}; +use std::io::{self, Read, Seek, Write}; #[cfg(unix)] use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; @@ -34,6 +34,7 @@ pub mod options { pub const VERBOSE: &str = "verbose"; pub const EXACT: &str = "exact"; pub const ZERO: &str = "zero"; + pub const RANDOM_SOURCE: &str = "random-source"; pub mod remove { pub const UNLINK: &str = "unlink"; @@ -152,16 +153,25 @@ impl Iterator for FilenameIter { } } +enum RandomSource { + System, + Read(File), +} + /// Used to generate blocks of bytes of size <= BLOCK_SIZE based on either a give pattern /// or randomness // The lint warns about a large difference because StdRng is big, but the buffers are much // larger anyway, so it's fine. #[allow(clippy::large_enum_variant)] -enum BytesWriter { +enum BytesWriter<'a> { Random { rng: StdRng, buffer: [u8; BLOCK_SIZE], }, + RandomFile { + rng_file: &'a File, + buffer: [u8; BLOCK_SIZE], + }, // To write patterns we only write to the buffer once. To be able to do // this, we need to extend the buffer with 2 bytes. We can then easily // obtain a buffer starting with any character of the pattern that we @@ -177,12 +187,18 @@ enum BytesWriter { }, } -impl BytesWriter { - fn from_pass_type(pass: &PassType) -> Self { +impl<'a> BytesWriter<'a> { + fn from_pass_type(pass: &PassType, random_source: &'a RandomSource) -> Self { match pass { - PassType::Random => Self::Random { - rng: StdRng::from_os_rng(), - buffer: [0; BLOCK_SIZE], + PassType::Random => match random_source { + RandomSource::System => Self::Random { + rng: StdRng::from_os_rng(), + buffer: [0; BLOCK_SIZE], + }, + RandomSource::Read(file) => Self::RandomFile { + rng_file: file, + buffer: [0; BLOCK_SIZE], + }, }, PassType::Pattern(pattern) => { // Copy the pattern in chunks rather than simply one byte at a time @@ -203,17 +219,22 @@ impl BytesWriter { } } - fn bytes_for_pass(&mut self, size: usize) -> &[u8] { + fn bytes_for_pass(&mut self, size: usize) -> Result<&[u8], io::Error> { match self { Self::Random { rng, buffer } => { let bytes = &mut buffer[..size]; rng.fill(bytes); - bytes + Ok(bytes) + } + Self::RandomFile { rng_file, buffer } => { + let bytes = &mut buffer[..size]; + rng_file.read_exact(bytes)?; + Ok(bytes) } Self::Pattern { offset, buffer } => { let bytes = &buffer[*offset..size + *offset]; *offset = (*offset + size) % PATTERN_LENGTH; - bytes + Ok(bytes) } } } @@ -240,6 +261,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => unreachable!(), }; + let random_source = match matches.get_one::(options::RANDOM_SOURCE) { + Some(filepath) => RandomSource::Read(File::open(filepath).map_err(|_| { + USimpleError::new( + 1, + format!("cannot open random source: {}", filepath.quote()), + ) + })?), + None => RandomSource::System, + }; // TODO: implement --random-source let remove_method = if matches.get_flag(options::WIPESYNC) { @@ -275,6 +305,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { size, exact, zero, + &random_source, verbose, force, )); @@ -356,6 +387,13 @@ pub fn uu_app() -> Command { .help("add a final overwrite with zeros to hide shredding") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::RANDOM_SOURCE) + .long(options::RANDOM_SOURCE) + .help("take random bytes from FILE") + .value_hint(clap::ValueHint::FilePath) + .action(ArgAction::Set), + ) // Positional arguments .arg( Arg::new(options::FILE) @@ -395,6 +433,7 @@ fn wipe_file( size: Option, exact: bool, zero: bool, + random_source: &RandomSource, verbose: bool, force: bool, ) -> UResult<()> { @@ -501,7 +540,7 @@ fn wipe_file( // size is an optional argument for exactly how many bytes we want to shred // Ignore failed writes; just keep trying show_if_err!( - do_pass(&mut file, &pass_type, exact, size) + do_pass(&mut file, &pass_type, exact, random_source, size) .map_err_context(|| format!("{}: File write pass failed", path.maybe_quote())) ); } @@ -529,22 +568,23 @@ fn do_pass( file: &mut File, pass_type: &PassType, exact: bool, + random_source: &RandomSource, file_size: u64, ) -> Result<(), io::Error> { // We might be at the end of the file due to a previous iteration, so rewind. file.rewind()?; - let mut writer = BytesWriter::from_pass_type(pass_type); + let mut writer = BytesWriter::from_pass_type(pass_type, random_source); let (number_of_blocks, bytes_left) = split_on_blocks(file_size, exact); // We start by writing BLOCK_SIZE times as many time as possible. for _ in 0..number_of_blocks { - let block = writer.bytes_for_pass(BLOCK_SIZE); + let block = writer.bytes_for_pass(BLOCK_SIZE)?; file.write_all(block)?; } // Then we write remaining data which is smaller than the BLOCK_SIZE - let block = writer.bytes_for_pass(bytes_left as usize); + let block = writer.bytes_for_pass(bytes_left as usize)?; file.write_all(block)?; file.sync_data()?; diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 99d80c419a9..9c810ae64bb 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -251,3 +251,44 @@ fn test_all_patterns_present() { result.stderr_contains(pat); } } + +#[test] +fn test_random_source_regular_file() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + // Currently, our block size is 4096. If it changes, this test has to be adapted. + let mut many_bytes = Vec::with_capacity(4096 * 4); + for i in 0..4096u32 { + many_bytes.extend(i.to_le_bytes()); + } + assert_eq!(many_bytes.len(), 4096 * 4); + at.write_bytes("source_long", &many_bytes); + let file = "foo.txt"; + at.write(file, "a"); + scene + .ucmd() + .arg("-vn3") + .arg("--random-source=source_long") + .arg(file) + .succeeds() + .stderr_only("shred: foo.txt: pass 1/3 (random)...\nshred: foo.txt: pass 2/3 (random)...\nshred: foo.txt: pass 3/3 (random)...\n"); + // Should rewrite the file exactly three times + assert_eq!(at.read_bytes(file), many_bytes[(4096 * 2)..(4096 * 3)]); +} + +#[test] +#[ignore = "known issue #7947"] +fn test_random_source_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("source"); + let file = "foo.txt"; + at.write(file, "a"); + scene + .ucmd() + .arg("-v") + .arg("--random-source=source") + .arg(file) + .fails() + .stderr_only("shred: foo.txt: pass 1/3 (random)...\nshred: foo.txt: File write pass failed: Is a directory\n"); +} From 4555e6fe488572a5fc6474bc69e40c4a5aca8103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Sat, 24 May 2025 01:27:09 +0300 Subject: [PATCH 002/139] expr: Handle trailing backslash error --- src/uu/expr/src/expr.rs | 2 ++ src/uu/expr/src/syntax_tree.rs | 6 ++++++ tests/by-util/test_expr.rs | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 073bf501a0b..fa165f9f37f 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -50,6 +50,8 @@ pub enum ExprError { UnmatchedClosingBrace, #[error("Invalid content of \\{{\\}}")] InvalidBracketContent, + #[error("Trailing backslash")] + TrailingBackslash, } impl UError for ExprError { diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 106b4bd6830..11103ee4b9d 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -161,6 +161,7 @@ impl StringOp { match first { Some('^') => {} // Start of string anchor is already added Some('*') => re_string.push_str(r"\*"), + Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash), Some(char) => re_string.push(char), None => return Ok(0.into()), }; @@ -169,6 +170,8 @@ impl StringOp { let mut prev = first.unwrap_or_default(); let mut prev_is_escaped = false; while let Some(curr) = pattern_chars.next() { + let curr_is_escaped = prev == '\\' && !prev_is_escaped; + match curr { '^' => match (prev, prev_is_escaped) { // Start of a capturing group @@ -201,6 +204,9 @@ impl StringOp { re_string.push('$'); } } + '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { + return Err(ExprError::TrailingBackslash); + } _ => re_string.push(curr), } diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index c5fb96c3d54..e301c24705f 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -365,6 +365,26 @@ fn test_regex() { .stdout_only("0\n"); } +#[test] +fn test_regex_trailing_backslash() { + new_ucmd!() + .args(&["\\", ":", "\\\\"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["\\", ":", "\\"]) + .fails() + .stderr_only("expr: Trailing backslash\n"); + new_ucmd!() + .args(&["abc\\", ":", "abc\\\\"]) + .succeeds() + .stdout_only("4\n"); + new_ucmd!() + .args(&["abc\\", ":", "abc\\"]) + .fails() + .stderr_only("expr: Trailing backslash\n"); +} + #[test] fn test_substr() { new_ucmd!() From b0390fe36e5a7404f7ddb83fb1131974b61e835d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Sat, 24 May 2025 01:28:53 +0300 Subject: [PATCH 003/139] expr: Handle `$` at the beginning of the regex pattern --- src/uu/expr/src/syntax_tree.rs | 39 +++++++++++++++++++++------------- tests/by-util/test_expr.rs | 16 ++++++++++++++ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 11103ee4b9d..9b80d40c2e4 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -161,6 +161,7 @@ impl StringOp { match first { Some('^') => {} // Start of string anchor is already added Some('*') => re_string.push_str(r"\*"), + Some('$') if !is_end_of_expression(&pattern_chars) => re_string.push_str(r"\$"), Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash), Some(char) => re_string.push(char), None => return Ok(0.into()), @@ -185,23 +186,12 @@ impl StringOp { _ => re_string.push_str(r"\^"), }, '$' => { - if let Some('\\') = pattern_chars.peek() { - // The next character was checked to be a backslash - let backslash = pattern_chars.next().unwrap_or_default(); - match pattern_chars.peek() { - // End of a capturing group - Some(')') => re_string.push('$'), - // End of an alternative pattern - Some('|') => re_string.push('$'), - _ => re_string.push_str(r"\$"), - } - re_string.push(backslash); - } else if (prev_is_escaped || prev != '\\') - && pattern_chars.peek().is_some() - { + if is_end_of_expression(&pattern_chars) { + re_string.push(curr); + } else if !curr_is_escaped { re_string.push_str(r"\$"); } else { - re_string.push('$'); + re_string.push(curr); } } '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { @@ -247,6 +237,25 @@ impl StringOp { } } +/// Check if regex pattern character iterator is at the end of a regex expression or subexpression +fn is_end_of_expression(pattern_chars: &I) -> bool +where + I: Iterator + Clone, +{ + let mut pattern_chars_clone = pattern_chars.clone(); + match pattern_chars_clone.next() { + Some('\\') => { + match pattern_chars_clone.next() { + Some(')') // End of a capturing group + | Some('|') => true, // End of an alternative pattern + _ => false, + } + } + None => true, // No characters left + _ => false, + } +} + /// Check for errors in a supplied regular expression /// /// GNU coreutils shows messages for invalid regular expressions diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index e301c24705f..2eee6455586 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -318,6 +318,14 @@ fn test_regex() { .args(&["a$c", ":", "a$\\c"]) .succeeds() .stdout_only("3\n"); + new_ucmd!() + .args(&["$a", ":", "$a"]) + .succeeds() + .stdout_only("2\n"); + new_ucmd!() + .args(&["a", ":", "a$\\|b"]) + .succeeds() + .stdout_only("1\n"); new_ucmd!() .args(&["^^^^^^^^^", ":", "^^^"]) .succeeds() @@ -363,6 +371,14 @@ fn test_regex() { .args(&["abc", ":", "ab[^c]"]) .fails() .stdout_only("0\n"); + new_ucmd!() + .args(&["$", ":", "$"]) + .fails() + .stdout_only("0\n"); + new_ucmd!() + .args(&["a$", ":", "a$\\|b"]) + .fails() + .stdout_only("0\n"); } #[test] From 63ce37cf6e8318ecb4b678e719a086dfb5bc15b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Sat, 24 May 2025 11:14:32 +0300 Subject: [PATCH 004/139] expr: Refactor regex tests into multiple targeted functions --- tests/by-util/test_expr.rs | 129 +++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 62 deletions(-) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 2eee6455586..2c0eafe3232 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -273,35 +273,44 @@ fn test_length_mb() { } #[test] -fn test_regex() { - new_ucmd!() - .args(&["a^b", ":", "a^b"]) - .succeeds() - .stdout_only("3\n"); +fn test_regex_empty() { + new_ucmd!().args(&["", ":", ""]).fails().stdout_only("0\n"); new_ucmd!() - .args(&["a^b", ":", "a\\^b"]) - .succeeds() - .stdout_only("3\n"); + .args(&["abc", ":", ""]) + .fails() + .stdout_only("0\n"); +} + +#[test] +fn test_regex_trailing_backslash() { new_ucmd!() - .args(&["b", ":", "a\\|^b"]) + .args(&["\\", ":", "\\\\"]) .succeeds() .stdout_only("1\n"); new_ucmd!() - .args(&["ab", ":", "\\(^a\\)b"]) - .succeeds() - .stdout_only("a\n"); + .args(&["\\", ":", "\\"]) + .fails() + .stderr_only("expr: Trailing backslash\n"); new_ucmd!() - .args(&["a$b", ":", "a\\$b"]) + .args(&["abc\\", ":", "abc\\\\"]) .succeeds() - .stdout_only("3\n"); + .stdout_only("4\n"); new_ucmd!() - .args(&["a", ":", "a$\\|b"]) + .args(&["abc\\", ":", "abc\\"]) + .fails() + .stderr_only("expr: Trailing backslash\n"); +} + +#[test] +fn test_regex_caret() { + new_ucmd!() + .args(&["a^b", ":", "a^b"]) .succeeds() - .stdout_only("1\n"); + .stdout_only("3\n"); new_ucmd!() - .args(&["ab", ":", "a\\(b$\\)"]) + .args(&["a^b", ":", "a\\^b"]) .succeeds() - .stdout_only("b\n"); + .stdout_only("3\n"); new_ucmd!() .args(&["abc", ":", "^abc"]) .succeeds() @@ -311,21 +320,17 @@ fn test_regex() { .succeeds() .stdout_only("4\n"); new_ucmd!() - .args(&["b^$ic", ":", "b^\\$ic"]) - .succeeds() - .stdout_only("5\n"); - new_ucmd!() - .args(&["a$c", ":", "a$\\c"]) + .args(&["b", ":", "a\\|^b"]) .succeeds() - .stdout_only("3\n"); + .stdout_only("1\n"); new_ucmd!() - .args(&["$a", ":", "$a"]) + .args(&["ab", ":", "\\(^a\\)b"]) .succeeds() - .stdout_only("2\n"); + .stdout_only("a\n"); new_ucmd!() - .args(&["a", ":", "a$\\|b"]) - .succeeds() - .stdout_only("1\n"); + .args(&["^abc", ":", "^abc"]) + .fails() + .stdout_only("0\n"); new_ucmd!() .args(&["^^^^^^^^^", ":", "^^^"]) .succeeds() @@ -346,59 +351,59 @@ fn test_regex() { .args(&["\\a", ":", "\\\\[^^]"]) .succeeds() .stdout_only("2\n"); - new_ucmd!() - .args(&["^a", ":", "^^[^^]"]) - .succeeds() - .stdout_only("2\n"); - new_ucmd!() - .args(&["-5", ":", "-\\{0,1\\}[0-9]*$"]) - .succeeds() - .stdout_only("2\n"); - new_ucmd!().args(&["", ":", ""]).fails().stdout_only("0\n"); - new_ucmd!() - .args(&["abc", ":", ""]) - .fails() - .stdout_only("0\n"); + // Patterns are anchored to the beginning of the pattern "^bc" new_ucmd!() .args(&["abc", ":", "bc"]) .fails() .stdout_only("0\n"); new_ucmd!() - .args(&["^abc", ":", "^abc"]) - .fails() - .stdout_only("0\n"); + .args(&["^a", ":", "^^[^^]"]) + .succeeds() + .stdout_only("2\n"); new_ucmd!() .args(&["abc", ":", "ab[^c]"]) .fails() .stdout_only("0\n"); - new_ucmd!() - .args(&["$", ":", "$"]) - .fails() - .stdout_only("0\n"); - new_ucmd!() - .args(&["a$", ":", "a$\\|b"]) - .fails() - .stdout_only("0\n"); } #[test] -fn test_regex_trailing_backslash() { +fn test_regex_dollar() { new_ucmd!() - .args(&["\\", ":", "\\\\"]) + .args(&["a$b", ":", "a\\$b"]) + .succeeds() + .stdout_only("3\n"); + new_ucmd!() + .args(&["a", ":", "a$\\|b"]) .succeeds() .stdout_only("1\n"); new_ucmd!() - .args(&["\\", ":", "\\"]) - .fails() - .stderr_only("expr: Trailing backslash\n"); + .args(&["ab", ":", "a\\(b$\\)"]) + .succeeds() + .stdout_only("b\n"); new_ucmd!() - .args(&["abc\\", ":", "abc\\\\"]) + .args(&["a$c", ":", "a$\\c"]) .succeeds() - .stdout_only("4\n"); + .stdout_only("3\n"); new_ucmd!() - .args(&["abc\\", ":", "abc\\"]) + .args(&["$a", ":", "$a"]) + .succeeds() + .stdout_only("2\n"); + new_ucmd!() + .args(&["a", ":", "a$\\|b"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["-5", ":", "-\\{0,1\\}[0-9]*$"]) + .succeeds() + .stdout_only("2\n"); + new_ucmd!() + .args(&["$", ":", "$"]) .fails() - .stderr_only("expr: Trailing backslash\n"); + .stdout_only("0\n"); + new_ucmd!() + .args(&["a$", ":", "a$\\|b"]) + .fails() + .stdout_only("0\n"); } #[test] From a0cf06ea8c3b698bda57dfdb181274078a489cf0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 24 May 2025 07:43:10 +0200 Subject: [PATCH 005/139] Bump onig from 6.4.0 to 6.5.1 --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- fuzz/Cargo.lock | 22 ++++++++-------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63ec9c208c5..5a6fac1689f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1620,11 +1620,11 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "libc", "once_cell", "onig_sys", @@ -1632,9 +1632,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index a4c9d3200f2..9d1b732be34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -318,7 +318,7 @@ num-bigint = "0.4.4" num-prime = "0.4.4" num-traits = "0.2.19" number_prefix = "0.4" -onig = { version = "~6.4", default-features = false } +onig = { version = "~6.5.1", default-features = false } parse_datetime = "0.9.0" phf = "0.11.2" phf_codegen = "0.11.2" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 06faf0f5f98..c946c32255e 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -122,12 +122,6 @@ dependencies = [ "compare", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.1" @@ -660,7 +654,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -723,11 +717,11 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags", "libc", "once_cell", "onig_sys", @@ -735,9 +729,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -969,7 +963,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.1", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1632,7 +1626,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags", ] [[package]] From 2a862bc385a6ac4ec15465776b100c84115d7654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= <44954973+frendsick@users.noreply.github.com> Date: Sat, 24 May 2025 21:15:53 +0300 Subject: [PATCH 006/139] expr: Simplify checking of the end of an expression Co-authored-by: Daniel Hofstetter --- src/uu/expr/src/syntax_tree.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 9b80d40c2e4..544b3b7736d 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -244,13 +244,7 @@ where { let mut pattern_chars_clone = pattern_chars.clone(); match pattern_chars_clone.next() { - Some('\\') => { - match pattern_chars_clone.next() { - Some(')') // End of a capturing group - | Some('|') => true, // End of an alternative pattern - _ => false, - } - } + Some('\\') => matches!(pattern_chars_clone.next(), Some(')' | '|')), None => true, // No characters left _ => false, } From ab5cf741850fdd05a68212842341096a5f562a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Sat, 24 May 2025 21:17:20 +0300 Subject: [PATCH 007/139] expr: Simplify parsing special cases for `$` in regex --- src/uu/expr/src/syntax_tree.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 544b3b7736d..b0326f7b6b7 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -185,14 +185,8 @@ impl StringOp { | ('\\', false) => re_string.push(curr), _ => re_string.push_str(r"\^"), }, - '$' => { - if is_end_of_expression(&pattern_chars) { - re_string.push(curr); - } else if !curr_is_escaped { - re_string.push_str(r"\$"); - } else { - re_string.push(curr); - } + '$' if !curr_is_escaped && !is_end_of_expression(&pattern_chars) => { + re_string.push_str(r"\$"); } '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { return Err(ExprError::TrailingBackslash); From 796201dbfd07106f90dd52ddd2dddf79bf8702b4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 25 May 2025 09:23:57 +0200 Subject: [PATCH 008/139] publish.sh: also publish uutests --- util/publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/publish.sh b/util/publish.sh index 7207ba7fb91..84022a70ab9 100755 --- a/util/publish.sh +++ b/util/publish.sh @@ -54,7 +54,7 @@ TOTAL_ORDER=${TOTAL_ORDER#ROOT} CRATE_VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) set -e -for dir in src/uuhelp_parser/ src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/; do +for dir in src/uuhelp_parser/ src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/ tests/uutests/; do ( cd "$dir" CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2) From c6473e1bbd4145e6663c208f2a05070fd564373a Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 24 May 2025 10:15:22 +0200 Subject: [PATCH 009/139] Revert "CICD: Disable windows-latest/x86_64-pc-windows-gnu for now" This reverts commit deef8cbfd69856f9a631a085bc0cd032ea852767. A new onig release has happened, this should fix the issue. --- .github/workflows/CICD.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index fba140aa286..f225ba7557d 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -528,8 +528,7 @@ jobs: - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos, workspace-tests: true } # M1 CPU - { os: macos-13 , target: x86_64-apple-darwin , features: feat_os_macos, workspace-tests: true } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - # TODO: Re-enable after rust-onig release: https://github.com/rust-onig/rust-onig/issues/193 - # - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } + - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: aarch64-pc-windows-msvc , features: feat_os_windows, use-cross: use-cross , skip-tests: true } steps: From aa3947a1cc9b33911ca85d0f175044c349a31c17 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 25 May 2025 08:51:14 +0200 Subject: [PATCH 010/139] .github/workflows/CICD.yml: Do not update gcc The issue referenced has been long fixed, and for reasons not totally clear to me, blake3 fails after the GCC update (I tried to dig into this, but couldn't really figure out if this is really a problem with GCC 15, or with the version provided by MSYS, or some other side effect of the exact sequence in CI). Since blake3 CI doesn't do that gcc update (it uses the default gcc ~12 in the github windows image), let's _also_ not do that, and if there's a real problem with gcc 15+, that'll presumably fail their CI as well. Fixes #7977. --- .github/workflows/CICD.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index f225ba7557d..c9dcb2e3552 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -693,11 +693,6 @@ jobs: sudo apt-get -y update sudo apt-get -y install fuse3 libfuse-dev ;; - # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 - x86_64-pc-windows-gnu) - C:/msys64/usr/bin/pacman.exe -Sy --needed mingw-w64-x86_64-gcc --noconfirm - echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH - ;; esac case '${{ matrix.job.os }}' in macos-latest) brew install coreutils ;; # needed for testing From 2f577a7d987b816e9b08ddcb7cba41adb7cf6f5d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 25 May 2025 16:33:14 +0200 Subject: [PATCH 011/139] publish.sh: be more prescriptive on the grep (#7983) otherwise, we are getting version.workspace first --- util/publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/publish.sh b/util/publish.sh index 84022a70ab9..880e82e19a2 100755 --- a/util/publish.sh +++ b/util/publish.sh @@ -51,7 +51,7 @@ TOTAL_ORDER=$(echo -e $PARTIAL_ORDER | tsort | tac) # Remove the ROOT node from the start TOTAL_ORDER=${TOTAL_ORDER#ROOT} -CRATE_VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) +CRATE_VERSION=$(grep '^version =' Cargo.toml | head -n1 | cut -d '"' -f2) set -e for dir in src/uuhelp_parser/ src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/ tests/uutests/; do From 2129caedbef4e482f746949dc24da06508fb3e37 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Sun, 25 May 2025 17:57:18 +0200 Subject: [PATCH 012/139] flake.nix: Add cspell --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 79c69c4901e..9c99b7b72e1 100644 --- a/flake.nix +++ b/flake.nix @@ -1,4 +1,4 @@ -# spell-checker:ignore bintools gnum gperf ldflags libclang nixpkgs numtide pkgs texinfo +# spell-checker:ignore bintools gnum gperf ldflags libclang nixpkgs numtide pkgs texinfo gettext { inputs = { nixpkgs.url = "github:nixos/nixpkgs"; @@ -30,6 +30,7 @@ rustup pre-commit + nodePackages.cspell # debugging gdb From 12f075cd95407c6ebbcb72f73a6dc6858b860ebb Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 25 May 2025 21:23:04 +0200 Subject: [PATCH 013/139] Cargo.lock: Bump toml_edit to latest version If we want to update parse_datetime, we'll need winnow 0.7.10 (and not 0.7.3). So let's just bump here. Test: cargo build --config 'patch.crates-io.parse_datetime.path="../parse_datetime"' --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27ce051f179..b4ca255519f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2531,15 +2531,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "toml_datetime", @@ -4133,9 +4133,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] From 3f12ed993a02590033b53384388ab9677e4ef153 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 1 Apr 2025 19:38:09 +0200 Subject: [PATCH 014/139] doc: extensions: Explain how printf/seq handle precision There are some difference in behaviour vs GNU coreutils, explain what those are. --- docs/src/extensions.md | 78 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/docs/src/extensions.md b/docs/src/extensions.md index 1e715f729c1..af6119da51a 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -1,3 +1,5 @@ + + # Extensions over GNU Though the main goal of the project is compatibility, uutils supports a few @@ -71,8 +73,84 @@ feature is adopted from [FreeBSD](https://www.freebsd.org/cgi/man.cgi?cut). mail headers in the input. `-q`/`--quick` breaks lines more quickly. And `-T`/`--tab-width` defines the number of spaces representing a tab when determining the line length. +## `printf` + +`printf` uses arbitrary precision decimal numbers to parse and format floating point +numbers. GNU coreutils uses `long double`, whose actual size may be [double precision +64-bit float](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +(e.g 32-bit arm), [extended precision 80-bit float](https://en.wikipedia.org/wiki/Extended_precision) +(x86(-64)), or +[quadruple precision 128-bit float](https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format) (e.g. arm64). + +Practically, this means that printing a number with a large precision will stay exact: +``` +printf "%.48f\n" 0.1 +0.100000000000000000000000000000000000000000000000 << uutils on all platforms +0.100000000000000000001355252715606880542509316001 << GNU coreutils on x86(-64) +0.100000000000000000000000000000000004814824860968 << GNU coreutils on arm64 +0.100000000000000005551115123125782702118158340454 << GNU coreutils on armv7 (32-bit) +``` + +### Hexadecimal floats + +For hexadecimal float format (`%a`), POSIX only states that one hexadecimal number +should be present left of the decimal point (`0xh.hhhhp±d` [1]), but does not say how +many _bits_ should be included (between 1 and 4). On x86(-64), the first digit always +includes 4 bits, so its value is always between `0x8` and `0xf`, while on other +architectures, only 1 bit is included, so the value is always `0x1`. + +However, the first digit will of course be `0x0` if the number is zero. Also, +rounding numbers may cause the first digit to be `0x1` on x86(-64) (e.g. +`0xf.fffffffp-5` rounds to `0x1.00p-1`), or `0x2` on other architectures. + +We chose to replicate x86-64 behavior on all platforms. + +Additionally, the default precision of the hexadecimal float format (`%a` without +any specifier) is expected to be "sufficient for exact representation of the value" [1]. +This is not possible in uutils as we store arbitrary precision numbers that may be +periodic in hexadecimal form (`0.1 = 0xc.ccc...p-7`), so we revert +to the number of digits that would be required to exactly print an +[extended precision 80-bit float](https://en.wikipedia.org/wiki/Extended_precision), +emulating GNU coreutils behavior on x86(-64). An 80-bit float has 64 bits in its +integer and fractional part, so 16 hexadecimal digits are printed in total (1 digit +before the decimal point, 15 after). + +Practically, this means that the default hexadecimal floating point output is +identical to x86(-64) GNU coreutils: +``` +printf "%a\n" 0.1 +0xc.ccccccccccccccdp-7 << uutils on all platforms +0xc.ccccccccccccccdp-7 << GNU coreutils on x86-64 +0x1.999999999999999999999999999ap-4 << GNU coreutils on arm64 +0x1.999999999999ap-4 << GNU coreutils on armv7 (32-bit) +``` + +We _can_ print an arbitrary number of digits if a larger precision is requested, +and the leading digit will still be in the `0x8`-`0xf` range: +``` +printf "%.32a\n" 0.1 +0xc.cccccccccccccccccccccccccccccccdp-7 << uutils on all platforms +0xc.ccccccccccccccd00000000000000000p-7 << GNU coreutils on x86-64 +0x1.999999999999999999999999999a0000p-4 << GNU coreutils on arm64 +0x1.999999999999a0000000000000000000p-4 << GNU coreutils on armv7 (32-bit) +``` + +***Note: The architecture-specific behavior on non-x86(-64) platforms may change in +the future.*** + ## `seq` +Unlike GNU coreutils, `seq` always uses arbitrary precision decimal numbers, no +matter the parameters (integers, decimal numbers, positive or negative increments, +format specified, etc.), so its output will be more correct than GNU coreutils for +some inputs (e.g. small fractional increments where GNU coreutils uses `long double`). + +The only limitation is that the position of the decimal point is stored in a `i64`, +so values smaller than 10**(-2**63) will underflow to 0, and some values larger +than 10**(2**63) may overflow to infinity. + +See also comments under `printf` for formatting precision and differences. + `seq` provides `-t`/`--terminator` to set the terminator character. ## `ls` From 3b4226c48c8817b87b7ad84d7c4c3f6189031595 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Sun, 25 May 2025 23:36:36 +0200 Subject: [PATCH 015/139] sort: prevent -o/--output to be specified multiple times --- src/uu/sort/src/sort.rs | 15 ++++++++++++++- tests/by-util/test_sort.rs | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 19baead3045..2e44fba2955 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -156,6 +156,9 @@ pub enum SortError { #[error("{error}")] Uft8Error { error: Utf8Error }, + + #[error("multiple output files specified")] + MultipleOutputFiles, } impl UError for SortError { @@ -1034,6 +1037,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; + // Prevent -o/--output to be specified multiple times + if matches + .get_occurrences::(options::OUTPUT) + .is_some_and(|out| out.len() > 1) + { + return Err(SortError::MultipleOutputFiles.into()); + } + settings.debug = matches.get_flag(options::DEBUG); // check whether user specified a zero terminated list of files for input, otherwise read files from args @@ -1427,7 +1438,9 @@ pub fn uu_app() -> Command { .long(options::OUTPUT) .help("write output to FILENAME instead of stdout") .value_name("FILENAME") - .value_hint(clap::ValueHint::FilePath), + .value_hint(clap::ValueHint::FilePath) + // To detect multiple occurrences and raise an error + .action(ArgAction::Append), ) .arg( Arg::new(options::REVERSE) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index f827eafea07..988360ca696 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1345,3 +1345,12 @@ fn test_failed_write_is_reported() { .fails() .stderr_is("sort: write failed: 'standard output': No space left on device\n"); } + +#[test] +// Test for GNU tests/sort/sort.pl "o2" +fn test_multiple_output_files() { + new_ucmd!() + .args(&["-o", "foo", "-o", "bar"]) + .fails_with_code(2) + .stderr_is("sort: multiple output files specified\n"); +} From eb16277f74cd990774096c7ba06fac95e1383ea7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 26 May 2025 07:31:11 +0200 Subject: [PATCH 016/139] Bump type-map from 0.5.0 to 0.5.1 --- Cargo.lock | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb83f499159..fb9132aa20b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,7 +181,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash", "shlex", "syn", ] @@ -1025,7 +1025,7 @@ dependencies = [ "fluent-syntax", "intl-memoizer", "intl_pluralrules", - "rustc-hash 2.1.1", + "rustc-hash", "self_cell", "smallvec", "unic-langid", @@ -2124,12 +2124,6 @@ dependencies = [ "trim-in-place", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -2554,11 +2548,11 @@ checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" [[package]] name = "type-map" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash 1.1.0", + "rustc-hash", ] [[package]] From 12eb45281f0e9f393f83bd653b101b1027e3979d Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 26 May 2025 07:34:06 +0200 Subject: [PATCH 017/139] deny.toml: remove rustc-hash from skip list --- deny.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/deny.toml b/deny.toml index 0881a09f3dd..1ae92b14a72 100644 --- a/deny.toml +++ b/deny.toml @@ -84,8 +84,6 @@ skip = [ { name = "thiserror-impl", version = "1.0.69" }, # bindgen { name = "itertools", version = "0.13.0" }, - # fluent-bundle - { name = "rustc-hash", version = "1.1.0" }, # ordered-multimap { name = "hashbrown", version = "0.14.5" }, # cexpr (via bindgen) From a57bb7f084bbb260fb44312de4244558adec5a51 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 26 May 2025 09:22:21 +0200 Subject: [PATCH 018/139] ci: reduce timeout of freebsd jobs from 90 min to 45 min --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 6e96bfd9599..6ba684719ed 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -24,7 +24,7 @@ jobs: style: name: Style and Lint runs-on: ${{ matrix.job.os }} - timeout-minutes: 90 + timeout-minutes: 45 strategy: fail-fast: false matrix: @@ -117,7 +117,7 @@ jobs: test: name: Tests runs-on: ${{ matrix.job.os }} - timeout-minutes: 90 + timeout-minutes: 45 strategy: fail-fast: false matrix: From aae62072d8743d7c4edb02c8ae6547296c9f3d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Mon, 26 May 2025 09:40:42 +0300 Subject: [PATCH 019/139] expr: Fix parsing range quantifiers in regex --- src/uu/expr/src/syntax_tree.rs | 54 ++++++++++++++++++++++++++++++---- tests/by-util/test_expr.rs | 5 ---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index b0326f7b6b7..41952696314 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -191,10 +191,17 @@ impl StringOp { '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { return Err(ExprError::TrailingBackslash); } + '{' if curr_is_escaped && is_valid_range_quantifier(&pattern_chars) => { + re_string.push(curr); + // Set the lower bound of range quantifier to 0 if it is missing + if pattern_chars.peek() == Some(&',') { + re_string.push('0'); + } + } _ => re_string.push(curr), } - prev_is_escaped = prev == '\\' && !prev_is_escaped; + prev_is_escaped = curr_is_escaped; prev = curr; } @@ -244,6 +251,46 @@ where } } +/// Check if regex pattern character iterator is at the start of a valid range quantifier. +/// The iterator's start position is expected to be after the opening brace. +/// Range quantifier ends to closing brace. +/// +/// # Examples of valid range quantifiers +/// +/// - `r"\{3\}"` +/// - `r"\{3,\}"` +/// - `r"\{,6\}"` +/// - `r"\{3,6\}"` +/// - `r"\{,\}"` +fn is_valid_range_quantifier(pattern_chars: &I) -> bool +where + I: Iterator + Clone, +{ + // Parse the string between braces + let mut quantifier = String::new(); + let mut pattern_chars_clone = pattern_chars.clone().peekable(); + let Some(mut prev) = pattern_chars_clone.next() else { + return false; + }; + let mut prev_is_escaped = false; + while let Some(curr) = pattern_chars_clone.next() { + if prev == '\\' && curr == '}' && !prev_is_escaped { + break; + } + if pattern_chars_clone.peek().is_none() { + return false; + } + + quantifier.push(prev); + prev_is_escaped = prev == '\\' && !prev_is_escaped; + prev = curr; + } + + // Check if parsed quantifier is valid + let re = Regex::new(r"(\d+|\d*,\d*)").expect("valid regular expression"); + re.is_match(&quantifier) +} + /// Check for errors in a supplied regular expression /// /// GNU coreutils shows messages for invalid regular expressions @@ -287,10 +334,7 @@ fn check_posix_regex_errors(pattern: &str) -> ExprResult<()> { .expect("splitn always returns at least one string"), repetition.next(), ) { - ("", None) => { - // Empty repeating pattern - invalid_content_error = true; - } + ("", Some("")) => {} (x, None | Some("")) => { if x.parse::().is_err() { invalid_content_error = true; diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 2c0eafe3232..b573ea098ab 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -875,7 +875,6 @@ mod gnu_expr { .stdout_only("\n"); } - #[ignore] #[test] fn test_bre17() { new_ucmd!() @@ -884,7 +883,6 @@ mod gnu_expr { .stdout_only("{1}a\n"); } - #[ignore] #[test] fn test_bre18() { new_ucmd!() @@ -893,7 +891,6 @@ mod gnu_expr { .stdout_only("1\n"); } - #[ignore] #[test] fn test_bre19() { new_ucmd!() @@ -1105,7 +1102,6 @@ mod gnu_expr { .stderr_contains("Invalid content of \\{\\}"); } - #[ignore] #[test] fn test_bre45() { new_ucmd!() @@ -1114,7 +1110,6 @@ mod gnu_expr { .stdout_only("1\n"); } - #[ignore] #[test] fn test_bre46() { new_ucmd!() From 4ee93ed6d809c9aee177d3d8db7c04c5ab62fd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Mon, 26 May 2025 16:50:08 +0300 Subject: [PATCH 020/139] expr: Remove redundant escaping of '*' character --- src/uu/expr/src/syntax_tree.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 41952696314..1d3f74bb704 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -160,7 +160,6 @@ impl StringOp { let first = pattern_chars.next(); match first { Some('^') => {} // Start of string anchor is already added - Some('*') => re_string.push_str(r"\*"), Some('$') if !is_end_of_expression(&pattern_chars) => re_string.push_str(r"\$"), Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash), Some(char) => re_string.push(char), From 837bab24c1e42572cb7340d43ef1e073b6333d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Mon, 26 May 2025 17:00:35 +0300 Subject: [PATCH 021/139] expr: Add tests for `is_valid_range_quantifier` function --- src/uu/expr/src/syntax_tree.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 1d3f74bb704..a966fca499d 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -793,6 +793,7 @@ pub fn is_truthy(s: &NumOrStr) -> bool { mod test { use crate::ExprError; use crate::ExprError::InvalidBracketContent; + use crate::syntax_tree::is_valid_range_quantifier; use super::{ AstNode, AstNodeInner, BinOp, NumericOp, RelationOp, StringOp, check_posix_regex_errors, @@ -1041,4 +1042,22 @@ mod test { Err(InvalidBracketContent) ); } + + #[test] + fn test_is_valid_range_quantifier() { + assert!(is_valid_range_quantifier(&"3\\}".chars())); + assert!(is_valid_range_quantifier(&"3,\\}".chars())); + assert!(is_valid_range_quantifier(&",6\\}".chars())); + assert!(is_valid_range_quantifier(&"3,6\\}".chars())); + assert!(is_valid_range_quantifier(&",\\}".chars())); + assert!(is_valid_range_quantifier(&"3,6\\}anything".chars())); + assert!(!is_valid_range_quantifier(&"\\{3,6\\}".chars())); + assert!(!is_valid_range_quantifier(&"\\}".chars())); + assert!(!is_valid_range_quantifier(&"".chars())); + assert!(!is_valid_range_quantifier(&"3".chars())); + assert!(!is_valid_range_quantifier(&"3,".chars())); + assert!(!is_valid_range_quantifier(&",6".chars())); + assert!(!is_valid_range_quantifier(&"3,6".chars())); + assert!(!is_valid_range_quantifier(&",".chars())); + } } From 7789ef46a4d16e8d579c99fa22688c3e5a00f4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 01:51:56 +0300 Subject: [PATCH 022/139] expr: handle `\{` literally at the start of an expression Normally, `\{` begins a range quantifier like `{n,m}`, but at the start of an expression, there is no preceding item to apply the quantifier to. --- src/uu/expr/src/syntax_tree.rs | 161 +++++++++++++++++---------------- 1 file changed, 84 insertions(+), 77 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index a966fca499d..fa9d71a213a 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -169,37 +169,46 @@ impl StringOp { // Handle the rest of the input pattern. let mut prev = first.unwrap_or_default(); let mut prev_is_escaped = false; + let mut is_start_of_expression = first == Some('\\'); while let Some(curr) = pattern_chars.next() { let curr_is_escaped = prev == '\\' && !prev_is_escaped; match curr { - '^' => match (prev, prev_is_escaped) { - // Start of a capturing group - ('(', true) - // Start of an alternative pattern - | ('|', true) - // Character class negation "[^a]" - | ('[', false) - // Explicitly escaped caret - | ('\\', false) => re_string.push(curr), - _ => re_string.push_str(r"\^"), - }, + // Character class negation "[^a]" + // Explicitly escaped caret "\^" + '^' if !is_start_of_expression && !matches!(prev, '[' | '\\') => { + re_string.push_str(r"\^"); + } '$' if !curr_is_escaped && !is_end_of_expression(&pattern_chars) => { re_string.push_str(r"\$"); } '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { return Err(ExprError::TrailingBackslash); } - '{' if curr_is_escaped && is_valid_range_quantifier(&pattern_chars) => { - re_string.push(curr); - // Set the lower bound of range quantifier to 0 if it is missing - if pattern_chars.peek() == Some(&',') { - re_string.push('0'); + '{' if curr_is_escaped => { + // Handle '{' literally at the start of an expression + if is_start_of_expression { + if re_string.ends_with('\\') { + let _ = re_string.pop(); + } + re_string.push(curr); + } else if is_valid_range_quantifier(&pattern_chars) { + re_string.push(curr); + // Set the lower bound of range quantifier to 0 if it is missing + if pattern_chars.peek() == Some(&',') { + re_string.push('0'); + } + } else { + return Err(ExprError::InvalidBracketContent); } } _ => re_string.push(curr), } + // Capturing group "\(abc\)" + // Alternative pattern "a\|b" + is_start_of_expression = curr_is_escaped && matches!(curr, '(' | '|') + || curr == '\\' && prev_is_escaped && matches!(prev, '(' | '|'); prev_is_escaped = curr_is_escaped; prev = curr; } @@ -209,7 +218,14 @@ impl StringOp { RegexOptions::REGEX_OPTION_SINGLELINE, Syntax::grep(), ) - .map_err(|_| ExprError::InvalidRegexExpression)?; + .map_err(|error| match error.code() { + // "invalid repeat range {lower,upper}" + -123 => ExprError::InvalidBracketContent, + // "too big number for repeat range" + -201 => ExprError::InvalidBracketContent, + _ => ExprError::InvalidRegexExpression, + })?; + Ok(if re.captures_len() > 0 { re.captures(&left) .and_then(|captures| captures.at(1)) @@ -286,8 +302,28 @@ where } // Check if parsed quantifier is valid - let re = Regex::new(r"(\d+|\d*,\d*)").expect("valid regular expression"); - re.is_match(&quantifier) + let re = Regex::new(r"(\d*,\d*|\d+)").expect("valid regular expression"); + match re.captures(&quantifier) { + None => false, + Some(captures) => { + let matched = captures.at(0).unwrap_or_default(); + let mut repetition = matched.splitn(2, ','); + match ( + repetition + .next() + .expect("splitn always returns at least one string"), + repetition.next(), + ) { + ("", Some("")) => true, + (x, None | Some("")) => x.parse::().map_or(true, |x| x <= i16::MAX as i32), + ("", Some(x)) => x.parse::().map_or(true, |x| x <= i16::MAX as i32), + (f, Some(l)) => match (f.parse::(), l.parse::()) { + (Ok(f), Ok(l)) => f <= l && f <= i16::MAX as i32 && l <= i16::MAX as i32, + _ => false, + }, + } + } + } } /// Check for errors in a supplied regular expression @@ -306,77 +342,48 @@ where fn check_posix_regex_errors(pattern: &str) -> ExprResult<()> { let mut escaped_parens: u64 = 0; let mut escaped_braces: u64 = 0; - let mut escaped = false; + let mut prev = '\0'; + let mut prev_is_escaped = false; + let mut is_brace_ignored = false; + let mut is_start_of_expression = true; - let mut repeating_pattern_text = String::new(); - let mut invalid_content_error = false; + for curr in pattern.chars() { + let curr_is_escaped = prev == '\\' && !prev_is_escaped; - for c in pattern.chars() { - match (escaped, c) { + match (curr_is_escaped, curr) { + (true, '(') => escaped_parens += 1, (true, ')') => { escaped_parens = escaped_parens .checked_sub(1) .ok_or(ExprError::UnmatchedClosingParenthesis)?; } - (true, '(') => { - escaped_parens += 1; - } - (true, '}') => { - escaped_braces = escaped_braces - .checked_sub(1) - .ok_or(ExprError::UnmatchedClosingBrace)?; - let mut repetition = - repeating_pattern_text[..repeating_pattern_text.len() - 1].splitn(2, ','); - match ( - repetition - .next() - .expect("splitn always returns at least one string"), - repetition.next(), - ) { - ("", Some("")) => {} - (x, None | Some("")) => { - if x.parse::().is_err() { - invalid_content_error = true; - } - } - ("", Some(x)) => { - if x.parse::().is_err() { - invalid_content_error = true; - } - } - (f, Some(l)) => { - if let (Ok(f), Ok(l)) = (f.parse::(), l.parse::()) { - invalid_content_error = invalid_content_error || f > l; - } else { - invalid_content_error = true; - } - } - } - repeating_pattern_text.clear(); - } (true, '{') => { - escaped_braces += 1; - } - _ => { - if escaped_braces > 0 && repeating_pattern_text.len() <= 13 { - repeating_pattern_text.push(c); + is_brace_ignored = is_start_of_expression; + if !is_brace_ignored { + escaped_braces += 1; } - if escaped_braces > 0 && !(c.is_ascii_digit() || c == '\\' || c == ',') { - invalid_content_error = true; + } + (true, '}') => { + if !is_brace_ignored { + escaped_braces = escaped_braces + .saturating_sub(1) + .ok_or(ExprError::UnmatchedClosingBrace)?; } } + _ => {} } - escaped = !escaped && c == '\\'; + + is_start_of_expression = prev == '\0' + || curr_is_escaped && matches!(curr, '(' | '|') + || curr == '\\' && prev_is_escaped && matches!(prev, '(' | '|'); + prev_is_escaped = curr_is_escaped; + prev = curr; } - match ( - escaped_parens.is_zero(), - escaped_braces.is_zero(), - invalid_content_error, - ) { - (true, true, false) => Ok(()), - (_, false, _) => Err(ExprError::UnmatchedOpeningBrace), - (false, _, _) => Err(ExprError::UnmatchedOpeningParenthesis), - (true, true, true) => Err(ExprError::InvalidBracketContent), + + match (escaped_parens.is_zero(), escaped_braces.is_zero()) { + (true, true) => Ok(()), + (_, false) => Err(ExprError::UnmatchedOpeningBrace), + (false, _) => Err(ExprError::UnmatchedOpeningParenthesis), } } From bbc912eb75bd0fb724e447ca66ed0f315cbdde7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 04:00:09 +0300 Subject: [PATCH 023/139] expr: Add tests for regex range quantifiers --- tests/by-util/test_expr.rs | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index b573ea098ab..38028a54b0a 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -406,6 +406,74 @@ fn test_regex_dollar() { .stdout_only("0\n"); } +#[test] +fn test_regex_range_quantifier() { + new_ucmd!() + .args(&["a", ":", "a\\{1\\}"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["aaaaaaaaaa", ":", "a\\{1,\\}"]) + .succeeds() + .stdout_only("10\n"); + new_ucmd!() + .args(&["aaa", ":", "a\\{,3\\}"]) + .succeeds() + .stdout_only("3\n"); + new_ucmd!() + .args(&["aa", ":", "a\\{1,3\\}"]) + .succeeds() + .stdout_only("2\n"); + new_ucmd!() + .args(&["aaaa", ":", "a\\{,\\}"]) + .succeeds() + .stdout_only("4\n"); + new_ucmd!() + .args(&["a", ":", "ab\\{,3\\}"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["abbb", ":", "ab\\{,3\\}"]) + .succeeds() + .stdout_only("4\n"); + new_ucmd!() + .args(&["abcabc", ":", "\\(abc\\)\\{,\\}"]) + .succeeds() + .stdout_only("abc\n"); + new_ucmd!() + .args(&["a", ":", "a\\{,6\\}"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["{abc}", ":", "\\{abc\\}"]) + .succeeds() + .stdout_only("5\n"); + new_ucmd!() + .args(&["a{bc}", ":", "a\\(\\{bc\\}\\)"]) + .succeeds() + .stdout_only("{bc}\n"); + new_ucmd!() + .args(&["{b}", ":", "a\\|\\{b\\}"]) + .succeeds() + .stdout_only("3\n"); + new_ucmd!() + .args(&["{", ":", "a\\|\\{"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["{}}}", ":", "\\{\\}\\}\\}"]) + .succeeds() + .stdout_only("4\n"); + new_ucmd!() + .args(&["a{}}}", ":", "a\\{\\}\\}\\}"]) + .fails() + .stderr_only("expr: Invalid content of \\{\\}\n"); + new_ucmd!() + .args(&["ab", ":", "ab\\{\\}"]) + .fails() + .stderr_only("expr: Invalid content of \\{\\}\n"); +} + #[test] fn test_substr() { new_ucmd!() From 874a9304cf22255420415d0b08b2786328b173d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 04:02:01 +0300 Subject: [PATCH 024/139] expr: Remove nonexistent error `UnmatchedClosingBrace` The closing brace without related opening brace is handled literally --- src/uu/expr/src/expr.rs | 2 -- src/uu/expr/src/syntax_tree.rs | 10 +--------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index fa165f9f37f..70f4e62d2e0 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -46,8 +46,6 @@ pub enum ExprError { UnmatchedClosingParenthesis, #[error("Unmatched \\{{")] UnmatchedOpeningBrace, - #[error("Unmatched ) or \\}}")] - UnmatchedClosingBrace, #[error("Invalid content of \\{{\\}}")] InvalidBracketContent, #[error("Trailing backslash")] diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index fa9d71a213a..e315535f6e1 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -365,9 +365,7 @@ fn check_posix_regex_errors(pattern: &str) -> ExprResult<()> { } (true, '}') => { if !is_brace_ignored { - escaped_braces = escaped_braces - .saturating_sub(1) - .ok_or(ExprError::UnmatchedClosingBrace)?; + escaped_braces = escaped_braces.saturating_sub(1); } } _ => {} @@ -1007,12 +1005,6 @@ mod test { Err(ExprError::UnmatchedClosingParenthesis) ); - assert_eq!( - check_posix_regex_errors(r"abc\}"), - Err(ExprError::UnmatchedClosingBrace) - ); - } - #[test] fn check_regex_empty_repeating_pattern() { assert_eq!( From 30654824405d682cd177216ef651fb19d7322420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 04:19:57 +0300 Subject: [PATCH 025/139] expr: Anchor regex for detecting range quantifier --- src/uu/expr/src/syntax_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index e315535f6e1..83240e7f889 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -302,7 +302,7 @@ where } // Check if parsed quantifier is valid - let re = Regex::new(r"(\d*,\d*|\d+)").expect("valid regular expression"); + let re = Regex::new(r"^(\d*,\d*|\d+)").expect("valid regular expression"); match re.captures(&quantifier) { None => false, Some(captures) => { From 6b49b26af721c9e08cfb8f9d81b5a54fb0707f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 04:25:25 +0300 Subject: [PATCH 026/139] expr: Remove redundant tests that should not work anymore --- src/uu/expr/src/syntax_tree.rs | 37 ---------------------------------- 1 file changed, 37 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 83240e7f889..e53c0d1a78f 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -797,7 +797,6 @@ pub fn is_truthy(s: &NumOrStr) -> bool { #[cfg(test)] mod test { use crate::ExprError; - use crate::ExprError::InvalidBracketContent; use crate::syntax_tree::is_valid_range_quantifier; use super::{ @@ -1004,42 +1003,6 @@ mod test { check_posix_regex_errors(r"abc\)"), Err(ExprError::UnmatchedClosingParenthesis) ); - - #[test] - fn check_regex_empty_repeating_pattern() { - assert_eq!( - check_posix_regex_errors("ab\\{\\}"), - Err(InvalidBracketContent) - ); - } - - #[test] - fn check_regex_intervals_two_numbers() { - assert_eq!( - // out of order - check_posix_regex_errors("ab\\{1,0\\}"), - Err(InvalidBracketContent) - ); - assert_eq!( - check_posix_regex_errors("ab\\{1,a\\}"), - Err(InvalidBracketContent) - ); - assert_eq!( - check_posix_regex_errors("ab\\{a,3\\}"), - Err(InvalidBracketContent) - ); - assert_eq!( - check_posix_regex_errors("ab\\{a,b\\}"), - Err(InvalidBracketContent) - ); - assert_eq!( - check_posix_regex_errors("ab\\{a,\\}"), - Err(InvalidBracketContent) - ); - assert_eq!( - check_posix_regex_errors("ab\\{,b\\}"), - Err(InvalidBracketContent) - ); } #[test] From 639310c697fe78b988106ab7af5f331958321228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 04:25:54 +0300 Subject: [PATCH 027/139] expr: Fix testing `UnmatchedOpeningBrace` --- src/uu/expr/src/syntax_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index e53c0d1a78f..4f5a4c24113 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -992,7 +992,7 @@ mod test { ); assert_eq!( - check_posix_regex_errors(r"\{1,2"), + check_posix_regex_errors(r"a\{1,2"), Err(ExprError::UnmatchedOpeningBrace) ); } From b1a91351bc50e5c7b754afbfa8216c1ab1ebdea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 04:34:38 +0300 Subject: [PATCH 028/139] expr: Ignore test cases from spell checker --- tests/by-util/test_expr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 38028a54b0a..458ba9e846d 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -3,8 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore αbcdef ; (people) kkos -// spell-checker:ignore aabcccd aabcd aabd abbbd abbcabc abbcac abbcbbbd abbcbd -// spell-checker:ignore abbccd abcac acabc andand bigcmp bignum emptysub +// spell-checker:ignore aabcccd aabcd aabd abbb abbbd abbcabc abbcac abbcbbbd abbcbd +// spell-checker:ignore abbccd abcabc abcac acabc andand bigcmp bignum emptysub // spell-checker:ignore orempty oror use uutests::new_ucmd; From 6aeae43f3ccfa8111cb2fa63e4145207db2a5e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 13:08:22 +0300 Subject: [PATCH 029/139] expr: Simplify verifying indexes within regex range quantifier --- src/uu/expr/src/syntax_tree.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 4f5a4c24113..c1abfb9a10c 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -302,27 +302,19 @@ where } // Check if parsed quantifier is valid + let is_valid_range_index = |s: &str| s.parse::().map_or(true, |x| x <= i16::MAX as i32); let re = Regex::new(r"^(\d*,\d*|\d+)").expect("valid regular expression"); - match re.captures(&quantifier) { - None => false, - Some(captures) => { - let matched = captures.at(0).unwrap_or_default(); - let mut repetition = matched.splitn(2, ','); - match ( - repetition - .next() - .expect("splitn always returns at least one string"), - repetition.next(), - ) { - ("", Some("")) => true, - (x, None | Some("")) => x.parse::().map_or(true, |x| x <= i16::MAX as i32), - ("", Some(x)) => x.parse::().map_or(true, |x| x <= i16::MAX as i32), - (f, Some(l)) => match (f.parse::(), l.parse::()) { - (Ok(f), Ok(l)) => f <= l && f <= i16::MAX as i32 && l <= i16::MAX as i32, - _ => false, - }, - } + if let Some(captures) = re.captures(&quantifier) { + let matched = captures.at(0).unwrap_or_default(); + match matched.split_once(',') { + None => is_valid_range_index(matched), + Some(("", "")) => true, + Some((x, "")) => is_valid_range_index(x), + Some(("", x)) => is_valid_range_index(x), + Some((f, l)) => f <= l && is_valid_range_index(f) && is_valid_range_index(l), } + } else { + false } } From 07caa4867bedbb3fa91fef3335e16ae11b60a53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 14:02:24 +0300 Subject: [PATCH 030/139] expr: Fix error message for too big range quantifier index --- src/uu/expr/src/expr.rs | 2 + src/uu/expr/src/syntax_tree.rs | 104 ++++++++++++++++++++++++--------- tests/by-util/test_expr.rs | 2 +- 3 files changed, 78 insertions(+), 30 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 70f4e62d2e0..6a4f9487bb0 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -50,6 +50,8 @@ pub enum ExprError { InvalidBracketContent, #[error("Trailing backslash")] TrailingBackslash, + #[error("Regular expression too big")] + TooBigRangeQuantifierIndex, } impl UError for ExprError { diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index c1abfb9a10c..64386f9201f 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -192,14 +192,15 @@ impl StringOp { let _ = re_string.pop(); } re_string.push(curr); - } else if is_valid_range_quantifier(&pattern_chars) { + } else { + // Check if the following section is a valid range quantifier + verify_range_quantifier(&pattern_chars)?; + re_string.push(curr); // Set the lower bound of range quantifier to 0 if it is missing if pattern_chars.peek() == Some(&',') { re_string.push('0'); } - } else { - return Err(ExprError::InvalidBracketContent); } } _ => re_string.push(curr), @@ -222,7 +223,7 @@ impl StringOp { // "invalid repeat range {lower,upper}" -123 => ExprError::InvalidBracketContent, // "too big number for repeat range" - -201 => ExprError::InvalidBracketContent, + -201 => ExprError::TooBigRangeQuantifierIndex, _ => ExprError::InvalidRegexExpression, })?; @@ -277,7 +278,7 @@ where /// - `r"\{,6\}"` /// - `r"\{3,6\}"` /// - `r"\{,\}"` -fn is_valid_range_quantifier(pattern_chars: &I) -> bool +fn verify_range_quantifier(pattern_chars: &I) -> Result<(), ExprError> where I: Iterator + Clone, { @@ -285,15 +286,19 @@ where let mut quantifier = String::new(); let mut pattern_chars_clone = pattern_chars.clone().peekable(); let Some(mut prev) = pattern_chars_clone.next() else { - return false; + return Err(ExprError::UnmatchedOpeningBrace); }; + if pattern_chars_clone.peek().is_none() { + return Err(ExprError::UnmatchedOpeningBrace); + } + let mut prev_is_escaped = false; while let Some(curr) = pattern_chars_clone.next() { if prev == '\\' && curr == '}' && !prev_is_escaped { break; } if pattern_chars_clone.peek().is_none() { - return false; + return Err(ExprError::UnmatchedOpeningBrace); } quantifier.push(prev); @@ -302,19 +307,32 @@ where } // Check if parsed quantifier is valid - let is_valid_range_index = |s: &str| s.parse::().map_or(true, |x| x <= i16::MAX as i32); let re = Regex::new(r"^(\d*,\d*|\d+)").expect("valid regular expression"); if let Some(captures) = re.captures(&quantifier) { let matched = captures.at(0).unwrap_or_default(); match matched.split_once(',') { - None => is_valid_range_index(matched), - Some(("", "")) => true, - Some((x, "")) => is_valid_range_index(x), - Some(("", x)) => is_valid_range_index(x), - Some((f, l)) => f <= l && is_valid_range_index(f) && is_valid_range_index(l), + Some(("", "")) => Ok(()), + Some((x, "")) | Some(("", x)) => match x.parse::() { + Ok(x) if x <= i16::MAX.into() => Ok(()), + Ok(_) => Err(ExprError::TooBigRangeQuantifierIndex), + Err(_) => Err(ExprError::InvalidBracketContent), + }, + Some((f, l)) => match (f.parse::(), l.parse::()) { + (Ok(f), Ok(l)) if f > l => Err(ExprError::InvalidBracketContent), + (Ok(f), Ok(l)) if f > i16::MAX.into() || l > i16::MAX.into() => { + Err(ExprError::TooBigRangeQuantifierIndex) + } + (Ok(_), Ok(_)) => Ok(()), + _ => Err(ExprError::InvalidBracketContent), + }, + None => match matched.parse::() { + Ok(x) if x <= i16::MAX.into() => Ok(()), + Ok(_) => Err(ExprError::TooBigRangeQuantifierIndex), + Err(_) => Err(ExprError::InvalidBracketContent), + }, } } else { - false + Err(ExprError::InvalidBracketContent) } } @@ -789,7 +807,7 @@ pub fn is_truthy(s: &NumOrStr) -> bool { #[cfg(test)] mod test { use crate::ExprError; - use crate::syntax_tree::is_valid_range_quantifier; + use crate::syntax_tree::verify_range_quantifier; use super::{ AstNode, AstNodeInner, BinOp, NumericOp, RelationOp, StringOp, check_posix_regex_errors, @@ -999,19 +1017,47 @@ mod test { #[test] fn test_is_valid_range_quantifier() { - assert!(is_valid_range_quantifier(&"3\\}".chars())); - assert!(is_valid_range_quantifier(&"3,\\}".chars())); - assert!(is_valid_range_quantifier(&",6\\}".chars())); - assert!(is_valid_range_quantifier(&"3,6\\}".chars())); - assert!(is_valid_range_quantifier(&",\\}".chars())); - assert!(is_valid_range_quantifier(&"3,6\\}anything".chars())); - assert!(!is_valid_range_quantifier(&"\\{3,6\\}".chars())); - assert!(!is_valid_range_quantifier(&"\\}".chars())); - assert!(!is_valid_range_quantifier(&"".chars())); - assert!(!is_valid_range_quantifier(&"3".chars())); - assert!(!is_valid_range_quantifier(&"3,".chars())); - assert!(!is_valid_range_quantifier(&",6".chars())); - assert!(!is_valid_range_quantifier(&"3,6".chars())); - assert!(!is_valid_range_quantifier(&",".chars())); + assert!(verify_range_quantifier(&"3\\}".chars()).is_ok()); + assert!(verify_range_quantifier(&"3,\\}".chars()).is_ok()); + assert!(verify_range_quantifier(&",6\\}".chars()).is_ok()); + assert!(verify_range_quantifier(&"3,6\\}".chars()).is_ok()); + assert!(verify_range_quantifier(&",\\}".chars()).is_ok()); + assert!(verify_range_quantifier(&"32767\\}anything".chars()).is_ok()); + assert_eq!( + verify_range_quantifier(&"\\{3,6\\}".chars()), + Err(ExprError::InvalidBracketContent) + ); + assert_eq!( + verify_range_quantifier(&"\\}".chars()), + Err(ExprError::InvalidBracketContent) + ); + assert_eq!( + verify_range_quantifier(&"".chars()), + Err(ExprError::UnmatchedOpeningBrace) + ); + assert_eq!( + verify_range_quantifier(&"3".chars()), + Err(ExprError::UnmatchedOpeningBrace) + ); + assert_eq!( + verify_range_quantifier(&"3,".chars()), + Err(ExprError::UnmatchedOpeningBrace) + ); + assert_eq!( + verify_range_quantifier(&",6".chars()), + Err(ExprError::UnmatchedOpeningBrace) + ); + assert_eq!( + verify_range_quantifier(&"3,6".chars()), + Err(ExprError::UnmatchedOpeningBrace) + ); + assert_eq!( + verify_range_quantifier(&",".chars()), + Err(ExprError::UnmatchedOpeningBrace) + ); + assert_eq!( + verify_range_quantifier(&"32768\\}".chars()), + Err(ExprError::TooBigRangeQuantifierIndex) + ); } } diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 458ba9e846d..b8952bdb88b 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -1210,7 +1210,7 @@ mod gnu_expr { .args(&["_", ":", "a\\{32768\\}"]) .fails_with_code(2) .no_stdout() - .stderr_contains("Invalid content of \\{\\}"); + .stderr_contains("Regular expression too big\n"); } #[test] From ce0c2320ea2f4b400a15c663329942beea1ba1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 14:35:13 +0300 Subject: [PATCH 031/139] expr: Remove redundant checks for `UnmatchedOpeningBrace` It is handled in `verify_range_quantifier` function. --- src/uu/expr/src/syntax_tree.rs | 38 ++++++---------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 64386f9201f..23a79fb98af 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -8,7 +8,7 @@ use std::{cell::Cell, collections::BTreeMap}; use num_bigint::{BigInt, ParseBigIntError}; -use num_traits::{ToPrimitive, Zero}; +use num_traits::ToPrimitive; use onig::{Regex, RegexOptions, Syntax}; use crate::{ExprError, ExprResult}; @@ -351,15 +351,11 @@ where /// has specific error messages. fn check_posix_regex_errors(pattern: &str) -> ExprResult<()> { let mut escaped_parens: u64 = 0; - let mut escaped_braces: u64 = 0; let mut prev = '\0'; - let mut prev_is_escaped = false; - let mut is_brace_ignored = false; - let mut is_start_of_expression = true; + let mut curr_is_escaped = false; for curr in pattern.chars() { - let curr_is_escaped = prev == '\\' && !prev_is_escaped; - + curr_is_escaped = prev == '\\' && !curr_is_escaped; match (curr_is_escaped, curr) { (true, '(') => escaped_parens += 1, (true, ')') => { @@ -367,31 +363,14 @@ fn check_posix_regex_errors(pattern: &str) -> ExprResult<()> { .checked_sub(1) .ok_or(ExprError::UnmatchedClosingParenthesis)?; } - (true, '{') => { - is_brace_ignored = is_start_of_expression; - if !is_brace_ignored { - escaped_braces += 1; - } - } - (true, '}') => { - if !is_brace_ignored { - escaped_braces = escaped_braces.saturating_sub(1); - } - } _ => {} } - - is_start_of_expression = prev == '\0' - || curr_is_escaped && matches!(curr, '(' | '|') - || curr == '\\' && prev_is_escaped && matches!(prev, '(' | '|'); - prev_is_escaped = curr_is_escaped; prev = curr; } - match (escaped_parens.is_zero(), escaped_braces.is_zero()) { - (true, true) => Ok(()), - (_, false) => Err(ExprError::UnmatchedOpeningBrace), - (false, _) => Err(ExprError::UnmatchedOpeningParenthesis), + match escaped_parens { + 0 => Ok(()), + _ => Err(ExprError::UnmatchedOpeningParenthesis), } } @@ -1000,11 +979,6 @@ mod test { check_posix_regex_errors(r"\(abc"), Err(ExprError::UnmatchedOpeningParenthesis) ); - - assert_eq!( - check_posix_regex_errors(r"a\{1,2"), - Err(ExprError::UnmatchedOpeningBrace) - ); } #[test] From 210f4f7154a7e2e40646ea5fde8ff0fb61bf213c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 14:53:05 +0300 Subject: [PATCH 032/139] expr: Simplify parsing a range quantifier --- src/uu/expr/src/syntax_tree.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 23a79fb98af..1d396a2cc21 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -282,27 +282,25 @@ fn verify_range_quantifier(pattern_chars: &I) -> Result<(), ExprError> where I: Iterator + Clone, { - // Parse the string between braces - let mut quantifier = String::new(); let mut pattern_chars_clone = pattern_chars.clone().peekable(); - let Some(mut prev) = pattern_chars_clone.next() else { - return Err(ExprError::UnmatchedOpeningBrace); - }; if pattern_chars_clone.peek().is_none() { return Err(ExprError::UnmatchedOpeningBrace); } - let mut prev_is_escaped = false; + // Parse the string between braces + let mut quantifier = String::new(); + let mut prev = '\0'; + let mut curr_is_escaped = false; while let Some(curr) = pattern_chars_clone.next() { - if prev == '\\' && curr == '}' && !prev_is_escaped { + curr_is_escaped = prev == '\\' && !curr_is_escaped; + if curr_is_escaped && curr == '}' { break; } if pattern_chars_clone.peek().is_none() { return Err(ExprError::UnmatchedOpeningBrace); } - quantifier.push(prev); - prev_is_escaped = prev == '\\' && !prev_is_escaped; + quantifier.push(curr); prev = curr; } From ca6a10ea9a10ec1522cf567b437c8616221df7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 15:26:41 +0300 Subject: [PATCH 033/139] expr: Only '^' requires special treatment at the start of regex --- src/uu/expr/src/syntax_tree.rs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 1d396a2cc21..e660b096d13 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -151,27 +151,21 @@ impl StringOp { let right = right?.eval_as_string(); check_posix_regex_errors(&right)?; - // All patterns are anchored so they begin with a caret (^) + // Transpile the input pattern from BRE syntax to `onig` crate's `Syntax::grep` let mut re_string = String::with_capacity(right.len() + 1); - re_string.push('^'); - - // Handle first character from the input pattern let mut pattern_chars = right.chars().peekable(); - let first = pattern_chars.next(); - match first { - Some('^') => {} // Start of string anchor is already added - Some('$') if !is_end_of_expression(&pattern_chars) => re_string.push_str(r"\$"), - Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash), - Some(char) => re_string.push(char), - None => return Ok(0.into()), - }; - - // Handle the rest of the input pattern. - let mut prev = first.unwrap_or_default(); + let mut prev = '\0'; let mut prev_is_escaped = false; - let mut is_start_of_expression = first == Some('\\'); + let mut is_start_of_expression = true; + + // All patterns are anchored so they begin with a caret (^) + if pattern_chars.peek() != Some(&'^') { + re_string.push('^'); + } + while let Some(curr) = pattern_chars.next() { let curr_is_escaped = prev == '\\' && !prev_is_escaped; + let is_first_character = prev == '\0'; match curr { // Character class negation "[^a]" @@ -208,8 +202,10 @@ impl StringOp { // Capturing group "\(abc\)" // Alternative pattern "a\|b" - is_start_of_expression = curr_is_escaped && matches!(curr, '(' | '|') + is_start_of_expression = curr == '\\' && is_first_character + || curr_is_escaped && matches!(curr, '(' | '|') || curr == '\\' && prev_is_escaped && matches!(prev, '(' | '|'); + prev_is_escaped = curr_is_escaped; prev = curr; } From 2b565612eeb5b3cc9699299cfa010124337b124b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 16:43:43 +0300 Subject: [PATCH 034/139] expr: Fix parsing regex range quantifier --- src/uu/expr/src/syntax_tree.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index e660b096d13..3570dd6a45a 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -295,8 +295,9 @@ where if pattern_chars_clone.peek().is_none() { return Err(ExprError::UnmatchedOpeningBrace); } - - quantifier.push(curr); + if prev != '\0' { + quantifier.push(prev); + } prev = curr; } From 74ad163da98644cc3218dc1ab580cdbf8efdc393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 16:44:27 +0300 Subject: [PATCH 035/139] expr: Fix regex for validating range quantifier --- src/uu/expr/src/syntax_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 3570dd6a45a..9f817d395a2 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -302,7 +302,7 @@ where } // Check if parsed quantifier is valid - let re = Regex::new(r"^(\d*,\d*|\d+)").expect("valid regular expression"); + let re = Regex::new(r"^(\d*,\d*|\d+)$").expect("valid regular expression"); if let Some(captures) = re.captures(&quantifier) { let matched = captures.at(0).unwrap_or_default(); match matched.split_once(',') { From 4946922c0f151b5c5ad10d89fdf1a00b4be53c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 16:45:54 +0300 Subject: [PATCH 036/139] expr: Fix error message for large numbers as range index --- src/uu/expr/src/syntax_tree.rs | 21 ++++++--------------- tests/by-util/test_expr.rs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 9f817d395a2..9a8fb05f243 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -307,24 +307,15 @@ where let matched = captures.at(0).unwrap_or_default(); match matched.split_once(',') { Some(("", "")) => Ok(()), - Some((x, "")) | Some(("", x)) => match x.parse::() { - Ok(x) if x <= i16::MAX.into() => Ok(()), - Ok(_) => Err(ExprError::TooBigRangeQuantifierIndex), - Err(_) => Err(ExprError::InvalidBracketContent), - }, - Some((f, l)) => match (f.parse::(), l.parse::()) { + Some((x, "") | ("", x)) if x.parse::().is_ok() => Ok(()), + Some((_, "") | ("", _)) => Err(ExprError::TooBigRangeQuantifierIndex), + Some((f, l)) => match (f.parse::(), l.parse::()) { (Ok(f), Ok(l)) if f > l => Err(ExprError::InvalidBracketContent), - (Ok(f), Ok(l)) if f > i16::MAX.into() || l > i16::MAX.into() => { - Err(ExprError::TooBigRangeQuantifierIndex) - } (Ok(_), Ok(_)) => Ok(()), - _ => Err(ExprError::InvalidBracketContent), - }, - None => match matched.parse::() { - Ok(x) if x <= i16::MAX.into() => Ok(()), - Ok(_) => Err(ExprError::TooBigRangeQuantifierIndex), - Err(_) => Err(ExprError::InvalidBracketContent), + _ => Err(ExprError::TooBigRangeQuantifierIndex), }, + None if matched.parse::().is_ok() => Ok(()), + None => Err(ExprError::TooBigRangeQuantifierIndex), } } else { Err(ExprError::InvalidBracketContent) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index b8952bdb88b..0a16d4ea958 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -472,6 +472,26 @@ fn test_regex_range_quantifier() { .args(&["ab", ":", "ab\\{\\}"]) .fails() .stderr_only("expr: Invalid content of \\{\\}\n"); + new_ucmd!() + .args(&["_", ":", "a\\{12345678901234567890\\}"]) + .fails() + .stderr_only("expr: Regular expression too big\n"); + new_ucmd!() + .args(&["_", ":", "a\\{12345678901234567890,\\}"]) + .fails() + .stderr_only("expr: Regular expression too big\n"); + new_ucmd!() + .args(&["_", ":", "a\\{,12345678901234567890\\}"]) + .fails() + .stderr_only("expr: Regular expression too big\n"); + new_ucmd!() + .args(&["_", ":", "a\\{1,12345678901234567890\\}"]) + .fails() + .stderr_only("expr: Regular expression too big\n"); + new_ucmd!() + .args(&["_", ":", "a\\{1,1234567890abcdef\\}"]) + .fails() + .stderr_only("expr: Invalid content of \\{\\}\n"); } #[test] From eb7bc2f251f724b3a5e0878a1f0f986a1c527406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Tue, 27 May 2025 17:50:44 +0300 Subject: [PATCH 037/139] expr: Allow only ASCII digits for regex range quantifiers --- src/uu/expr/src/syntax_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 9a8fb05f243..0950020c955 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -302,7 +302,7 @@ where } // Check if parsed quantifier is valid - let re = Regex::new(r"^(\d*,\d*|\d+)$").expect("valid regular expression"); + let re = Regex::new(r"^([0-9]*,[0-9]*|[0-9]+)$").expect("valid regular expression"); if let Some(captures) = re.captures(&quantifier) { let matched = captures.at(0).unwrap_or_default(); match matched.split_once(',') { From c3cb9ef619492e2a642f15fb0f948cd63c47e49b Mon Sep 17 00:00:00 2001 From: E <79379754+oech3@users.noreply.github.com> Date: Wed, 28 May 2025 03:21:05 +0900 Subject: [PATCH 038/139] Add stty to GNUmakefile Add `stty` to `GNUmakefile` --- GNUmakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/GNUmakefile b/GNUmakefile index f46126a82f5..a80b10ec8b2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -127,6 +127,7 @@ PROGS := \ sleep \ sort \ split \ + stty \ sum \ sync \ tac \ From 72ccd8dcada1034c1e957fb6f5a37d66fea9d3b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 19:53:29 +0000 Subject: [PATCH 039/139] chore(deps): update rust crate clap to v4.5.39 --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a4cb3705a7..630aebef0a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,18 +348,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -944,7 +944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1405,7 +1405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -2149,7 +2149,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2162,7 +2162,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2406,7 +2406,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.1", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3909,7 +3909,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] From e92cf92f7ea1917782b68265f791fd4df8389780 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 19:53:36 +0000 Subject: [PATCH 040/139] chore(deps): update rust crate clap_complete to v4.5.51 --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a4cb3705a7..09095c482e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.50" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1" +checksum = "8d2267df7f3c8e74e38268887ea5235d4dfadd39bfff2d56ab82d61776be355e" dependencies = [ "clap", ] @@ -944,7 +944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1405,7 +1405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -2149,7 +2149,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2162,7 +2162,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2406,7 +2406,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.1", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3909,7 +3909,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] From 2408bd6b3647ec13b2b1f5d373efc7d0cc354b46 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Tue, 20 May 2025 10:25:28 -0700 Subject: [PATCH 041/139] GNUMakefile: fix compilation on Macs Previously, attempting to compile on Macs would bring in the SELinux dependencies (runcon) which led to compilation errors on Macs. Fixes #7695. --- GNUmakefile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index a80b10ec8b2..43d8bba8240 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -57,6 +57,13 @@ TOYBOX_ROOT := $(BASEDIR)/tmp TOYBOX_VER := 0.8.12 TOYBOX_SRC := $(TOYBOX_ROOT)/toybox-$(TOYBOX_VER) +#------------------------------------------------------------------------ +# Detect the host system. +# On Windows the environment already sets OS = Windows_NT. +# Otherwise let it default to the kernel name returned by uname -s +# (Linux, Darwin, FreeBSD, …). +#------------------------------------------------------------------------ +OS ?= $(shell uname -s) ifdef SELINUX_ENABLED override SELINUX_ENABLED := 0 @@ -180,6 +187,13 @@ SELINUX_PROGS := \ chcon \ runcon +$(info Detected OS = $(OS)) + +# Don't build the SELinux programs on macOS (Darwin) +ifeq ($(OS),Darwin) + SELINUX_PROGS := +endif + ifneq ($(OS),Windows_NT) PROGS := $(PROGS) $(UNIX_PROGS) # Build the selinux command even if not on the system From 24ff36ee88b66329c84684cd2bd7d3b3d12670ea Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Tue, 27 May 2025 15:09:40 -0700 Subject: [PATCH 042/139] .github: add additional combination to CI --- .github/workflows/CICD.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c9dcb2e3552..9bab8002897 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -526,6 +526,8 @@ jobs: - { os: ubuntu-latest , target: x86_64-unknown-redox , features: feat_os_unix_redox , use-cross: redoxer , skip-tests: true } - { os: ubuntu-latest , target: wasm32-unknown-unknown , default-features: false, features: uucore/format, skip-tests: true, skip-package: true, skip-publish: true } - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos, workspace-tests: true } # M1 CPU + # PR #7964: Mac should still build even if the feature is not enabled + - { os: macos-latest , target: aarch64-apple-darwin , workspace-tests: true } # M1 CPU - { os: macos-13 , target: x86_64-apple-darwin , features: feat_os_macos, workspace-tests: true } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } From f8d6eea2dcc5197778a23d32254e12da332218da Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Tue, 27 May 2025 20:53:54 -0400 Subject: [PATCH 043/139] sync: remove Android workaround The libc crate now exposes Android's sync like other unix platforms. --- src/uu/sync/src/sync.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index abcdf40997e..a299f4ea55c 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -38,13 +38,9 @@ mod platform { use uucore::error::UResult; /// # Safety - /// This function is unsafe because it calls `libc::sync` or `libc::syscall` which are unsafe. + /// This function is unsafe because it calls `libc::sync` which is unsafe. pub unsafe fn do_sync() -> UResult<()> { unsafe { - // see https://github.com/rust-lang/libc/pull/2161 - #[cfg(target_os = "android")] - libc::syscall(libc::SYS_sync); - #[cfg(not(target_os = "android"))] libc::sync(); } Ok(()) From f32f1efc2374ddf2d84113e047672294fac358d1 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Tue, 27 May 2025 22:23:21 -0400 Subject: [PATCH 044/139] sync: reduce use of unsafe and improve comments --- src/uu/sync/Cargo.toml | 2 +- src/uu/sync/src/sync.rs | 94 +++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index c6f876e3f0f..dc6d874348e 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -22,7 +22,7 @@ clap = { workspace = true } libc = { workspace = true } uucore = { workspace = true, features = ["wide"] } -[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +[target.'cfg(unix)'.dependencies] nix = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index a299f4ea55c..1ae3d623014 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -31,41 +31,32 @@ static ARG_FILES: &str = "files"; #[cfg(unix)] mod platform { + use nix::unistd::sync; #[cfg(any(target_os = "linux", target_os = "android"))] - use std::fs::File; + use nix::unistd::{fdatasync, syncfs}; #[cfg(any(target_os = "linux", target_os = "android"))] - use std::os::unix::io::AsRawFd; + use std::fs::File; use uucore::error::UResult; - /// # Safety - /// This function is unsafe because it calls `libc::sync` which is unsafe. - pub unsafe fn do_sync() -> UResult<()> { - unsafe { - libc::sync(); - } + pub fn do_sync() -> UResult<()> { + sync(); Ok(()) } #[cfg(any(target_os = "linux", target_os = "android"))] - /// # Safety - /// This function is unsafe because it calls `libc::syscall` which is unsafe. - pub unsafe fn do_syncfs(files: Vec) -> UResult<()> { + pub fn do_syncfs(files: Vec) -> UResult<()> { for path in files { let f = File::open(path).unwrap(); - let fd = f.as_raw_fd(); - unsafe { libc::syscall(libc::SYS_syncfs, fd) }; + syncfs(f)?; } Ok(()) } #[cfg(any(target_os = "linux", target_os = "android"))] - /// # Safety - /// This function is unsafe because it calls `libc::syscall` which is unsafe. - pub unsafe fn do_fdatasync(files: Vec) -> UResult<()> { + pub fn do_fdatasync(files: Vec) -> UResult<()> { for path in files { let f = File::open(path).unwrap(); - let fd = f.as_raw_fd(); - unsafe { libc::syscall(libc::SYS_fdatasync, fd) }; + fdatasync(f)?; } Ok(()) } @@ -86,17 +77,22 @@ mod platform { }; use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED; - /// # Safety - /// This function is unsafe because it calls an unsafe function. - unsafe fn flush_volume(name: &str) -> UResult<()> { + fn get_last_error() -> u32 { + // SAFETY: GetLastError has no safety preconditions + unsafe { GetLastError() as u32 } + } + + fn flush_volume(name: &str) -> UResult<()> { let name_wide = name.to_wide_null(); + // SAFETY: `name` is a valid `str`, so `name_wide` is valid null-terminated UTF-16 if unsafe { GetDriveTypeW(name_wide.as_ptr()) } == DRIVE_FIXED { let sliced_name = &name[..name.len() - 1]; // eliminate trailing backslash match OpenOptions::new().write(true).open(sliced_name) { Ok(file) => { + // SAFETY: `file` is a valid `File` if unsafe { FlushFileBuffers(file.as_raw_handle() as HANDLE) } == 0 { Err(USimpleError::new( - unsafe { GetLastError() } as i32, + get_last_error() as i32, "failed to flush file buffer", )) } else { @@ -113,14 +109,13 @@ mod platform { } } - /// # Safety - /// This function is unsafe because it calls an unsafe function. - unsafe fn find_first_volume() -> UResult<(String, HANDLE)> { + fn find_first_volume() -> UResult<(String, HANDLE)> { let mut name: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize]; + // SAFETY: `name` was just constructed and in scope, `len()` is its length by definition let handle = unsafe { FindFirstVolumeW(name.as_mut_ptr(), name.len() as u32) }; if handle == INVALID_HANDLE_VALUE { return Err(USimpleError::new( - unsafe { GetLastError() } as i32, + get_last_error() as i32, "failed to find first volume", )); } @@ -129,16 +124,19 @@ mod platform { /// # Safety /// This function is unsafe because it calls an unsafe function. - unsafe fn find_all_volumes() -> UResult> { - let (first_volume, next_volume_handle) = unsafe { find_first_volume()? }; + fn find_all_volumes() -> UResult> { + let (first_volume, next_volume_handle) = find_first_volume()?; let mut volumes = vec![first_volume]; loop { let mut name: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize]; + // SAFETY: `next_volume_handle` was returned by `find_first_volume`, + // `name` was just constructed and in scope, `len()` is its length by definition if unsafe { FindNextVolumeW(next_volume_handle, name.as_mut_ptr(), name.len() as u32) } == 0 { - return match unsafe { GetLastError() } { + return match get_last_error() { ERROR_NO_MORE_FILES => { + // SAFETY: next_volume_handle was returned by `find_first_volume` unsafe { FindVolumeClose(next_volume_handle) }; Ok(volumes) } @@ -150,31 +148,25 @@ mod platform { } } - /// # Safety - /// This function is unsafe because it calls `find_all_volumes` which is unsafe. - pub unsafe fn do_sync() -> UResult<()> { - let volumes = unsafe { find_all_volumes()? }; + pub fn do_sync() -> UResult<()> { + let volumes = find_all_volumes()?; for vol in &volumes { - unsafe { flush_volume(vol)? }; + flush_volume(vol)?; } Ok(()) } - /// # Safety - /// This function is unsafe because it calls `find_all_volumes` which is unsafe. - pub unsafe fn do_syncfs(files: Vec) -> UResult<()> { + pub fn do_syncfs(files: Vec) -> UResult<()> { for path in files { - unsafe { - flush_volume( - Path::new(&path) - .components() - .next() - .unwrap() - .as_os_str() - .to_str() - .unwrap(), - )?; - } + flush_volume( + Path::new(&path) + .components() + .next() + .unwrap() + .as_os_str() + .to_str() + .unwrap(), + )?; } Ok(()) } @@ -259,15 +251,15 @@ pub fn uu_app() -> Command { } fn sync() -> UResult<()> { - unsafe { platform::do_sync() } + platform::do_sync() } #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))] fn syncfs(files: Vec) -> UResult<()> { - unsafe { platform::do_syncfs(files) } + platform::do_syncfs(files) } #[cfg(any(target_os = "linux", target_os = "android"))] fn fdatasync(files: Vec) -> UResult<()> { - unsafe { platform::do_fdatasync(files) } + platform::do_fdatasync(files) } From 0f632f2fbab49b16705fc2a80cc3b02a6d6d1b47 Mon Sep 17 00:00:00 2001 From: "jovie :)" <44442031+jovielarue@users.noreply.github.com> Date: Wed, 28 May 2025 01:29:19 -0600 Subject: [PATCH 045/139] chroot: remove unwrap calls (#7890) --- src/uu/chroot/src/chroot.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 3f7c0886b5f..15c7bac4d8d 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -439,7 +439,7 @@ fn enter_chroot(root: &Path, skip_chdir: bool) -> UResult<()> { let err = unsafe { chroot( CString::new(root.as_os_str().as_bytes().to_vec()) - .unwrap() + .map_err(|e| ChrootError::CannotEnter("root".to_string(), e.into()))? .as_bytes_with_nul() .as_ptr() .cast::(), @@ -448,7 +448,7 @@ fn enter_chroot(root: &Path, skip_chdir: bool) -> UResult<()> { if err == 0 { if !skip_chdir { - std::env::set_current_dir("/").unwrap(); + std::env::set_current_dir("/")?; } Ok(()) } else { From c14282209d22e78c2bacb7998ac9d3c2d248735e Mon Sep 17 00:00:00 2001 From: Etienne Cordonnier Date: Wed, 28 May 2025 11:54:03 +0200 Subject: [PATCH 046/139] utmpx: silence musl warnings Fixes https://github.com/uutils/coreutils/issues/7865 We are intentionally calling the stub utmpx functions of musl, in order to achieve behavior parity with GNU coreutils, and in order to be able to ship those utilities for musl libc. Silence those warnings. See also https://github.com/uutils/coreutils/issues/1361 for details. Signed-off-by: Etienne Cordonnier --- src/uucore/src/lib/features/utmpx.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 46bc6d828d2..1dba4e16e16 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -40,11 +40,19 @@ use std::ptr; use std::sync::{Mutex, MutexGuard}; pub use self::ut::*; + +// See the FAQ at https://wiki.musl-libc.org/faq#Q:-Why-is-the-utmp/wtmp-functionality-only-implemented-as-stubs? +// Musl implements only stubs for the utmp functions, and the libc crate issues a deprecation warning about this. +// However, calling these stubs is the correct approach to maintain consistent behavior with GNU coreutils. +#[cfg_attr(target_env = "musl", allow(deprecated))] pub use libc::endutxent; +#[cfg_attr(target_env = "musl", allow(deprecated))] pub use libc::getutxent; +#[cfg_attr(target_env = "musl", allow(deprecated))] pub use libc::setutxent; use libc::utmpx; #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "netbsd"))] +#[cfg_attr(target_env = "musl", allow(deprecated))] pub use libc::utmpxname; /// # Safety @@ -278,6 +286,7 @@ impl Utmpx { // This can technically fail, and it would be nice to detect that, // but it doesn't return anything so we'd have to do nasty things // with errno. + #[cfg_attr(target_env = "musl", allow(deprecated))] setutxent(); } iter @@ -302,7 +311,9 @@ impl Utmpx { // is specified, no warning or anything. // So this function is pretty crazy and we don't try to detect errors. // Not much we can do besides pray. + #[cfg_attr(target_env = "musl", allow(deprecated))] utmpxname(path.as_ptr()); + #[cfg_attr(target_env = "musl", allow(deprecated))] setutxent(); } iter @@ -342,6 +353,7 @@ impl Iterator for UtmpxIter { type Item = Utmpx; fn next(&mut self) -> Option { unsafe { + #[cfg_attr(target_env = "musl", allow(deprecated))] let res = getutxent(); if res.is_null() { None @@ -361,6 +373,7 @@ impl Iterator for UtmpxIter { impl Drop for UtmpxIter { fn drop(&mut self) { unsafe { + #[cfg_attr(target_env = "musl", allow(deprecated))] endutxent(); } } From 07f7226a96e599f2ba0a29be1174fcc561f0d5a1 Mon Sep 17 00:00:00 2001 From: Caleb Maclennan Date: Wed, 28 May 2025 16:20:55 +0300 Subject: [PATCH 047/139] deny.toml: remove obsolete skips related to rustix The holdup on crossterm was bumped in d5258d280, bringing rustix to 0.38.44. Meanwhile while linux-raw-sys is still at 0.4.15 in the lock file, it is unclear to my why it got added in 2c0f97fc3 in the first place as it does not seem to trigger any complaints from `cargo deny`. --- deny.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deny.toml b/deny.toml index 1ae92b14a72..1b1700dcdf4 100644 --- a/deny.toml +++ b/deny.toml @@ -98,10 +98,6 @@ skip = [ { name = "rand_chacha", version = "0.3.1" }, # rand { name = "rand_core", version = "0.6.4" }, - # crossterm, procfs, terminal_size - { name = "rustix", version = "0.38.43" }, - # rustix - { name = "linux-raw-sys", version = "0.4.15" }, ] # spell-checker: enable From 7439050d85f674a1886c4dc497fade20a9d794a7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 28 May 2025 15:53:54 +0200 Subject: [PATCH 048/139] csplit: only allow ASCII digits for repeat pattern --- src/uu/csplit/src/patterns.rs | 2 +- tests/by-util/test_csplit.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index bdf15b51197..b49d1c60699 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -107,7 +107,7 @@ fn extract_patterns(args: &[String]) -> Result, CsplitError> { let mut patterns = Vec::with_capacity(args.len()); let to_match_reg = Regex::new(r"^(/(?P.+)/|%(?P.+)%)(?P[\+-]?\d+)?$").unwrap(); - let execute_ntimes_reg = Regex::new(r"^\{(?P\d+)|\*\}$").unwrap(); + let execute_ntimes_reg = Regex::new(r"^\{(?P[0-9]+)|\*\}$").unwrap(); let mut iter = args.iter().peekable(); while let Some(arg) = iter.next() { diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index a7a802b92f0..2a94c10bf7e 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -83,6 +83,15 @@ fn test_up_to_line_sequence() { assert_eq!(at.read("xx02"), generate(25, 51)); } +#[test] +fn test_up_to_line_with_non_ascii_repeat() { + // we use a different error message than GNU + new_ucmd!() + .args(&["numbers50.txt", "10", "{𝟚}"]) + .fails() + .stderr_contains("invalid pattern"); +} + #[test] fn test_up_to_match() { let (at, mut ucmd) = at_and_ucmd!(); From d5b6af5216fd9cdefa9004703ecaa76684181213 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 28 May 2025 16:07:45 +0200 Subject: [PATCH 049/139] csplit: only allow ASCII digits as offset --- src/uu/csplit/src/patterns.rs | 2 +- tests/by-util/test_csplit.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index b49d1c60699..9326ea3795e 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -106,7 +106,7 @@ pub fn get_patterns(args: &[String]) -> Result, CsplitError> { fn extract_patterns(args: &[String]) -> Result, CsplitError> { let mut patterns = Vec::with_capacity(args.len()); let to_match_reg = - Regex::new(r"^(/(?P.+)/|%(?P.+)%)(?P[\+-]?\d+)?$").unwrap(); + Regex::new(r"^(/(?P.+)/|%(?P.+)%)(?P[\+-]?[0-9]+)?$").unwrap(); let execute_ntimes_reg = Regex::new(r"^\{(?P[0-9]+)|\*\}$").unwrap(); let mut iter = args.iter().peekable(); diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 2a94c10bf7e..96322e44474 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -176,6 +176,15 @@ fn test_up_to_match_offset_repeat_twice() { assert_eq!(at.read("xx03"), generate(32, 51)); } +#[test] +fn test_up_to_match_non_ascii_offset() { + // we use a different error message than GNU + new_ucmd!() + .args(&["numbers50.txt", "/9$/𝟚"]) + .fails() + .stderr_contains("invalid pattern"); +} + #[test] fn test_up_to_match_negative_offset() { let (at, mut ucmd) = at_and_ucmd!(); From 5e92917b6d8649c07d45e211769c6b3cdd2c824e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 28 May 2025 19:54:11 +0200 Subject: [PATCH 050/139] Cargo.toml: remove unused coz dependency --- Cargo.lock | 11 ----------- Cargo.toml | 1 - src/uu/factor/Cargo.toml | 1 - 3 files changed, 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de221b82ac8..d447ee4e408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,16 +601,6 @@ dependencies = [ "zip", ] -[[package]] -name = "coz" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef55b3fe2f5477d59e12bc792e8b3c95a25bd099eadcfae006ecea136de76e2" -dependencies = [ - "libc", - "once_cell", -] - [[package]] name = "cpp" version = "0.5.10" @@ -2931,7 +2921,6 @@ name = "uu_factor" version = "0.1.0" dependencies = [ "clap", - "coz", "num-bigint", "num-prime", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 656c7f1ad6b..4a61b5f0321 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -287,7 +287,6 @@ clap = { version = "4.5", features = ["wrap_help", "cargo"] } clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" -coz = { version = "0.1.3" } crossterm = "0.29.0" ctrlc = { version = "3.4.7", features = ["termination"] } dns-lookup = { version = "2.0.4" } diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 6b875074ec5..2bc850250ae 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -19,7 +19,6 @@ num-traits = { workspace = true } # used in src/numerics.rs, which is included b [dependencies] clap = { workspace = true } -coz = { workspace = true, optional = true } num-traits = { workspace = true } rand = { workspace = true } smallvec = { workspace = true } From e6784a600f8e4574e26aa57bec5cc541d99efae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= Date: Wed, 28 May 2025 22:43:06 +0300 Subject: [PATCH 051/139] expr: Enable GNU test `expr.pl` --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index 978545b26fa..c4c371d070f 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -14,7 +14,6 @@ This file documents why some tests are failing: * gnu/tests/du/long-from-unreadable.sh - https://github.com/uutils/coreutils/issues/7217 * gnu/tests/du/move-dir-while-traversing.sh * gnu/tests/expr/expr-multibyte.pl -* gnu/tests/expr/expr.pl * gnu/tests/fmt/goal-option.sh * gnu/tests/fmt/non-space.sh * gnu/tests/head/head-elide-tail.pl From 72597bcf7bac4ebd7be2407f0f4bc18263fcad0a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 May 2025 18:25:30 +0200 Subject: [PATCH 052/139] locale: refactor the locale system: * remove the default value. Avoid duplication of the english string + facilitate translation * have english as a default. Load english when the translated string isn't available --- src/uucore/src/lib/mods/locale.rs | 231 +++++++++++++++--------------- 1 file changed, 117 insertions(+), 114 deletions(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index bcc9fb2db6a..3dd6b410503 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -45,26 +45,47 @@ impl UError for LocalizationError { pub const DEFAULT_LOCALE: &str = "en-US"; -// A struct to handle localization +// A struct to handle localization with optional English fallback struct Localizer { - bundle: FluentBundle, + primary_bundle: FluentBundle, + fallback_bundle: Option>, } impl Localizer { - fn new(bundle: FluentBundle) -> Self { - Self { bundle } + fn new(primary_bundle: FluentBundle) -> Self { + Self { + primary_bundle, + fallback_bundle: None, + } + } + + fn with_fallback(mut self, fallback_bundle: FluentBundle) -> Self { + self.fallback_bundle = Some(fallback_bundle); + self } - fn format(&self, id: &str, args: Option<&FluentArgs>, default: &str) -> String { - match self.bundle.get_message(id).and_then(|m| m.value()) { - Some(value) => { + fn format(&self, id: &str, args: Option<&FluentArgs>) -> String { + // Try primary bundle first + if let Some(message) = self.primary_bundle.get_message(id).and_then(|m| m.value()) { + let mut errs = Vec::new(); + return self + .primary_bundle + .format_pattern(message, args, &mut errs) + .to_string(); + } + + // Fall back to English bundle if available + if let Some(ref fallback) = self.fallback_bundle { + if let Some(message) = fallback.get_message(id).and_then(|m| m.value()) { let mut errs = Vec::new(); - self.bundle - .format_pattern(value, args, &mut errs) - .to_string() + return fallback + .format_pattern(message, args, &mut errs) + .to_string(); } - None => default.to_string(), } + + // Return the key ID if not found anywhere + id.to_string() } } @@ -76,117 +97,117 @@ thread_local! { // Initialize localization with a specific locale and config fn init_localization( locale: &LanguageIdentifier, - config: &LocalizationConfig, + locales_dir: &Path, ) -> Result<(), LocalizationError> { - let bundle = create_bundle(locale, config)?; + let en_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) + .expect("Default locale should always be valid"); + + let english_bundle = create_bundle(&en_locale, locales_dir)?; + let loc = if locale == &en_locale { + // If requesting English, just use English as primary (no fallback needed) + Localizer::new(english_bundle) + } else { + // Try to load the requested locale + if let Ok(primary_bundle) = create_bundle(locale, locales_dir) { + // Successfully loaded requested locale, load English as fallback + Localizer::new(primary_bundle).with_fallback(english_bundle) + } else { + // Failed to load requested locale, just use English as primary + Localizer::new(english_bundle) + } + }; + LOCALIZER.with(|lock| { - let loc = Localizer::new(bundle); lock.set(loc) .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) })?; Ok(()) } -// Create a bundle for a locale with fallback chain +// Create a bundle for a specific locale fn create_bundle( locale: &LanguageIdentifier, - config: &LocalizationConfig, + locales_dir: &Path, ) -> Result, LocalizationError> { - // Create a new bundle with requested locale - let mut bundle = FluentBundle::new(vec![locale.clone()]); - - // Try to load the requested locale - let mut locales_to_try = vec![locale.clone()]; - locales_to_try.extend_from_slice(&config.fallback_locales); + let locale_path = locales_dir.join(format!("{locale}.ftl")); - // Try each locale in the chain - let mut tried_paths = Vec::new(); - - for try_locale in locales_to_try { - let locale_path = config.get_locale_path(&try_locale); - tried_paths.push(locale_path.clone()); - - if let Ok(ftl_file) = fs::read_to_string(&locale_path) { - let resource = FluentResource::try_new(ftl_file).map_err(|_| { - LocalizationError::Parse(format!( - "Failed to parse localization resource for {}", - try_locale - )) - })?; + let ftl_file = fs::read_to_string(&locale_path).map_err(|e| LocalizationError::Io { + source: e, + path: locale_path.clone(), + })?; - bundle.add_resource(resource).map_err(|_| { - LocalizationError::Bundle(format!( - "Failed to add resource to bundle for {}", - try_locale - )) - })?; + let resource = FluentResource::try_new(ftl_file).map_err(|_| { + LocalizationError::Parse(format!( + "Failed to parse localization resource for {}: {}", + locale, + locale_path.display() + )) + })?; - return Ok(bundle); - } - } + let mut bundle = FluentBundle::new(vec![locale.clone()]); - let paths_str = tried_paths - .iter() - .map(|p| p.to_string_lossy().to_string()) - .collect::>() - .join(", "); + bundle.add_resource(resource).map_err(|errs| { + LocalizationError::Bundle(format!( + "Failed to add resource to bundle for {}: {:?}", + locale, errs + )) + })?; - Err(LocalizationError::Io { - source: std::io::Error::new(std::io::ErrorKind::NotFound, "No localization files found"), - path: PathBuf::from(paths_str), - }) + Ok(bundle) } -fn get_message_internal(id: &str, args: Option, default: &str) -> String { +fn get_message_internal(id: &str, args: Option) -> String { LOCALIZER.with(|lock| { lock.get() - .map(|loc| loc.format(id, args.as_ref(), default)) - .unwrap_or_else(|| default.to_string()) + .map(|loc| loc.format(id, args.as_ref())) + .unwrap_or_else(|| id.to_string()) // Return the key ID if localizer not initialized }) } /// Retrieves a localized message by its identifier. /// /// Looks up a message with the given ID in the current locale bundle and returns -/// the localized text. If the message ID is not found, returns the provided default text. +/// the localized text. If the message ID is not found in the current locale, +/// it will fall back to English. If the message is not found in English either, +/// returns the message ID itself. /// /// # Arguments /// /// * `id` - The message identifier in the Fluent resources -/// * `default` - Default text to use if the message ID isn't found /// /// # Returns /// -/// A `String` containing either the localized message or the default text +/// A `String` containing the localized message, or the message ID if not found /// /// # Examples /// /// ``` /// use uucore::locale::get_message; /// -/// // Get a localized greeting or fall back to English -/// let greeting = get_message("greeting", "Hello, World!"); +/// // Get a localized greeting (from .ftl files) +/// let greeting = get_message("greeting"); /// println!("{}", greeting); /// ``` -pub fn get_message(id: &str, default: &str) -> String { - get_message_internal(id, None, default) +pub fn get_message(id: &str) -> String { + get_message_internal(id, None) } /// Retrieves a localized message with variable substitution. /// /// Looks up a message with the given ID in the current locale bundle, /// substitutes variables from the provided arguments map, and returns the -/// localized text. If the message ID is not found, returns the provided default text. +/// localized text. If the message ID is not found in the current locale, +/// it will fall back to English. If the message is not found in English either, +/// returns the message ID itself. /// /// # Arguments /// /// * `id` - The message identifier in the Fluent resources /// * `ftl_args` - Key-value pairs for variable substitution in the message -/// * `default` - Default text to use if the message ID isn't found /// /// # Returns /// -/// A `String` containing either the localized message with variable substitution or the default text +/// A `String` containing the localized message with variable substitution, or the message ID if not found /// /// # Examples /// @@ -199,44 +220,25 @@ pub fn get_message(id: &str, default: &str) -> String { /// args.insert("name".to_string(), "Alice".to_string()); /// args.insert("count".to_string(), "3".to_string()); /// -/// let message = get_message_with_args( -/// "notification", -/// args, -/// "Hello! You have notifications." -/// ); +/// let message = get_message_with_args("notification", args); /// println!("{}", message); /// ``` -pub fn get_message_with_args(id: &str, ftl_args: HashMap, default: &str) -> String { - let args = ftl_args.into_iter().collect(); - get_message_internal(id, Some(args), default) -} - -// Configuration for localization -#[derive(Clone)] -struct LocalizationConfig { - locales_dir: PathBuf, - fallback_locales: Vec, -} - -impl LocalizationConfig { - // Create a new config with a specific locales directory - fn new>(locales_dir: P) -> Self { - Self { - locales_dir: locales_dir.as_ref().to_path_buf(), - fallback_locales: vec![], +pub fn get_message_with_args(id: &str, ftl_args: HashMap) -> String { + let mut args = FluentArgs::new(); + + for (key, value) in ftl_args { + // Try to parse as number first for proper pluralization support + if let Ok(num_val) = value.parse::() { + args.set(key, num_val); + } else if let Ok(float_val) = value.parse::() { + args.set(key, float_val); + } else { + // Keep as string if not a number + args.set(key, value); } } - // Set fallback locales - fn with_fallbacks(mut self, fallbacks: Vec) -> Self { - self.fallback_locales = fallbacks; - self - } - - // Get path for a specific locale - fn get_locale_path(&self, locale: &LanguageIdentifier) -> PathBuf { - self.locales_dir.join(format!("{}.ftl", locale)) - } + get_message_internal(id, Some(args)) } // Function to detect system locale from environment variables @@ -252,11 +254,12 @@ fn detect_system_locale() -> Result { .map_err(|_| LocalizationError::Parse(format!("Failed to parse locale: {}", locale_str))) } -/// Sets up localization using the system locale (or default) and project paths. +/// Sets up localization using the system locale with English fallback. /// /// This function initializes the localization system based on the system's locale -/// preferences (via the LANG environment variable) or falls back to the default locale -/// if the system locale cannot be determined or is invalid. +/// preferences (via the LANG environment variable) or falls back to English +/// if the system locale cannot be determined or the locale file doesn't exist. +/// English is always loaded as a fallback. /// /// # Arguments /// @@ -270,8 +273,8 @@ fn detect_system_locale() -> Result { /// # Errors /// /// Returns a `LocalizationError` if: -/// * The localization files cannot be read -/// * The files contain invalid syntax +/// * The en-US.ftl file cannot be read (English is required) +/// * The files contain invalid Fluent syntax /// * The bundle cannot be initialized properly /// /// # Examples @@ -280,6 +283,8 @@ fn detect_system_locale() -> Result { /// use uucore::locale::setup_localization; /// /// // Initialize localization using files in the "locales" directory +/// // Make sure you have at least an "en-US.ftl" file in this directory +/// // Other locale files like "fr-FR.ftl" are optional /// match setup_localization("./locales") { /// Ok(_) => println!("Localization initialized successfully"), /// Err(e) => eprintln!("Failed to initialize localization: {}", e), @@ -290,14 +295,12 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> { LanguageIdentifier::from_str(DEFAULT_LOCALE).expect("Default locale should always be valid") }); - let locales_dir = PathBuf::from(p); - let fallback_locales = vec![ - LanguageIdentifier::from_str(DEFAULT_LOCALE) - .expect("Default locale should always be valid"), - ]; + let coreutils_path = PathBuf::from(format!("src/uu/{p}/locales/")); + let locales_dir = if coreutils_path.exists() { + coreutils_path + } else { + PathBuf::from(p) + }; - let config = LocalizationConfig::new(locales_dir).with_fallbacks(fallback_locales); - - init_localization(&locale, &config)?; - Ok(()) + init_localization(&locale, &locales_dir) } From 463f7c1530c9b5018b4b2e31c41ccc66146d5067 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 May 2025 19:07:25 +0200 Subject: [PATCH 053/139] locale: add unit tests --- src/uucore/src/lib/mods/locale.rs | 678 +++++++++++++++++++++++++++++- 1 file changed, 677 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 3dd6b410503..142ba962f40 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore unic_langid +// spell-checker:disable use crate::error::UError; use fluent::{FluentArgs, FluentBundle, FluentResource}; @@ -304,3 +304,679 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> { init_localization(&locale, &locales_dir) } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::env; + use std::fs; + use std::path::PathBuf; + use tempfile::TempDir; + + // Helper function to create a temporary directory with test locale files + fn create_test_locales_dir() -> TempDir { + let temp_dir = TempDir::new().expect("Failed to create temp directory"); + + // Create en-US.ftl + let en_content = r#" +greeting = Hello, world! +welcome = Welcome, { $name }! +count-items = You have { $count -> + [one] { $count } item + *[other] { $count } items +} +missing-in-other = This message only exists in English +"#; + + // Create fr-FR.ftl + let fr_content = r#" +greeting = Bonjour, le monde! +welcome = Bienvenue, { $name }! +count-items = Vous avez { $count -> + [one] { $count } élément + *[other] { $count } éléments +} +"#; + + // Create ja-JP.ftl (Japanese) + let ja_content = r#" +greeting = こんにちは、世界! +welcome = ようこそ、{ $name }さん! +count-items = { $count }個のアイテムがあります +"#; + + // Create ar-SA.ftl (Arabic - Right-to-Left) + let ar_content = r#" +greeting = أهلاً بالعالم! +welcome = أهلاً وسهلاً، { $name }! +count-items = لديك { $count -> + [zero] لا عناصر + [one] عنصر واحد + [two] عنصران + [few] { $count } عناصر + *[other] { $count } عنصر +} +"#; + + // Create es-ES.ftl with invalid syntax + let es_invalid_content = r#" +greeting = Hola, mundo! +invalid-syntax = This is { $missing +"#; + + fs::write(temp_dir.path().join("en-US.ftl"), en_content) + .expect("Failed to write en-US.ftl"); + fs::write(temp_dir.path().join("fr-FR.ftl"), fr_content) + .expect("Failed to write fr-FR.ftl"); + fs::write(temp_dir.path().join("ja-JP.ftl"), ja_content) + .expect("Failed to write ja-JP.ftl"); + fs::write(temp_dir.path().join("ar-SA.ftl"), ar_content) + .expect("Failed to write ar-SA.ftl"); + fs::write(temp_dir.path().join("es-ES.ftl"), es_invalid_content) + .expect("Failed to write es-ES.ftl"); + + temp_dir + } + + #[test] + fn test_localization_error_from_io_error() { + let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"); + let loc_error = LocalizationError::from(io_error); + + match loc_error { + LocalizationError::Io { source: _, path } => { + assert_eq!(path, PathBuf::from("")); + } + _ => panic!("Expected IO error variant"), + } + } + + #[test] + fn test_localization_error_uerror_impl() { + let error = LocalizationError::Parse("test error".to_string()); + assert_eq!(error.code(), 1); + } + + #[test] + fn test_create_bundle_success() { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("en-US").unwrap(); + + let result = create_bundle(&locale, temp_dir.path()); + assert!(result.is_ok()); + + let bundle = result.unwrap(); + assert!(bundle.get_message("greeting").is_some()); + } + + #[test] + fn test_create_bundle_file_not_found() { + let temp_dir = TempDir::new().unwrap(); + let locale = LanguageIdentifier::from_str("de-DE").unwrap(); + + let result = create_bundle(&locale, temp_dir.path()); + assert!(result.is_err()); + + if let Err(LocalizationError::Io { source: _, path }) = result { + assert!(path.to_string_lossy().contains("de-DE.ftl")); + } else { + panic!("Expected IO error"); + } + } + + #[test] + fn test_create_bundle_invalid_syntax() { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("es-ES").unwrap(); + + let result = create_bundle(&locale, temp_dir.path()); + assert!(result.is_err()); + + if let Err(LocalizationError::Parse(_)) = result { + // Expected parse error + } else { + panic!("Expected parse error"); + } + } + + #[test] + fn test_localizer_format_primary_bundle() { + let temp_dir = create_test_locales_dir(); + let en_bundle = create_bundle( + &LanguageIdentifier::from_str("en-US").unwrap(), + temp_dir.path(), + ) + .unwrap(); + + let localizer = Localizer::new(en_bundle); + let result = localizer.format("greeting", None); + assert_eq!(result, "Hello, world!"); + } + + #[test] + fn test_localizer_format_with_args() { + let temp_dir = create_test_locales_dir(); + let en_bundle = create_bundle( + &LanguageIdentifier::from_str("en-US").unwrap(), + temp_dir.path(), + ) + .unwrap(); + + let localizer = Localizer::new(en_bundle); + let mut args = FluentArgs::new(); + args.set("name", "Alice"); + + let result = localizer.format("welcome", Some(&args)); + assert_eq!(result, "Welcome, \u{2068}Alice\u{2069}!"); + } + + #[test] + fn test_localizer_fallback_to_english() { + let temp_dir = create_test_locales_dir(); + let fr_bundle = create_bundle( + &LanguageIdentifier::from_str("fr-FR").unwrap(), + temp_dir.path(), + ) + .unwrap(); + let en_bundle = create_bundle( + &LanguageIdentifier::from_str("en-US").unwrap(), + temp_dir.path(), + ) + .unwrap(); + + let localizer = Localizer::new(fr_bundle).with_fallback(en_bundle); + + // This message exists in French + let result1 = localizer.format("greeting", None); + assert_eq!(result1, "Bonjour, le monde!"); + + // This message only exists in English, should fallback + let result2 = localizer.format("missing-in-other", None); + assert_eq!(result2, "This message only exists in English"); + } + + #[test] + fn test_localizer_format_message_not_found() { + let temp_dir = create_test_locales_dir(); + let en_bundle = create_bundle( + &LanguageIdentifier::from_str("en-US").unwrap(), + temp_dir.path(), + ) + .unwrap(); + + let localizer = Localizer::new(en_bundle); + let result = localizer.format("nonexistent-message", None); + assert_eq!(result, "nonexistent-message"); + } + + #[test] + fn test_init_localization_english_only() { + // Run in a separate thread to avoid conflicts with other tests + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("en-US").unwrap(); + + let result = init_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Test that we can get messages + let message = get_message("greeting"); + assert_eq!(message, "Hello, world!"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_init_localization_with_fallback() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("fr-FR").unwrap(); + + let result = init_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Test French message + let message1 = get_message("greeting"); + assert_eq!(message1, "Bonjour, le monde!"); + + // Test fallback to English + let message2 = get_message("missing-in-other"); + assert_eq!(message2, "This message only exists in English"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_init_localization_invalid_locale_falls_back_to_english() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("de-DE").unwrap(); // No German file + + let result = init_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Should use English as primary since German failed to load + let message = get_message("greeting"); + assert_eq!(message, "Hello, world!"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_init_localization_already_initialized() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("en-US").unwrap(); + + // Initialize once + let result1 = init_localization(&locale, temp_dir.path()); + assert!(result1.is_ok()); + + // Try to initialize again - should fail + let result2 = init_localization(&locale, temp_dir.path()); + assert!(result2.is_err()); + + match result2 { + Err(LocalizationError::Bundle(msg)) => { + assert!(msg.contains("already initialized")); + } + _ => panic!("Expected Bundle error"), + } + }) + .join() + .unwrap(); + } + + #[test] + fn test_get_message() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("fr-FR").unwrap(); + + init_localization(&locale, temp_dir.path()).unwrap(); + + let message = get_message("greeting"); + assert_eq!(message, "Bonjour, le monde!"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_get_message_not_initialized() { + std::thread::spawn(|| { + let message = get_message("greeting"); + assert_eq!(message, "greeting"); // Should return the ID itself + }) + .join() + .unwrap(); + } + + #[test] + fn test_get_message_with_args() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("en-US").unwrap(); + + init_localization(&locale, temp_dir.path()).unwrap(); + + let mut args = HashMap::new(); + args.insert("name".to_string(), "Bob".to_string()); + + let message = get_message_with_args("welcome", args); + assert_eq!(message, "Welcome, \u{2068}Bob\u{2069}!"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_get_message_with_args_pluralization() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("en-US").unwrap(); + + init_localization(&locale, temp_dir.path()).unwrap(); + + // Test singular + let mut args1 = HashMap::new(); + args1.insert("count".to_string(), "1".to_string()); + let message1 = get_message_with_args("count-items", args1); + assert_eq!(message1, "You have \u{2068}\u{2068}1\u{2069} item\u{2069}"); + + // Test plural + let mut args2 = HashMap::new(); + args2.insert("count".to_string(), "5".to_string()); + let message2 = get_message_with_args("count-items", args2); + assert_eq!(message2, "You have \u{2068}\u{2068}5\u{2069} items\u{2069}"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_detect_system_locale_from_lang_env() { + // Save current LANG value + let original_lang = env::var("LANG").ok(); + + // Test with a valid locale + unsafe { + env::set_var("LANG", "fr-FR.UTF-8"); + } + let result = detect_system_locale(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "fr-FR"); + + // Test with locale without encoding + unsafe { + env::set_var("LANG", "es-ES"); + } + let result = detect_system_locale(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "es-ES"); + + // Restore original LANG value + match original_lang { + Some(val) => unsafe { + env::set_var("LANG", val); + }, + None => unsafe { + env::remove_var("LANG"); + }, + } + } + + #[test] + fn test_detect_system_locale_no_lang_env() { + // Save current LANG value + let original_lang = env::var("LANG").ok(); + + // Remove LANG environment variable + unsafe { + env::remove_var("LANG"); + } + + let result = detect_system_locale(); + assert!(result.is_ok()); + assert_eq!(result.unwrap().to_string(), "en-US"); + + // Restore original LANG value + match original_lang { + Some(val) => unsafe { + env::set_var("LANG", val); + }, + None => {} // Was already unset + } + } + + #[test] + fn test_setup_localization_success() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + + // Save current LANG value + let original_lang = env::var("LANG").ok(); + unsafe { + env::set_var("LANG", "fr-FR.UTF-8"); + } + + let result = setup_localization(temp_dir.path().to_str().unwrap()); + assert!(result.is_ok()); + + // Test that French is loaded + let message = get_message("greeting"); + assert_eq!(message, "Bonjour, le monde!"); + + // Restore original LANG value + match original_lang { + Some(val) => unsafe { + env::set_var("LANG", val); + }, + None => unsafe { + env::remove_var("LANG"); + }, + } + }) + .join() + .unwrap(); + } + + #[test] + fn test_setup_localization_falls_back_to_english() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + + // Save current LANG value + let original_lang = env::var("LANG").ok(); + unsafe { + env::set_var("LANG", "de-DE.UTF-8"); + } // German file doesn't exist + + let result = setup_localization(temp_dir.path().to_str().unwrap()); + assert!(result.is_ok()); + + // Should fall back to English + let message = get_message("greeting"); + assert_eq!(message, "Hello, world!"); + + // Restore original LANG value + match original_lang { + Some(val) => unsafe { + env::set_var("LANG", val); + }, + None => unsafe { + env::remove_var("LANG"); + }, + } + }) + .join() + .unwrap(); + } + + #[test] + fn test_setup_localization_missing_english_file() { + std::thread::spawn(|| { + let temp_dir = TempDir::new().unwrap(); // Empty directory + + let result = setup_localization(temp_dir.path().to_str().unwrap()); + assert!(result.is_err()); + + match result { + Err(LocalizationError::Io { source: _, path }) => { + assert!(path.to_string_lossy().contains("en-US.ftl")); + } + _ => panic!("Expected IO error for missing English file"), + } + }) + .join() + .unwrap(); + } + + #[test] + fn test_thread_local_isolation() { + use std::thread; + + let temp_dir = create_test_locales_dir(); + + // Initialize in main thread with French + let temp_path_main = temp_dir.path().to_path_buf(); + let main_handle = thread::spawn(move || { + let locale = LanguageIdentifier::from_str("fr-FR").unwrap(); + init_localization(&locale, &temp_path_main).unwrap(); + let main_message = get_message("greeting"); + assert_eq!(main_message, "Bonjour, le monde!"); + }); + main_handle.join().unwrap(); + + // Test in a different thread - should not be initialized + let temp_path = temp_dir.path().to_path_buf(); + let handle = thread::spawn(move || { + // This thread should have its own uninitialized LOCALIZER + let thread_message = get_message("greeting"); + assert_eq!(thread_message, "greeting"); // Returns ID since not initialized + + // Initialize in this thread with English + let en_locale = LanguageIdentifier::from_str("en-US").unwrap(); + init_localization(&en_locale, &temp_path).unwrap(); + let thread_message_after_init = get_message("greeting"); + assert_eq!(thread_message_after_init, "Hello, world!"); + }); + + handle.join().unwrap(); + + // Test another thread to verify French doesn't persist across threads + let final_handle = thread::spawn(move || { + // Should be uninitialized again + let final_message = get_message("greeting"); + assert_eq!(final_message, "greeting"); + }); + final_handle.join().unwrap(); + } + + #[test] + fn test_japanese_localization() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ja-JP").unwrap(); + + let result = init_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Test Japanese greeting + let message = get_message("greeting"); + assert_eq!(message, "こんにちは、世界!"); + + // Test Japanese with arguments + let mut args = HashMap::new(); + args.insert("name".to_string(), "田中".to_string()); + let welcome = get_message_with_args("welcome", args); + assert_eq!(welcome, "ようこそ、\u{2068}田中\u{2069}さん!"); + + // Test Japanese count (no pluralization) + let mut count_args = HashMap::new(); + count_args.insert("count".to_string(), "5".to_string()); + let count_message = get_message_with_args("count-items", count_args); + assert_eq!(count_message, "\u{2068}5\u{2069}個のアイテムがあります"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_arabic_localization() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); + + let result = init_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Test Arabic greeting (RTL text) + let message = get_message("greeting"); + assert_eq!(message, "أهلاً بالعالم!"); + + // Test Arabic with arguments + let mut args = HashMap::new(); + args.insert("name".to_string(), "أحمد".to_string()); + let welcome = get_message_with_args("welcome", args); + assert_eq!(welcome, "أهلاً وسهلاً، \u{2068}أحمد\u{2069}!"); + + // Test Arabic pluralization (zero case) + let mut args_zero = HashMap::new(); + args_zero.insert("count".to_string(), "0".to_string()); + let message_zero = get_message_with_args("count-items", args_zero); + assert_eq!(message_zero, "لديك \u{2068}لا عناصر\u{2069}"); + + // Test Arabic pluralization (one case) + let mut args_one = HashMap::new(); + args_one.insert("count".to_string(), "1".to_string()); + let message_one = get_message_with_args("count-items", args_one); + assert_eq!(message_one, "لديك \u{2068}عنصر واحد\u{2069}"); + + // Test Arabic pluralization (two case) + let mut args_two = HashMap::new(); + args_two.insert("count".to_string(), "2".to_string()); + let message_two = get_message_with_args("count-items", args_two); + assert_eq!(message_two, "لديك \u{2068}عنصران\u{2069}"); + + // Test Arabic pluralization (few case - 3-10) + let mut args_few = HashMap::new(); + args_few.insert("count".to_string(), "5".to_string()); + let message_few = get_message_with_args("count-items", args_few); + assert_eq!(message_few, "لديك \u{2068}\u{2068}5\u{2069} عناصر\u{2069}"); + + // Test Arabic pluralization (other case - 11+) + let mut args_many = HashMap::new(); + args_many.insert("count".to_string(), "15".to_string()); + let message_many = get_message_with_args("count-items", args_many); + assert_eq!(message_many, "لديك \u{2068}\u{2068}15\u{2069} عنصر\u{2069}"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_mixed_script_fallback() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); + + let result = init_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Test Arabic message exists + let arabic_message = get_message("greeting"); + assert_eq!(arabic_message, "أهلاً بالعالم!"); + + // Test fallback to English for missing message + let fallback_message = get_message("missing-in-other"); + assert_eq!(fallback_message, "This message only exists in English"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_unicode_directional_isolation() { + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); + + init_localization(&locale, temp_dir.path()).unwrap(); + + // Test that Latin script names are properly isolated in RTL context + let mut args = HashMap::new(); + args.insert("name".to_string(), "John Smith".to_string()); + let message = get_message_with_args("welcome", args); + + // The Latin name should be wrapped in directional isolate characters + assert!(message.contains("\u{2068}John Smith\u{2069}")); + assert_eq!(message, "أهلاً وسهلاً، \u{2068}John Smith\u{2069}!"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_error_display() { + let io_error = LocalizationError::Io { + source: std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"), + path: PathBuf::from("/test/path.ftl"), + }; + let error_string = format!("{}", io_error); + assert!(error_string.contains("I/O error loading")); + assert!(error_string.contains("/test/path.ftl")); + + let parse_error = LocalizationError::Parse("Syntax error".to_string()); + let parse_string = format!("{}", parse_error); + assert!(parse_string.contains("Parse error: Syntax error")); + + let bundle_error = LocalizationError::Bundle("Bundle creation failed".to_string()); + let bundle_string = format!("{}", bundle_error); + assert!(bundle_string.contains("Bundle error: Bundle creation failed")); + } +} From 075cdcf21f02bbd42dab9e3c05c370fb8923ce6a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 May 2025 19:29:44 +0200 Subject: [PATCH 054/139] CI: also install the spanish locale --- .github/workflows/GnuTests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index b12dbb235aa..fc40fc91f5f 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -162,6 +162,7 @@ jobs: sudo locale-gen sudo locale-gen --keep-existing fr_FR sudo locale-gen --keep-existing fr_FR.UTF-8 + sudo locale-gen --keep-existing es_ES.UTF-8 sudo locale-gen --keep-existing sv_SE sudo locale-gen --keep-existing sv_SE.UTF-8 sudo locale-gen --keep-existing en_US From 7847894acf7f49f3b38394d5a77e23be72f4ff44 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 May 2025 20:54:17 +0200 Subject: [PATCH 055/139] arch: support translations --- src/uu/arch/arch.md | 11 ----------- src/uu/arch/locales/en-US.ftl | 5 +++++ src/uu/arch/locales/fr-FR.ftl | 5 +++++ src/uu/arch/src/arch.rs | 13 ++++++------- 4 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 src/uu/arch/arch.md create mode 100644 src/uu/arch/locales/en-US.ftl create mode 100644 src/uu/arch/locales/fr-FR.ftl diff --git a/src/uu/arch/arch.md b/src/uu/arch/arch.md deleted file mode 100644 index a4ba2e75fce..00000000000 --- a/src/uu/arch/arch.md +++ /dev/null @@ -1,11 +0,0 @@ -# arch - -``` -arch -``` - -Display machine architecture - -## After Help - -Determine architecture name for current machine. diff --git a/src/uu/arch/locales/en-US.ftl b/src/uu/arch/locales/en-US.ftl new file mode 100644 index 00000000000..1646e50030d --- /dev/null +++ b/src/uu/arch/locales/en-US.ftl @@ -0,0 +1,5 @@ +# Error message when system architecture information cannot be retrieved +cannot-get-system = cannot get system name + +arch-about = Display machine architecture +arch-after-help = Determine architecture name for current machine. diff --git a/src/uu/arch/locales/fr-FR.ftl b/src/uu/arch/locales/fr-FR.ftl new file mode 100644 index 00000000000..f08462a87b4 --- /dev/null +++ b/src/uu/arch/locales/fr-FR.ftl @@ -0,0 +1,5 @@ +# Error message when system architecture information cannot be retrieved +cannot-get-system = impossible d'obtenir le nom du système + +arch-about = Afficher l'architecture de la machine +arch-after-help = Déterminer le nom de l'architecture pour la machine actuelle. diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 590def48fb9..82e9bb79e5c 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -7,16 +7,15 @@ use platform_info::*; use clap::Command; use uucore::error::{UResult, USimpleError}; -use uucore::{help_about, help_section}; - -static ABOUT: &str = help_about!("arch.md"); -static SUMMARY: &str = help_section!("after help", "arch.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { + locale::setup_localization(uucore::util_name())?; uu_app().try_get_matches_from(args)?; - let uts = PlatformInfo::new().map_err(|_e| USimpleError::new(1, "cannot get system name"))?; + let uts = + PlatformInfo::new().map_err(|_e| USimpleError::new(1, get_message("cannot-get-system")))?; println!("{}", uts.machine().to_string_lossy().trim()); Ok(()) @@ -25,7 +24,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(SUMMARY) + .about(get_message("arch-about")) + .after_help(get_message("arch-after-help")) .infer_long_args(true) } From 726481c8623ff7c813649be9b83e7f6ecba8cfd2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 May 2025 21:59:28 +0200 Subject: [PATCH 056/139] cspell: ignore the ftl files --- .vscode/cSpell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index 01e192d59ba..199830c2d1d 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -26,7 +26,8 @@ "tests/**/fixtures/**", "src/uu/dd/test-resources/**", "vendor/**", - "**/*.svg" + "**/*.svg", + "src/uu/*/locales/*.ftl" ], "enableGlobDot": true, From 91a620969920f3bd769082bf5461f86c883b9f40 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 26 May 2025 21:40:57 +0200 Subject: [PATCH 057/139] locale: try to guess where the locale files are --- src/uucore/src/lib/mods/locale.rs | 73 ++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 142ba962f40..858c0508922 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -25,6 +25,10 @@ pub enum LocalizationError { Parse(String), #[error("Bundle error: {0}")] Bundle(String), + #[error("Locales directory not found: {0}")] + LocalesDirNotFound(String), + #[error("Path resolution error: {0}")] + PathResolution(String), } impl From for LocalizationError { @@ -295,16 +299,71 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> { LanguageIdentifier::from_str(DEFAULT_LOCALE).expect("Default locale should always be valid") }); - let coreutils_path = PathBuf::from(format!("src/uu/{p}/locales/")); - let locales_dir = if coreutils_path.exists() { - coreutils_path - } else { - PathBuf::from(p) - }; - + let locales_dir = get_locales_dir(p)?; init_localization(&locale, &locales_dir) } +/// Helper function to get the locales directory based on the build configuration +fn get_locales_dir(p: &str) -> Result { + #[cfg(debug_assertions)] + { + // During development, use the project's locales directory + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + // from uucore path, load the locales directory from the program directory + let dev_path = PathBuf::from(manifest_dir) + .join("../uu") + .join(p) + .join("locales"); + + if dev_path.exists() { + return Ok(dev_path); + } + + // Fallback for development if the expected path doesn't exist + let fallback_dev_path = PathBuf::from(manifest_dir).join(p); + if fallback_dev_path.exists() { + return Ok(fallback_dev_path); + } + + Err(LocalizationError::LocalesDirNotFound(format!( + "Development locales directory not found at {} or {}", + dev_path.display(), + fallback_dev_path.display() + ))) + } + + #[cfg(not(debug_assertions))] + { + use std::env; + // In release builds, look relative to executable + let exe_path = env::current_exe().map_err(|e| { + LocalizationError::PathResolution(format!("Failed to get executable path: {}", e)) + })?; + + let exe_dir = exe_path.parent().ok_or_else(|| { + LocalizationError::PathResolution("Failed to get executable directory".to_string()) + })?; + + // Try the coreutils-style path first + let coreutils_path = exe_dir.join("locales").join(p); + if coreutils_path.exists() { + return Ok(coreutils_path); + } + + // Fallback to just the parameter as a relative path + let fallback_path = exe_dir.join(p); + if fallback_path.exists() { + return Ok(fallback_path); + } + + return Err(LocalizationError::LocalesDirNotFound(format!( + "Release locales directory not found at {} or {}", + coreutils_path.display(), + fallback_path.display() + ))); + } +} + #[cfg(test)] mod tests { use super::*; From 1e33e535ce4383456b952f372547d5562b42cf5c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 27 May 2025 00:24:31 +0200 Subject: [PATCH 058/139] also install the locales --- GNUmakefile | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index a80b10ec8b2..dbcdcd0ce08 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -5,6 +5,7 @@ PROFILE ?= debug MULTICALL ?= n COMPLETIONS ?= y MANPAGES ?= y +LOCALES ?= y INSTALL ?= install ifneq (,$(filter install, $(MAKECMDGOALS))) override PROFILE:=release @@ -300,7 +301,7 @@ else endif endif -build-coreutils: +build-coreutils: locales ${CARGO} build ${CARGOFLAGS} --features "${EXES} $(BUILD_SPEC_FEATURE)" ${PROFILE_CMD} --no-default-features build: build-coreutils build-pkgs @@ -396,7 +397,32 @@ else install-completions: endif -install: build install-manpages install-completions +ifeq ($(LOCALES),y) +locales: + $(foreach prog, $(INSTALLEES), \ + if [ -d "$(BASEDIR)/src/uu/$(prog)/locales" ]; then \ + mkdir -p "$(BUILDDIR)/locales/$(prog)"; \ + for locale_file in "$(BASEDIR)"/src/uu/$(prog)/locales/*.ftl; do \ + $(INSTALL) -v "$$locale_file" "$(BUILDDIR)/locales/$(prog)/"; \ + done; \ + fi $(newline) \ + ) + + +install-locales: + $(foreach prog, $(INSTALLEES), \ + if [ -d "$(BASEDIR)/src/uu/$(prog)/locales" ]; then \ + mkdir -p "$(DESTDIR)$(DATAROOTDIR)/locales/$(prog)"; \ + for locale_file in "$(BASEDIR)"/src/uu/$(prog)/locales/*.ftl; do \ + $(INSTALL) -v "$$locale_file" "$(DESTDIR)$(DATAROOTDIR)/locales/$(prog)/"; \ + done; \ + fi $(newline) \ + ) +else +install-locales: +endif + +install: build install-manpages install-completions install-locales mkdir -p $(INSTALLDIR_BIN) ifeq (${MULTICALL}, y) $(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils From 508e791f5613fcea041f657c32454e856e30f61c Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Wed, 28 May 2025 19:27:40 -0400 Subject: [PATCH 059/139] sync: adjust safety comments Removes a stale safety comment I missed, plus adds some backticks. --- src/uu/sync/src/sync.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 1ae3d623014..b0221d3d764 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -78,7 +78,7 @@ mod platform { use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED; fn get_last_error() -> u32 { - // SAFETY: GetLastError has no safety preconditions + // SAFETY: `GetLastError` has no safety preconditions unsafe { GetLastError() as u32 } } @@ -122,8 +122,6 @@ mod platform { Ok((String::from_wide_null(&name), handle)) } - /// # Safety - /// This function is unsafe because it calls an unsafe function. fn find_all_volumes() -> UResult> { let (first_volume, next_volume_handle) = find_first_volume()?; let mut volumes = vec![first_volume]; @@ -136,7 +134,7 @@ mod platform { { return match get_last_error() { ERROR_NO_MORE_FILES => { - // SAFETY: next_volume_handle was returned by `find_first_volume` + // SAFETY: `next_volume_handle` was returned by `find_first_volume` unsafe { FindVolumeClose(next_volume_handle) }; Ok(volumes) } From 0ef7c205d0b1f5545fda7de06051fea8879f036d Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 29 May 2025 09:11:03 +0200 Subject: [PATCH 060/139] .pre-commit-config.yaml: Do not fail cspell if all files are ignored cspell pre-commit hook would fail if the only change was a Cargo.lock change. This fixes it. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53e879d09a5..eccff578b05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,6 @@ repos: - id: cspell name: Code spell checker (cspell) description: Run cspell to check for spelling errors. - entry: cspell -- + entry: cspell --no-must-find-files -- pass_filenames: true language: system From 4a0c2a95e804a8213f3d73cf664baf03a43fe157 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 29 May 2025 12:07:01 +0200 Subject: [PATCH 061/139] uucore: num_parser: Update pow_with_context This is the latest version in https://github.com/akubera/bigdecimal-rs/pull/148 , but the discussion sort of stalled, this is really complicated math, but this new function is a little bit better than the original (at least I hope so). --- .../src/lib/features/parser/num_parser.rs | 88 +++++++++++++------ 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index 3ee07e41357..efd3ba0ede5 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -5,7 +5,9 @@ //! Utilities for parsing numbers in various formats -// spell-checker:ignore powf copysign prec inity infinit infs bigdecimal extendedbigdecimal biguint underflowed +// spell-checker:ignore powf copysign prec ilog inity infinit infs bigdecimal extendedbigdecimal biguint underflowed muls + +use std::num::NonZeroU64; use bigdecimal::{ BigDecimal, Context, @@ -375,30 +377,66 @@ fn make_error<'a>(overflow: bool, negative: bool) -> ExtendedParserError<'a, Ext } } -/// Compute bd**exp using exponentiation by squaring algorithm, while maintaining the -/// precision specified in ctx (the number of digits would otherwise explode). -// TODO: We do lose a little bit of precision, and the last digits may not be correct. -// TODO: Upstream this to bigdecimal-rs. -fn pow_with_context(bd: BigDecimal, exp: u32, ctx: &bigdecimal::Context) -> BigDecimal { +// Compute bd**exp using exponentiation by squaring algorithm, while maintaining the +// precision specified in ctx (the number of digits would otherwise explode). +// +// Algorithm comes from https://en.wikipedia.org/wiki/Exponentiation_by_squaring +// +// TODO: Still pending discussion in https://github.com/akubera/bigdecimal-rs/issues/147, +// we do lose a little bit of precision, and the last digits may not be correct. +fn pow_with_context(bd: &BigDecimal, exp: i64, ctx: &Context) -> BigDecimal { if exp == 0 { return 1.into(); } - fn trim_precision(bd: BigDecimal, ctx: &bigdecimal::Context) -> BigDecimal { - if bd.digits() > ctx.precision().get() { - bd.with_precision_round(ctx.precision(), ctx.rounding_mode()) + // When performing a multiplication between 2 numbers, we may lose up to 2 digits + // of precision. + // "Proof": https://github.com/akubera/bigdecimal-rs/issues/147#issuecomment-2793431202 + const MARGIN_PER_MUL: u64 = 2; + // When doing many multiplication, we still introduce additional errors, add 1 more digit + // per 10 multiplications. + const MUL_PER_MARGIN_EXTRA: u64 = 10; + + fn trim_precision(bd: BigDecimal, ctx: &Context, margin: u64) -> BigDecimal { + let prec = ctx.precision().get() + margin; + if bd.digits() > prec { + bd.with_precision_round(NonZeroU64::new(prec).unwrap(), ctx.rounding_mode()) } else { bd } } - let bd = trim_precision(bd, ctx); - let ret = if exp % 2 == 0 { - pow_with_context(bd.square(), exp / 2, ctx) + // Count the number of multiplications we're going to perform, one per "1" binary digit + // in exp, and the number of times we can divide exp by 2. + let mut n = exp.unsigned_abs(); + // Note: 63 - n.leading_zeros() == n.ilog2, but that's only available in recent Rust versions. + let muls = (n.count_ones() + (63 - n.leading_zeros()) - 1) as u64; + // Note: div_ceil would be nice to use here, but only available in recent Rust versions. + let margin_extra = (muls + MUL_PER_MARGIN_EXTRA / 2) / MUL_PER_MARGIN_EXTRA; + let mut margin = margin_extra + MARGIN_PER_MUL * muls; + + let mut bd_y: BigDecimal = 1.into(); + let mut bd_x = if exp >= 0 { + bd.clone() } else { - &bd * pow_with_context(bd.square(), (exp - 1) / 2, ctx) + bd.inverse_with_context(&ctx.with_precision( + NonZeroU64::new(ctx.precision().get() + margin + MARGIN_PER_MUL).unwrap(), + )) }; - trim_precision(ret, ctx) + + while n > 1 { + if n % 2 == 1 { + bd_y = trim_precision(&bd_x * bd_y, ctx, margin); + margin -= MARGIN_PER_MUL; + n -= 1; + } + bd_x = trim_precision(bd_x.square(), ctx, margin); + margin -= MARGIN_PER_MUL; + n /= 2; + } + debug_assert_eq!(margin, margin_extra); + + trim_precision(bd_x * bd_y, ctx, 0) } // Construct an ExtendedBigDecimal based on parsed data @@ -444,22 +482,20 @@ fn construct_extended_big_decimal<'a>( let bd = BigDecimal::from_bigint(signed_digits, 0) / BigDecimal::from_bigint(BigInt::from(16).pow(scale as u32), 0); - let abs_exponent = exponent.abs(); - // Again, pow "only" supports u32 values. Just overflow/underflow if the value provided - // is > 2**32 or < 2**-32. - if abs_exponent > u32::MAX.into() { - return Err(make_error(exponent.is_positive(), negative)); - } + // pow_with_context "only" supports i64 values. Just overflow/underflow if the value provided + // is > 2**64 or < 2**-64. + let exponent = match exponent.to_i64() { + Some(exp) => exp, + None => { + return Err(make_error(exponent.is_positive(), negative)); + } + }; // Confusingly, exponent is in base 2 for hex floating point numbers. + let base: BigDecimal = 2.into(); // Note: We cannot overflow/underflow BigDecimal here, as we will not be able to reach the // maximum/minimum scale (i64 range). - let base: BigDecimal = if !exponent.is_negative() { - 2.into() - } else { - BigDecimal::from(2).inverse() - }; - let pow2 = pow_with_context(base, abs_exponent.to_u32().unwrap(), &Context::default()); + let pow2 = pow_with_context(&base, exponent, &Context::default()); bd * pow2 } else { From b522f2b9b53fcca95f32d325d476111798c381a0 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 29 May 2025 13:53:27 +0200 Subject: [PATCH 062/139] uucore: Use next_back on PathBuf's DoubleEndedIterator As recommended by clippy, last() needlessly goes through the whole path while next_back() is available. --- src/uucore/src/lib/features/proc_info.rs | 2 +- src/uucore/src/lib/features/tty.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/proc_info.rs b/src/uucore/src/lib/features/proc_info.rs index 7ea54a85a3e..3f5334d1007 100644 --- a/src/uucore/src/lib/features/proc_info.rs +++ b/src/uucore/src/lib/features/proc_info.rs @@ -164,7 +164,7 @@ impl ProcessInformation { let pid = { value .iter() - .last() + .next_back() .ok_or(io::ErrorKind::Other)? .to_str() .ok_or(io::ErrorKind::InvalidData)? diff --git a/src/uucore/src/lib/features/tty.rs b/src/uucore/src/lib/features/tty.rs index 6854ba16449..221ee442d63 100644 --- a/src/uucore/src/lib/features/tty.rs +++ b/src/uucore/src/lib/features/tty.rs @@ -73,7 +73,7 @@ impl TryFrom for Teletype { let f = |prefix: &str| { value .iter() - .last()? + .next_back()? .to_str()? .strip_prefix(prefix)? .parse::() From 7854efc8021d8a66dd93f757a0e16d4c28c8f127 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 29 May 2025 13:35:25 +0200 Subject: [PATCH 063/139] code-quality.yml: Clippy with --all-features and --workspace on ubuntu Without this, some uucore packages are not properly tested. --- .github/workflows/code-quality.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 6323d2d7841..a5bbf42af4d 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -78,7 +78,7 @@ jobs: fail-fast: false matrix: job: - - { os: ubuntu-latest , features: feat_os_unix } + - { os: ubuntu-latest , features: all , workspace: true } - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: @@ -104,6 +104,16 @@ jobs: *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; esac; outputs FAIL_ON_FAULT FAULT_TYPE + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.os }}' in + ubuntu-*) + # selinux headers needed to enable all features + sudo apt-get -y install libselinux1-dev + ;; + esac - name: "`cargo clippy` lint testing" uses: nick-fields/retry@v3 with: @@ -117,7 +127,17 @@ jobs: fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets --features ${{ matrix.job.features }} --tests -pcoreutils -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + if [[ "${{ matrix.job.features }}" == "all" ]]; then + extra="--all-features" + else + extra="--features ${{ matrix.job.features }}" + fi + case '${{ matrix.job.workspace-tests }}' in + 1|t|true|y|yes) + extra="${extra} --workspace" + ;; + esac + S=$(cargo clippy --all-targets $extra --tests -pcoreutils -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi style_spellcheck: From 7f05fca1aebbead5e95ba9b2f431ae24ee8d3e76 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 29 May 2025 13:34:51 +0200 Subject: [PATCH 064/139] .pre-commit-config.yaml: Match config in CI We need to enable -D warnings to get the same behaviour. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53e879d09a5..b840b9ed3b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: - id: rust-clippy name: Rust clippy description: Run cargo clippy on files included in the commit. - entry: cargo +stable clippy --workspace --all-targets --all-features -- + entry: cargo +stable clippy --workspace --all-targets --all-features -- -D warnings pass_filenames: false types: [file, rust] language: system From 301e33cfe301efcd83308d3f0ee7ebb2473bc20b Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Mon, 28 Apr 2025 09:50:51 +0800 Subject: [PATCH 065/139] date: switch from chrono to jiff Also adds cargo dependency. --- Cargo.lock | 51 +++++++++++++++++++ Cargo.toml | 5 ++ fuzz/Cargo.lock | 57 +++++++++++++++++++++ src/uu/date/Cargo.toml | 9 +++- src/uu/date/src/date.rs | 108 ++++++++++++++++++++++------------------ 5 files changed, 180 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de221b82ac8..dc7cf092791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1347,6 +1347,47 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1849,6 +1890,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2818,6 +2868,7 @@ version = "0.1.0" dependencies = [ "chrono", "clap", + "jiff", "libc", "parse_datetime", "uucore", diff --git a/Cargo.toml b/Cargo.toml index 656c7f1ad6b..44dda8c7db3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -304,6 +304,11 @@ hostname = "0.4" iana-time-zone = "0.1.57" indicatif = "0.17.8" itertools = "0.14.0" +jiff = { version = "0.2.10", default-features = false, features = [ + "std", + "alloc", + "tz-system", +] } libc = "0.2.172" linux-raw-sys = "0.9" lscolors = { version = "0.20.0", default-features = false, features = [ diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4495a0a5a60..f7d500c6a5d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -644,6 +644,47 @@ dependencies = [ "either", ] +[[package]] +name = "jiff" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys", +] + +[[package]] +name = "jiff-static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.33" @@ -895,6 +936,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1316,6 +1372,7 @@ version = "0.1.0" dependencies = [ "chrono", "clap", + "jiff", "libc", "parse_datetime", "uucore", diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 087d4befc7e..f498befc79e 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,4 +1,4 @@ -# spell-checker:ignore datetime +# spell-checker:ignore datetime tzdb zoneinfo [package] name = "uu_date" description = "date ~ (uutils) display or set the current time" @@ -19,8 +19,13 @@ workspace = true path = "src/date.rs" [dependencies] -chrono = { workspace = true } clap = { workspace = true } +chrono = { workspace = true } # TODO: Eventually we'll want to remove this +jiff = { workspace = true, features = [ + "tzdb-bundle-platform", + "tzdb-zoneinfo", + "tzdb-concatenated", +] } uucore = { workspace = true, features = ["custom-tz-fmt", "parser"] } parse_datetime = { workspace = true } diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index f4c9313cb62..aff353fee6c 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -3,19 +3,17 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes +// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes -use chrono::format::{Item, StrftimeItems}; -use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc}; -#[cfg(windows)] -use chrono::{Datelike, Timelike}; use clap::{Arg, ArgAction, Command}; +use jiff::fmt::strtime; +use jiff::tz::TimeZone; +use jiff::{SignedDuration, Timestamp, Zoned}; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use libc::{CLOCK_REALTIME, clock_settime, timespec}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; -use uucore::custom_tz_fmt::custom_time_format; use uucore::display::Quotable; use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; @@ -75,7 +73,7 @@ struct Settings { utc: bool, format: Format, date_source: DateSource, - set_to: Option>, + set_to: Option, } /// Various ways of displaying the date @@ -93,7 +91,7 @@ enum DateSource { Custom(String), File(PathBuf), Stdin, - Human(TimeDelta), + Human(SignedDuration), } enum Iso8601Format { @@ -167,9 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - let ref_time = Local::now(); - if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date.as_str()) { - let duration = new_time.signed_duration_since(ref_time); + if let Ok(duration) = parse_offset(date.as_str()) { DateSource::Human(duration) } else { DateSource::Custom(date.into()) @@ -203,39 +199,37 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if let Some(date) = settings.set_to { // All set time functions expect UTC datetimes. - let date: DateTime = if settings.utc { - date.with_timezone(&Utc) + let date = if settings.utc { + date.with_time_zone(TimeZone::UTC) } else { - date.into() + date }; return set_system_datetime(date); } else { // Get the current time, either in the local time zone or UTC. - let now: DateTime = if settings.utc { - let now = Utc::now(); - now.with_timezone(&now.offset().fix()) + let now = if settings.utc { + Timestamp::now().to_zoned(TimeZone::UTC) } else { - let now = Local::now(); - now.with_timezone(now.offset()) + Zoned::now() }; // Iterate over all dates - whether it's a single date or a file. let dates: Box> = match settings.date_source { DateSource::Custom(ref input) => { - let date = parse_date(input.clone()); + let date = parse_date(input); let iter = std::iter::once(date); Box::new(iter) } DateSource::Human(relative_time) => { // Double check the result is overflow or not of the current_time + relative_time // it may cause a panic of chrono::datetime::DateTime add - match now.checked_add_signed(relative_time) { - Some(date) => { + match now.checked_add(relative_time) { + Ok(date) => { let iter = std::iter::once(Ok(date)); Box::new(iter) } - None => { + Err(_) => { return Err(USimpleError::new( 1, format!("invalid date {relative_time}"), @@ -272,23 +266,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Format all the dates for date in dates { match date { - Ok(date) => { - let format_string = custom_time_format(format_string); - // Hack to work around panic in chrono, - // TODO - remove when a fix for https://github.com/chronotope/chrono/issues/623 is released - let format_items = StrftimeItems::new(format_string.as_str()); - if format_items.clone().any(|i| i == Item::Error) { + // TODO: Switch to lenient formatting. + Ok(date) => match strtime::format(format_string, &date) { + Ok(s) => println!("{s}"), + Err(e) => { return Err(USimpleError::new( 1, - format!("invalid format {}", format_string.replace("%f", "%N")), + format!("invalid format {} ({e})", format_string), )); } - let formatted = date - .format_with_items(format_items) - .to_string() - .replace("%f", "%N"); - println!("{formatted}"); - } + }, Err((input, _err)) => show!(USimpleError::new( 1, format!("invalid date {}", input.quote()) @@ -388,13 +375,13 @@ fn make_format_string(settings: &Settings) -> &str { Iso8601Format::Hours => "%FT%H%:z", Iso8601Format::Minutes => "%FT%H:%M%:z", Iso8601Format::Seconds => "%FT%T%:z", - Iso8601Format::Ns => "%FT%T,%f%:z", + Iso8601Format::Ns => "%FT%T,%N%:z", }, Format::Rfc5322 => "%a, %d %h %Y %T %z", Format::Rfc3339(ref fmt) => match *fmt { Rfc3339Format::Date => "%F", Rfc3339Format::Seconds => "%F %T%:z", - Rfc3339Format::Ns => "%F %T.%f%:z", + Rfc3339Format::Ns => "%F %T.%N%:z", }, Format::Custom(ref fmt) => fmt, Format::Default => "%a %b %e %X %Z %Y", @@ -403,19 +390,43 @@ fn make_format_string(settings: &Settings) -> &str { /// Parse a `String` into a `DateTime`. /// If it fails, return a tuple of the `String` along with its `ParseError`. +// TODO: Convert `parse_datetime` to jiff and remove wrapper from chrono to jiff structures. fn parse_date + Clone>( s: S, -) -> Result, (String, parse_datetime::ParseDateTimeError)> { - parse_datetime::parse_datetime(s.as_ref()).map_err(|e| (s.as_ref().into(), e)) +) -> Result { + match parse_datetime::parse_datetime(s.as_ref()) { + Ok(date) => { + let timestamp = + Timestamp::new(date.timestamp(), date.timestamp_subsec_nanos() as i32).unwrap(); + Ok(Zoned::new(timestamp, TimeZone::UTC)) + } + Err(e) => Err((s.as_ref().into(), e)), + } +} + +// TODO: Convert `parse_datetime` to jiff and remove wrapper from chrono to jiff structures. +// Also, consider whether parse_datetime::parse_datetime_at_date can be renamed to something +// like parse_datetime::parse_offset, instead of doing some addition/subtraction. +fn parse_offset(date: &str) -> Result { + let ref_time = chrono::Local::now(); + if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date) { + let duration = new_time.signed_duration_since(ref_time); + Ok(SignedDuration::new( + duration.num_seconds(), + duration.subsec_nanos(), + )) + } else { + Err(()) + } } #[cfg(not(any(unix, windows)))] -fn set_system_datetime(_date: DateTime) -> UResult<()> { +fn set_system_datetime(_date: Zoned) -> UResult<()> { unimplemented!("setting date not implemented (unsupported target)"); } #[cfg(target_os = "macos")] -fn set_system_datetime(_date: DateTime) -> UResult<()> { +fn set_system_datetime(_date: Zoned) -> UResult<()> { Err(USimpleError::new( 1, "setting the date is not supported by macOS".to_string(), @@ -423,7 +434,7 @@ fn set_system_datetime(_date: DateTime) -> UResult<()> { } #[cfg(target_os = "redox")] -fn set_system_datetime(_date: DateTime) -> UResult<()> { +fn set_system_datetime(_date: Zoned) -> UResult<()> { Err(USimpleError::new( 1, "setting the date is not supported by Redox".to_string(), @@ -436,10 +447,11 @@ fn set_system_datetime(_date: DateTime) -> UResult<()> { /// `` /// `` /// `` -fn set_system_datetime(date: DateTime) -> UResult<()> { +fn set_system_datetime(date: Zoned) -> UResult<()> { + let ts = date.timestamp(); let timespec = timespec { - tv_sec: date.timestamp() as _, - tv_nsec: date.timestamp_subsec_nanos() as _, + tv_sec: ts.as_second() as _, + tv_nsec: ts.subsec_nanosecond() as _, }; let result = unsafe { clock_settime(CLOCK_REALTIME, ×pec) }; @@ -456,7 +468,7 @@ fn set_system_datetime(date: DateTime) -> UResult<()> { /// See here for more: /// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime /// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime -fn set_system_datetime(date: DateTime) -> UResult<()> { +fn set_system_datetime(date: Zoned) -> UResult<()> { let system_time = SYSTEMTIME { wYear: date.year() as u16, wMonth: date.month() as u16, @@ -467,7 +479,7 @@ fn set_system_datetime(date: DateTime) -> UResult<()> { wMinute: date.minute() as u16, wSecond: date.second() as u16, // TODO: be careful of leap seconds - valid range is [0, 999] - how to handle? - wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as u16, + wMilliseconds: ((date.subsec_nanosecond() / 1_000_000) % 1000) as u16, }; let result = unsafe { SetSystemTime(&system_time) }; From 07c9205d229d023189e1087e442724fb7ee2a386 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Mon, 28 Apr 2025 20:37:12 +0800 Subject: [PATCH 066/139] Revert "ls: Optimize time formatting" This reverts commit fc6b896c271eed5654418acc267eb21377c55690. This also reverts the change from new to new_lenient, we'll recover that later as part of the jiff conversion. --- src/uu/ls/src/ls.rs | 88 ++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 62 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0297a569cef..5cc6983b2cf 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -27,7 +27,6 @@ use std::{ use std::{collections::HashSet, io::IsTerminal}; use ansi_width::ansi_width; -use chrono::format::{Item, StrftimeItems}; use chrono::{DateTime, Local, TimeDelta}; use clap::{ Arg, ArgAction, Command, @@ -274,64 +273,32 @@ enum TimeStyle { Format(String), } -/// A struct/impl used to format a file DateTime, precomputing the format for performance reasons. -struct TimeStyler { - // default format, always specified. - default: Vec>, - // format for a recent time, only specified it is is different from the default - recent: Option>>, - // If `recent` is set, cache the threshold time when we switch from recent to default format. - recent_time_threshold: Option>, -} - -impl TimeStyler { - /// Create a TimeStyler based on a TimeStyle specification. - fn new(style: &TimeStyle) -> TimeStyler { - let default: Vec> = match style { - TimeStyle::FullIso => StrftimeItems::new("%Y-%m-%d %H:%M:%S.%f %z").parse(), - TimeStyle::LongIso => StrftimeItems::new("%Y-%m-%d %H:%M").parse(), - TimeStyle::Iso => StrftimeItems::new("%Y-%m-%d ").parse(), - // In this version of chrono translating can be done - // The function is chrono::datetime::DateTime::format_localized - // However it's currently still hard to get the current pure-rust-locale - // So it's not yet implemented - TimeStyle::Locale => StrftimeItems::new("%b %e %Y").parse(), - TimeStyle::Format(fmt) => { - StrftimeItems::new_lenient(custom_tz_fmt::custom_time_format(fmt).as_str()) - .parse_to_owned() - } - } - .unwrap(); - let recent = match style { - TimeStyle::Iso => Some(StrftimeItems::new("%m-%d %H:%M")), - // See comment above about locale - TimeStyle::Locale => Some(StrftimeItems::new("%b %e %H:%M")), - _ => None, - } - .map(|x| x.collect()); - let recent_time_threshold = if recent.is_some() { - // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. - Some(Local::now() - TimeDelta::try_seconds(31_556_952 / 2).unwrap()) - } else { - None - }; - - TimeStyler { - default, - recent, - recent_time_threshold, - } - } +/// Whether the given date is considered recent (i.e., in the last 6 months). +fn is_recent(time: DateTime) -> bool { + // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. + time + TimeDelta::try_seconds(31_556_952 / 2).unwrap() > Local::now() +} - /// Format a DateTime, using `recent` format if available, and the DateTime - /// is recent enough. +impl TimeStyle { + /// Format the given time according to this time format style. fn format(&self, time: DateTime) -> String { - if self.recent.is_none() || time <= self.recent_time_threshold.unwrap() { - time.format_with_items(self.default.iter()) - } else { - time.format_with_items(self.recent.as_ref().unwrap().iter()) + let recent = is_recent(time); + match (self, recent) { + (Self::FullIso, _) => time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string(), + (Self::LongIso, _) => time.format("%Y-%m-%d %H:%M").to_string(), + (Self::Iso, true) => time.format("%m-%d %H:%M").to_string(), + (Self::Iso, false) => time.format("%Y-%m-%d ").to_string(), + // spell-checker:ignore (word) datetime + //In this version of chrono translating can be done + //The function is chrono::datetime::DateTime::format_localized + //However it's currently still hard to get the current pure-rust-locale + //So it's not yet implemented + (Self::Locale, true) => time.format("%b %e %H:%M").to_string(), + (Self::Locale, false) => time.format("%b %e %Y").to_string(), + (Self::Format(fmt), _) => time + .format(custom_tz_fmt::custom_time_format(fmt).as_str()) + .to_string(), } - .to_string() } } @@ -2093,8 +2060,6 @@ struct ListState<'a> { uid_cache: HashMap, #[cfg(unix)] gid_cache: HashMap, - - time_styler: TimeStyler, } #[allow(clippy::cognitive_complexity)] @@ -2111,7 +2076,6 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { uid_cache: HashMap::new(), #[cfg(unix)] gid_cache: HashMap::new(), - time_styler: TimeStyler::new(&config.time_style), }; for loc in locs { @@ -2907,7 +2871,7 @@ fn display_item_long( }; output_display.extend(b" "); - output_display.extend(display_date(md, config, state).as_bytes()); + output_display.extend(display_date(md, config).as_bytes()); output_display.extend(b" "); let item_name = display_item_name( @@ -3111,9 +3075,9 @@ fn get_time(md: &Metadata, config: &Config) -> Option> { Some(time.into()) } -fn display_date(metadata: &Metadata, config: &Config, state: &mut ListState) -> String { +fn display_date(metadata: &Metadata, config: &Config) -> String { match get_time(metadata, config) { - Some(time) => state.time_styler.format(time), + Some(time) => config.time_style.format(time), None => "???".into(), } } From fc947eca339b74007ecadf1e72a27a61e4a51d39 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Mon, 28 Apr 2025 21:09:52 +0800 Subject: [PATCH 067/139] ls: convert to jiff --- Cargo.lock | 1 + src/uu/ls/Cargo.toml | 7 +++++++ src/uu/ls/src/ls.rs | 34 +++++++++++++++------------------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc7cf092791..427a8166c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3141,6 +3141,7 @@ dependencies = [ "clap", "glob", "hostname", + "jiff", "lscolors", "number_prefix", "selinux", diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index ff00175e747..83a2f4fa53b 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,3 +1,5 @@ +# spell-checker:ignore tzdb zoneinfo + [package] name = "uu_ls" description = "ls ~ (uutils) display directory contents" @@ -23,6 +25,11 @@ chrono = { workspace = true } clap = { workspace = true, features = ["env"] } glob = { workspace = true } hostname = { workspace = true } +jiff = { workspace = true, features = [ + "tzdb-bundle-platform", + "tzdb-zoneinfo", + "tzdb-concatenated", +] } lscolors = { workspace = true } number_prefix = { workspace = true } selinux = { workspace = true, optional = true } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 5cc6983b2cf..3214b4910da 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -16,23 +16,22 @@ use std::{ fs::{self, DirEntry, FileType, Metadata, ReadDir}, io::{BufWriter, ErrorKind, Stdout, Write, stdout}, path::{Path, PathBuf}, - time::{SystemTime, UNIX_EPOCH}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; #[cfg(unix)] use std::{ collections::HashMap, os::unix::fs::{FileTypeExt, MetadataExt}, - time::Duration, }; use std::{collections::HashSet, io::IsTerminal}; use ansi_width::ansi_width; -use chrono::{DateTime, Local, TimeDelta}; use clap::{ Arg, ArgAction, Command, builder::{NonEmptyStringValueParser, PossibleValue, ValueParser}, }; use glob::{MatchOptions, Pattern}; +use jiff::{Timestamp, Zoned}; use lscolors::LsColors; use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB}; use thiserror::Error; @@ -58,7 +57,6 @@ use uucore::libc::{dev_t, major, minor}; use uucore::line_ending::LineEnding; use uucore::quoting_style::{self, QuotingStyle, escape_name}; use uucore::{ - custom_tz_fmt, display::Quotable, error::{UError, UResult, set_exit_code}, format_usage, @@ -274,30 +272,28 @@ enum TimeStyle { } /// Whether the given date is considered recent (i.e., in the last 6 months). -fn is_recent(time: DateTime) -> bool { +fn is_recent(time: Timestamp) -> bool { // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. - time + TimeDelta::try_seconds(31_556_952 / 2).unwrap() > Local::now() + time + Duration::new(31_556_952 / 2, 0) > Timestamp::now() } impl TimeStyle { /// Format the given time according to this time format style. - fn format(&self, time: DateTime) -> String { - let recent = is_recent(time); + fn format(&self, date: Zoned) -> String { + let recent = is_recent(date.timestamp()); match (self, recent) { - (Self::FullIso, _) => time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string(), - (Self::LongIso, _) => time.format("%Y-%m-%d %H:%M").to_string(), - (Self::Iso, true) => time.format("%m-%d %H:%M").to_string(), - (Self::Iso, false) => time.format("%Y-%m-%d ").to_string(), + (Self::FullIso, _) => date.strftime("%Y-%m-%d %H:%M:%S.%f %z").to_string(), + (Self::LongIso, _) => date.strftime("%Y-%m-%d %H:%M").to_string(), + (Self::Iso, true) => date.strftime("%m-%d %H:%M").to_string(), + (Self::Iso, false) => date.strftime("%Y-%m-%d ").to_string(), // spell-checker:ignore (word) datetime //In this version of chrono translating can be done //The function is chrono::datetime::DateTime::format_localized //However it's currently still hard to get the current pure-rust-locale //So it's not yet implemented - (Self::Locale, true) => time.format("%b %e %H:%M").to_string(), - (Self::Locale, false) => time.format("%b %e %Y").to_string(), - (Self::Format(fmt), _) => time - .format(custom_tz_fmt::custom_time_format(fmt).as_str()) - .to_string(), + (Self::Locale, true) => date.strftime("%b %e %H:%M").to_string(), + (Self::Locale, false) => date.strftime("%b %e %Y").to_string(), + (Self::Format(fmt), _) => date.strftime(&fmt).to_string(), } } } @@ -3070,9 +3066,9 @@ fn get_system_time(md: &Metadata, config: &Config) -> Option { } } -fn get_time(md: &Metadata, config: &Config) -> Option> { +fn get_time(md: &Metadata, config: &Config) -> Option { let time = get_system_time(md, config)?; - Some(time.into()) + time.try_into().ok() } fn display_date(metadata: &Metadata, config: &Config) -> String { From c599363242341a247edaa09f6af43ec65eaf5ff7 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Wed, 30 Apr 2025 00:29:23 +0800 Subject: [PATCH 068/139] ls: cache recent time threshold in jiff implementation --- src/uu/ls/src/ls.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3214b4910da..e092ea85fe5 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -272,15 +272,15 @@ enum TimeStyle { } /// Whether the given date is considered recent (i.e., in the last 6 months). -fn is_recent(time: Timestamp) -> bool { +fn is_recent(time: Timestamp, state: &mut ListState) -> bool { // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. - time + Duration::new(31_556_952 / 2, 0) > Timestamp::now() + time > state.recent_time_threshold } impl TimeStyle { /// Format the given time according to this time format style. - fn format(&self, date: Zoned) -> String { - let recent = is_recent(date.timestamp()); + fn format(&self, date: Zoned, state: &mut ListState) -> String { + let recent = is_recent(date.timestamp(), state); match (self, recent) { (Self::FullIso, _) => date.strftime("%Y-%m-%d %H:%M:%S.%f %z").to_string(), (Self::LongIso, _) => date.strftime("%Y-%m-%d %H:%M").to_string(), @@ -2056,6 +2056,7 @@ struct ListState<'a> { uid_cache: HashMap, #[cfg(unix)] gid_cache: HashMap, + recent_time_threshold: Timestamp, } #[allow(clippy::cognitive_complexity)] @@ -2072,6 +2073,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { uid_cache: HashMap::new(), #[cfg(unix)] gid_cache: HashMap::new(), + recent_time_threshold: Timestamp::now() - Duration::new(31_556_952 / 2, 0), }; for loc in locs { @@ -2867,7 +2869,7 @@ fn display_item_long( }; output_display.extend(b" "); - output_display.extend(display_date(md, config).as_bytes()); + output_display.extend(display_date(md, config, state).as_bytes()); output_display.extend(b" "); let item_name = display_item_name( @@ -3071,9 +3073,9 @@ fn get_time(md: &Metadata, config: &Config) -> Option { time.try_into().ok() } -fn display_date(metadata: &Metadata, config: &Config) -> String { +fn display_date(metadata: &Metadata, config: &Config, state: &mut ListState) -> String { match get_time(metadata, config) { - Some(time) => config.time_style.format(time), + Some(time) => config.time_style.format(time, state), None => "???".into(), } } From 10fb220c72739591015d872e761c0ae449693dc3 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Wed, 30 Apr 2025 00:56:22 +0800 Subject: [PATCH 069/139] ls: Avoid additional String creation/copy in display_date From code provided in #7852 by @BurntSushi. Depending on the benchmarks, there is _still_ a small performance difference (~4%) vs main, but it's seen mostly on small trees getting printed repeatedly, which is probably not a terribly interesting use case. --- src/uu/ls/src/ls.rs | 48 +++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index e092ea85fe5..e4a5e00f83d 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly nohash +// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly nohash strtime use std::iter; #[cfg(windows)] @@ -31,6 +31,8 @@ use clap::{ builder::{NonEmptyStringValueParser, PossibleValue, ValueParser}, }; use glob::{MatchOptions, Pattern}; +use jiff::fmt::StdIoWrite; +use jiff::fmt::strtime::BrokenDownTime; use jiff::{Timestamp, Zoned}; use lscolors::LsColors; use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB}; @@ -279,21 +281,29 @@ fn is_recent(time: Timestamp, state: &mut ListState) -> bool { impl TimeStyle { /// Format the given time according to this time format style. - fn format(&self, date: Zoned, state: &mut ListState) -> String { + fn format( + &self, + date: Zoned, + out: &mut Vec, + state: &mut ListState, + ) -> Result<(), jiff::Error> { let recent = is_recent(date.timestamp(), state); + let tm = BrokenDownTime::from(&date); + let out = StdIoWrite(out); + match (self, recent) { - (Self::FullIso, _) => date.strftime("%Y-%m-%d %H:%M:%S.%f %z").to_string(), - (Self::LongIso, _) => date.strftime("%Y-%m-%d %H:%M").to_string(), - (Self::Iso, true) => date.strftime("%m-%d %H:%M").to_string(), - (Self::Iso, false) => date.strftime("%Y-%m-%d ").to_string(), + (Self::FullIso, _) => tm.format("%Y-%m-%d %H:%M:%S.%f %z", out), + (Self::LongIso, _) => tm.format("%Y-%m-%d %H:%M", out), + (Self::Iso, true) => tm.format("%m-%d %H:%M", out), + (Self::Iso, false) => tm.format("%Y-%m-%d ", out), // spell-checker:ignore (word) datetime //In this version of chrono translating can be done //The function is chrono::datetime::DateTime::format_localized //However it's currently still hard to get the current pure-rust-locale //So it's not yet implemented - (Self::Locale, true) => date.strftime("%b %e %H:%M").to_string(), - (Self::Locale, false) => date.strftime("%b %e %Y").to_string(), - (Self::Format(fmt), _) => date.strftime(&fmt).to_string(), + (Self::Locale, true) => tm.format("%b %e %H:%M", out), + (Self::Locale, false) => tm.format("%b %e %Y", out), + (Self::Format(fmt), _) => tm.format(&fmt, out), } } } @@ -2869,7 +2879,7 @@ fn display_item_long( }; output_display.extend(b" "); - output_display.extend(display_date(md, config, state).as_bytes()); + display_date(md, config, state, &mut output_display)?; output_display.extend(b" "); let item_name = display_item_name( @@ -3073,10 +3083,22 @@ fn get_time(md: &Metadata, config: &Config) -> Option { time.try_into().ok() } -fn display_date(metadata: &Metadata, config: &Config, state: &mut ListState) -> String { +fn display_date( + metadata: &Metadata, + config: &Config, + state: &mut ListState, + out: &mut Vec, +) -> UResult<()> { match get_time(metadata, config) { - Some(time) => config.time_style.format(time, state), - None => "???".into(), + // TODO: Some fancier error conversion might be nice. + Some(time) => config + .time_style + .format(time, out, state) + .map_err(|x| USimpleError::new(1, x.to_string())), + None => { + out.extend(b"???"); + Ok(()) + } } } From 6031de5a2915ee3287823167d2d928f1dd51aa0e Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 1 May 2025 10:58:38 +0800 Subject: [PATCH 070/139] ls: switch to lenient formating configuration --- src/uu/ls/src/ls.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index e4a5e00f83d..df55e10f107 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -289,21 +289,24 @@ impl TimeStyle { ) -> Result<(), jiff::Error> { let recent = is_recent(date.timestamp(), state); let tm = BrokenDownTime::from(&date); - let out = StdIoWrite(out); + let mut out = StdIoWrite(out); + let config = jiff::fmt::strtime::Config::new().lenient(true); match (self, recent) { - (Self::FullIso, _) => tm.format("%Y-%m-%d %H:%M:%S.%f %z", out), - (Self::LongIso, _) => tm.format("%Y-%m-%d %H:%M", out), - (Self::Iso, true) => tm.format("%m-%d %H:%M", out), - (Self::Iso, false) => tm.format("%Y-%m-%d ", out), + (Self::FullIso, _) => { + tm.format_with_config(&config, "%Y-%m-%d %H:%M:%S.%f %z", &mut out) + } + (Self::LongIso, _) => tm.format_with_config(&config, "%Y-%m-%d %H:%M", &mut out), + (Self::Iso, true) => tm.format_with_config(&config, "%m-%d %H:%M", &mut out), + (Self::Iso, false) => tm.format_with_config(&config, "%Y-%m-%d ", &mut out), // spell-checker:ignore (word) datetime //In this version of chrono translating can be done //The function is chrono::datetime::DateTime::format_localized //However it's currently still hard to get the current pure-rust-locale //So it's not yet implemented - (Self::Locale, true) => tm.format("%b %e %H:%M", out), - (Self::Locale, false) => tm.format("%b %e %Y", out), - (Self::Format(fmt), _) => tm.format(&fmt, out), + (Self::Locale, true) => tm.format_with_config(&config, "%b %e %H:%M", &mut out), + (Self::Locale, false) => tm.format_with_config(&config, "%b %e %Y", &mut out), + (Self::Format(fmt), _) => tm.format_with_config(&config, fmt, &mut out), } } } From d1525e2d2ea6af1707d0552c560469389331ed40 Mon Sep 17 00:00:00 2001 From: Jadi Date: Thu, 27 Mar 2025 17:01:43 -0700 Subject: [PATCH 071/139] date: Add more TZ tests [drinkcat: separated test changes] --- tests/by-util/test_date.rs | 110 +++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 09cf7ac790e..e5dda70bb3b 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -572,3 +572,113 @@ fn test_date_empty_tz() { .succeeds() .stdout_only("UTC\n"); } + +#[test] +fn test_date_tz_utc() { + new_ucmd!() + .env("TZ", "UTC0") + .arg("+%Z") + .succeeds() + .stdout_only("UTC\n"); +} + +#[test] +fn test_date_tz_berlin() { + new_ucmd!() + .env("TZ", "Europe/Berlin") + .arg("+%Z") + .succeeds() + .stdout_matches(&Regex::new(r"^(CET|CEST)\n$").unwrap()); +} + +#[test] +fn test_date_tz_vancouver() { + new_ucmd!() + .env("TZ", "America/Vancouver") + .arg("+%Z") + .succeeds() + .stdout_matches(&Regex::new(r"^(PDT|PST)\n$").unwrap()); +} + +#[test] +fn test_date_tz_invalid() { + new_ucmd!() + .env("TZ", "Invalid/Timezone") + .arg("+%Z") + .succeeds() + .stdout_only("UTC\n"); +} + +#[test] +fn test_date_tz_with_format() { + new_ucmd!() + .env("TZ", "Europe/Berlin") + .arg("+%Y-%m-%d %H:%M:%S %Z") + .succeeds() + .stdout_matches( + &Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} (CET|CEST)\n$").unwrap(), + ); +} + +#[test] +fn test_date_tz_with_utc_flag() { + new_ucmd!() + .env("TZ", "Europe/Berlin") + .arg("-u") + .arg("+%Z") + .succeeds() + .stdout_only("UTC\n"); +} + +#[test] +fn test_date_tz_with_date_string() { + new_ucmd!() + .env("TZ", "Asia/Tokyo") + .arg("-d") + .arg("2024-01-01 12:00:00") + .arg("+%Y-%m-%d %H:%M:%S %Z") + .succeeds() + .stdout_only("2024-01-01 12:00:00 JST\n"); +} + +#[test] +fn test_date_tz_with_relative_time() { + new_ucmd!() + .env("TZ", "America/Vancouver") + .arg("-d") + .arg("1 hour ago") + .arg("+%Y-%m-%d %H:%M:%S %Z") + .succeeds() + .stdout_matches(&Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} PDT\n$").unwrap()); +} + +#[test] +fn test_date_utc_time() { + // Test that -u flag shows correct UTC time + new_ucmd!().arg("-u").arg("+%H:%M").succeeds(); + + // Test that -u flag shows UTC timezone + new_ucmd!() + .arg("-u") + .arg("+%Z") + .succeeds() + .stdout_only("UTC\n"); + + // Test that -u flag with specific timestamp shows correct UTC time + new_ucmd!() + .arg("-u") + .arg("-d") + .arg("@0") + .succeeds() + .stdout_only("Thu Jan 1 00:00:00 UTC 1970\n"); +} + +#[test] +fn test_date_empty_tz_time() { + new_ucmd!() + .env("TZ", "") + .arg("-d") + .arg("@0") + .succeeds() + .stdout_only("Thu Jan 1 00:00:00 UTC 1970\n"); +} From dadda0dd6a4c27de8f18b4eb3f350db85f40d04b Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 6 May 2025 11:27:03 +0800 Subject: [PATCH 072/139] test_date: Extend coverage to a lot more timezones Also test %z/%Z formats. --- tests/by-util/test_date.rs | 122 +++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index e5dda70bb3b..713717f6afe 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -2,6 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// +// spell-checker: ignore: AEDT AEST EEST NZDT NZST use chrono::{DateTime, Datelike, Duration, NaiveTime, Utc}; // spell-checker:disable-line use regex::Regex; @@ -564,60 +566,43 @@ fn test_date_from_stdin() { ); } -#[test] -fn test_date_empty_tz() { - new_ucmd!() - .env("TZ", "") - .arg("+%Z") - .succeeds() - .stdout_only("UTC\n"); -} - -#[test] -fn test_date_tz_utc() { - new_ucmd!() - .env("TZ", "UTC0") - .arg("+%Z") - .succeeds() - .stdout_only("UTC\n"); -} - -#[test] -fn test_date_tz_berlin() { - new_ucmd!() - .env("TZ", "Europe/Berlin") - .arg("+%Z") - .succeeds() - .stdout_matches(&Regex::new(r"^(CET|CEST)\n$").unwrap()); -} - -#[test] -fn test_date_tz_vancouver() { - new_ucmd!() - .env("TZ", "America/Vancouver") - .arg("+%Z") - .succeeds() - .stdout_matches(&Regex::new(r"^(PDT|PST)\n$").unwrap()); -} +const JAN2: &str = "2024-01-02 12:00:00 +0000"; +const JUL2: &str = "2024-07-02 12:00:00 +0000"; #[test] -fn test_date_tz_invalid() { - new_ucmd!() - .env("TZ", "Invalid/Timezone") - .arg("+%Z") - .succeeds() - .stdout_only("UTC\n"); -} +fn test_date_tz() { + fn test_tz(tz: &str, date: &str, output: &str) { + println!("Test with TZ={tz}, date=\"{date}\"."); + new_ucmd!() + .env("TZ", tz) + .arg("-d") + .arg(date) + .arg("+%Y-%m-%d %H:%M:%S %Z") + .succeeds() + .stdout_only(output); + } -#[test] -fn test_date_tz_with_format() { - new_ucmd!() - .env("TZ", "Europe/Berlin") - .arg("+%Y-%m-%d %H:%M:%S %Z") - .succeeds() - .stdout_matches( - &Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} (CET|CEST)\n$").unwrap(), - ); + // Empty TZ, UTC0, invalid timezone. + test_tz("", JAN2, "2024-01-02 12:00:00 UTC\n"); + test_tz("UTC0", JAN2, "2024-01-02 12:00:00 UTC\n"); + // TODO: We do not handle invalid timezones the same way as GNU coreutils + //test_tz("Invalid/Timezone", JAN2, "2024-01-02 12:00:00 Invalid\n"); + + // Test various locations, some of them use daylight saving, some don't. + test_tz("America/Vancouver", JAN2, "2024-01-02 04:00:00 PST\n"); + test_tz("America/Vancouver", JUL2, "2024-07-02 05:00:00 PDT\n"); + test_tz("Europe/Berlin", JAN2, "2024-01-02 13:00:00 CET\n"); + test_tz("Europe/Berlin", JUL2, "2024-07-02 14:00:00 CEST\n"); + test_tz("Africa/Cairo", JAN2, "2024-01-02 14:00:00 EET\n"); + // Egypt restored daylight saving in 2023, so if the database is outdated, this will fail. + //test_tz("Africa/Cairo", JUL2, "2024-07-02 15:00:00 EEST\n"); + test_tz("Asia/Tokyo", JAN2, "2024-01-02 21:00:00 JST\n"); + test_tz("Asia/Tokyo", JUL2, "2024-07-02 21:00:00 JST\n"); + test_tz("Australia/Sydney", JAN2, "2024-01-02 23:00:00 AEDT\n"); + test_tz("Australia/Sydney", JUL2, "2024-07-02 22:00:00 AEST\n"); // Shifts the other way. + test_tz("Pacific/Tahiti", JAN2, "2024-01-02 02:00:00 -10\n"); // No abbreviation. + test_tz("Antarctica/South_Pole", JAN2, "2024-01-03 01:00:00 NZDT\n"); + test_tz("Antarctica/South_Pole", JUL2, "2024-07-03 00:00:00 NZST\n"); } #[test] @@ -631,14 +616,31 @@ fn test_date_tz_with_utc_flag() { } #[test] -fn test_date_tz_with_date_string() { - new_ucmd!() - .env("TZ", "Asia/Tokyo") - .arg("-d") - .arg("2024-01-01 12:00:00") - .arg("+%Y-%m-%d %H:%M:%S %Z") - .succeeds() - .stdout_only("2024-01-01 12:00:00 JST\n"); +fn test_date_tz_various_formats() { + fn test_tz(tz: &str, date: &str, output: &str) { + println!("Test with TZ={tz}, date=\"{date}\"."); + new_ucmd!() + .env("TZ", tz) + .arg("-d") + .arg(date) + .arg("+%z %:z %::z %:::z %Z") + .succeeds() + .stdout_only(output); + } + + test_tz( + "America/Vancouver", + JAN2, + "-0800 -08:00 -08:00:00 -08 PST\n", + ); + // Half-hour timezone + test_tz("Asia/Calcutta", JAN2, "+0530 +05:30 +05:30:00 +05:30 IST\n"); + test_tz("Europe/Berlin", JAN2, "+0100 +01:00 +01:00:00 +01 CET\n"); + test_tz( + "Australia/Sydney", + JAN2, + "+1100 +11:00 +11:00:00 +11 AEDT\n", + ); } #[test] @@ -649,7 +651,7 @@ fn test_date_tz_with_relative_time() { .arg("1 hour ago") .arg("+%Y-%m-%d %H:%M:%S %Z") .succeeds() - .stdout_matches(&Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} PDT\n$").unwrap()); + .stdout_matches(&Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} P[DS]T\n$").unwrap()); } #[test] From eb5fc4c4cb63307d9ce11a33e58e02304051621a Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 27 Apr 2025 14:26:00 +0800 Subject: [PATCH 073/139] Cross.toml: Install tzdata in container Linux tests require that now, as we now assume /usr/share/zoneinfo is present. --- .github/workflows/CICD.yml | 4 ---- Cross.toml | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 Cross.toml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c9dcb2e3552..7fa43165f48 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -653,10 +653,6 @@ jobs: ;; esac outputs CARGO_TEST_OPTIONS - # ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml") - if [ "${CARGO_CMD}" = 'cross' ] && [ ! -e "Cross.toml" ] ; then - printf "[build.env]\npassthrough = [\"CI\", \"RUST_BACKTRACE\", \"CARGO_TERM_COLOR\"]\n" > Cross.toml - fi # * executable for `strip`? STRIP="strip" case ${{ matrix.job.target }} in diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 00000000000..52f5bad21dd --- /dev/null +++ b/Cross.toml @@ -0,0 +1,7 @@ +# spell-checker:ignore (misc) dpkg noninteractive tzdata +[build] +pre-build = [ + "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install tzdata", +] +[build.env] +passthrough = ["CI", "RUST_BACKTRACE", "CARGO_TERM_COLOR"] From 986bdf545da4ceaae0ae18339b11096a7059c704 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 25 May 2025 16:06:55 +0200 Subject: [PATCH 074/139] uucore: Remove custom_tz_fmt, cleanup dependencies Nobody needs it anymore. --- Cargo.lock | 32 ------ Cargo.toml | 2 - fuzz/Cargo.lock | 102 +------------------ src/uu/date/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 1 - src/uucore/Cargo.toml | 3 - src/uucore/src/lib/features.rs | 2 - src/uucore/src/lib/features/custom_tz_fmt.rs | 60 ----------- src/uucore/src/lib/lib.rs | 2 - 9 files changed, 6 insertions(+), 200 deletions(-) delete mode 100644 src/uucore/src/lib/features/custom_tz_fmt.rs diff --git a/Cargo.lock b/Cargo.lock index 427a8166c36..ab9d8d50b82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,27 +314,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "chrono-tz" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" -dependencies = [ - "parse-zoneinfo", - "phf_codegen", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -1798,15 +1777,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] - [[package]] name = "parse_datetime" version = "0.9.0" @@ -3738,7 +3708,6 @@ dependencies = [ "blake2b_simd", "blake3", "chrono", - "chrono-tz", "clap", "crc32fast", "data-encoding", @@ -3750,7 +3719,6 @@ dependencies = [ "fluent-bundle", "glob", "hex", - "iana-time-zone", "itertools 0.14.0", "libc", "md-5", diff --git a/Cargo.toml b/Cargo.toml index 44dda8c7db3..046f1c6ca24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -282,7 +282,6 @@ chrono = { version = "0.4.41", default-features = false, features = [ "alloc", "clock", ] } -chrono-tz = "0.10.0" clap = { version = "4.5", features = ["wrap_help", "cargo"] } clap_complete = "4.4" clap_mangen = "0.2" @@ -301,7 +300,6 @@ gcd = "2.3" glob = "0.3.1" half = "2.4.1" hostname = "0.4" -iana-time-zone = "0.1.57" indicatif = "0.17.8" itertools = "0.14.0" jiff = { version = "0.2.10", default-features = false, features = [ diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index f7d500c6a5d..cd9aae5a479 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -219,27 +219,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "chrono-tz" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" -dependencies = [ - "parse-zoneinfo", - "phf_codegen", -] - [[package]] name = "clap" version = "4.5.38" @@ -872,15 +851,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] - [[package]] name = "parse_datetime" version = "0.9.0" @@ -892,44 +862,6 @@ dependencies = [ "regex", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -984,15 +916,6 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.1" @@ -1000,7 +923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -1010,15 +933,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - [[package]] name = "rand_core" version = "0.9.3" @@ -1189,12 +1106,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "sm3" version = "0.4.2" @@ -1442,7 +1353,7 @@ dependencies = [ "itertools", "memchr", "nix", - "rand 0.9.1", + "rand", "rayon", "self_cell", "tempfile", @@ -1499,8 +1410,6 @@ dependencies = [ "bigdecimal", "blake2b_simd", "blake3", - "chrono", - "chrono-tz", "clap", "crc32fast", "data-encoding", @@ -1511,7 +1420,6 @@ dependencies = [ "fluent-bundle", "glob", "hex", - "iana-time-zone", "itertools", "libc", "md-5", @@ -1538,7 +1446,7 @@ name = "uucore-fuzz" version = "0.0.0" dependencies = [ "libfuzzer-sys", - "rand 0.9.1", + "rand", "uu_cksum", "uu_cut", "uu_date", @@ -1571,7 +1479,7 @@ version = "0.1.0" dependencies = [ "console", "libc", - "rand 0.9.1", + "rand", "similar", "tempfile", "uucore", diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index f498befc79e..af1e87fb4aa 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -26,7 +26,7 @@ jiff = { workspace = true, features = [ "tzdb-zoneinfo", "tzdb-concatenated", ] } -uucore = { workspace = true, features = ["custom-tz-fmt", "parser"] } +uucore = { workspace = true, features = ["parser"] } parse_datetime = { workspace = true } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 83a2f4fa53b..ffb90a9403c 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -37,7 +37,6 @@ terminal_size = { workspace = true } thiserror = { workspace = true } uucore = { workspace = true, features = [ "colors", - "custom-tz-fmt", "entries", "format", "fs", diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index be2db18e578..7395df34300 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -21,7 +21,6 @@ path = "src/lib/lib.rs" [dependencies] chrono = { workspace = true, optional = true } -chrono-tz = { workspace = true, optional = true } clap = { workspace = true } uucore_procs = { workspace = true } number_prefix = { workspace = true } @@ -29,7 +28,6 @@ dns-lookup = { workspace = true, optional = true } dunce = { version = "1.0.4", optional = true } wild = "2.2.1" glob = { workspace = true, optional = true } -iana-time-zone = { workspace = true, optional = true } itertools = { workspace = true, optional = true } time = { workspace = true, optional = true, features = [ "formatting", @@ -138,6 +136,5 @@ utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] version-cmp = [] wide = [] -custom-tz-fmt = ["chrono", "chrono-tz", "iana-time-zone"] tty = [] uptime = ["chrono", "libc", "windows-sys", "utmpx", "utmp-classic"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 257043e00ba..44db5307180 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -14,8 +14,6 @@ pub mod buf_copy; pub mod checksum; #[cfg(feature = "colors")] pub mod colors; -#[cfg(feature = "custom-tz-fmt")] -pub mod custom_tz_fmt; #[cfg(feature = "encoding")] pub mod encoding; #[cfg(feature = "extendedbigdecimal")] diff --git a/src/uucore/src/lib/features/custom_tz_fmt.rs b/src/uucore/src/lib/features/custom_tz_fmt.rs deleted file mode 100644 index 0d2b6aebe41..00000000000 --- a/src/uucore/src/lib/features/custom_tz_fmt.rs +++ /dev/null @@ -1,60 +0,0 @@ -// 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. - -use chrono::{TimeZone, Utc}; -use chrono_tz::{OffsetName, Tz}; -use iana_time_zone::get_timezone; - -/// Get the alphabetic abbreviation of the current timezone. -/// -/// For example, "UTC" or "CET" or "PDT" -fn timezone_abbreviation() -> String { - let tz = match std::env::var("TZ") { - // TODO Support other time zones... - Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC, - _ => match get_timezone() { - Ok(tz_str) => tz_str.parse().unwrap(), - Err(_) => Tz::Etc__UTC, - }, - }; - - let offset = tz.offset_from_utc_date(&Utc::now().date_naive()); - offset.abbreviation().unwrap_or("UTC").to_string() -} - -/// Adapt the given string to be accepted by the chrono library crate. -/// -/// # Arguments -/// -/// fmt: the format of the string -/// -/// # Return -/// -/// A string that can be used as parameter of the chrono functions that use formats -pub fn custom_time_format(fmt: &str) -> String { - // TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970 - // chrono crashes on %#z, but it's the same as %z anyway. - // GNU `date` uses `%N` for nano seconds, however the `chrono` crate uses `%f`. - fmt.replace("%#z", "%z") - .replace("%N", "%f") - .replace("%Z", timezone_abbreviation().as_ref()) -} - -#[cfg(test)] -mod tests { - use super::{custom_time_format, timezone_abbreviation}; - - #[test] - fn test_custom_time_format() { - assert_eq!(custom_time_format("%Y-%m-%d %H-%M-%S"), "%Y-%m-%d %H-%M-%S"); - assert_eq!(custom_time_format("%d-%m-%Y %H-%M-%S"), "%d-%m-%Y %H-%M-%S"); - assert_eq!(custom_time_format("%Y-%m-%d %H-%M-%S"), "%Y-%m-%d %H-%M-%S"); - assert_eq!( - custom_time_format("%Y-%m-%d %H-%M-%S.%N"), - "%Y-%m-%d %H-%M-%S.%f" - ); - assert_eq!(custom_time_format("%Z"), timezone_abbreviation()); - } -} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index b1a9363f728..8dc49cadd97 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -41,8 +41,6 @@ pub use crate::features::buf_copy; pub use crate::features::checksum; #[cfg(feature = "colors")] pub use crate::features::colors; -#[cfg(feature = "custom-tz-fmt")] -pub use crate::features::custom_tz_fmt; #[cfg(feature = "encoding")] pub use crate::features::encoding; #[cfg(feature = "extendedbigdecimal")] From 5e3284139c6f280b78a2eb72e1a3bb22fb5c71ec Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Thu, 29 May 2025 14:37:03 +0200 Subject: [PATCH 075/139] num_parser: Fix tests after pow_with_context update We get more precision, and more range, now. --- src/uucore/src/lib/features/parser/num_parser.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index efd3ba0ede5..c657b3ade8f 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -996,14 +996,14 @@ mod tests { assert_eq!( Ok(ExtendedBigDecimal::BigDecimal( // Wolfram Alpha says 9.8162042336235053508313854078782835648991393286913072670026492205522618203568834202759669215027003865... × 10^903089986 - BigDecimal::from_str("9.816204233623505350831385407878283564899139328691307267002649220552261820356883420275966921514831318e+903089986").unwrap() + BigDecimal::from_str("9.816204233623505350831385407878283564899139328691307267002649220552261820356883420275966921502700387e+903089986").unwrap() )), ExtendedBigDecimal::extended_parse("0x1p3000000000") ); assert_eq!( Ok(ExtendedBigDecimal::BigDecimal( // Wolfram Alpha says 1.3492131462369983551036088935544888715959511045742395978049631768570509541390540646442193112226520316... × 10^-9030900 - BigDecimal::from_str("1.349213146236998355103608893554488871595951104574239597804963176857050954139054064644219311222656999e-9030900").unwrap() + BigDecimal::from_str("1.349213146236998355103608893554488871595951104574239597804963176857050954139054064644219311222652032e-9030900").unwrap() )), // Couldn't get a answer from Wolfram Alpha for smaller negative exponents ExtendedBigDecimal::extended_parse("0x1p-30000000") @@ -1011,21 +1011,21 @@ mod tests { // ExtendedBigDecimal overflow/underflow assert!(matches!( - ExtendedBigDecimal::extended_parse(&format!("0x1p{}", u32::MAX as u64 + 1)), + ExtendedBigDecimal::extended_parse(&format!("0x1p{}", u64::MAX as u128 + 1)), Err(ExtendedParserError::Overflow(ExtendedBigDecimal::Infinity)) )); assert!(matches!( - ExtendedBigDecimal::extended_parse(&format!("-0x100P{}", u32::MAX as u64 + 1)), + ExtendedBigDecimal::extended_parse(&format!("-0x100P{}", u64::MAX as u128 + 1)), Err(ExtendedParserError::Overflow( ExtendedBigDecimal::MinusInfinity )) )); assert!(matches!( - ExtendedBigDecimal::extended_parse(&format!("0x1p-{}", u32::MAX as u64 + 1)), + ExtendedBigDecimal::extended_parse(&format!("0x1p-{}", u64::MAX as u128 + 1)), Err(ExtendedParserError::Underflow(ebd)) if ebd == ExtendedBigDecimal::zero() )); assert!(matches!( - ExtendedBigDecimal::extended_parse(&format!("-0x0.100p-{}", u32::MAX as u64 + 1)), + ExtendedBigDecimal::extended_parse(&format!("-0x0.100p-{}", u64::MAX as u128 + 1)), Err(ExtendedParserError::Underflow( ExtendedBigDecimal::MinusZero )) From 1baedc417b85b5ffc3a56e299a75e9e3c49d1d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= <72708393+RenjiSann@users.noreply.github.com> Date: Fri, 30 May 2025 16:29:43 +0200 Subject: [PATCH 076/139] ln: don't show error when overwriting a link with -sfn (#7449) * ln: fix #6350 * test(ln): test_symlink_no_deref_dir success has no stderr --- src/uu/ln/src/ln.rs | 1 + tests/by-util/test_ln.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 3b8ff0d7069..0fc72da499d 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -301,6 +301,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) show_error!("Could not update {}: {e}", target_dir.quote()); }; } + #[cfg(windows)] if target_dir.is_dir() { // Not sure why but on Windows, the symlink can be // considered as a dir diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 9ef25ef087c..99859503539 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -549,7 +549,11 @@ fn test_symlink_no_deref_dir() { scene.ucmd().args(&["-sn", dir1, link]).fails(); // Try with the no-deref - scene.ucmd().args(&["-sfn", dir1, link]).succeeds(); + scene + .ucmd() + .args(&["-sfn", dir1, link]) + .succeeds() + .no_stderr(); assert!(at.dir_exists(dir1)); assert!(at.dir_exists(dir2)); assert!(at.is_symlink(link)); From d96b62c17fd3ffd87cda26d00b938b3831de74e1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 30 May 2025 16:44:00 +0200 Subject: [PATCH 077/139] ln: rename error variant from TargetIsDirectory to TargetIsNotADirectory --- src/uu/ln/src/ln.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 0fc72da499d..c464076481e 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -48,7 +48,7 @@ pub enum OverwriteMode { #[derive(Error, Debug)] enum LnError { #[error("target {} is not a directory", _0.quote())] - TargetIsDirectory(PathBuf), + TargetIsNotADirectory(PathBuf), #[error("")] SomeLinksFailed, @@ -283,7 +283,7 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> { #[allow(clippy::cognitive_complexity)] fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> UResult<()> { if !target_dir.is_dir() { - return Err(LnError::TargetIsDirectory(target_dir.to_owned()).into()); + return Err(LnError::TargetIsNotADirectory(target_dir.to_owned()).into()); } // remember the linked destinations for further usage let mut linked_destinations: HashSet = HashSet::with_capacity(files.len()); From 7f5d1fd5b6a0249f747e48d9827db681cc6a824a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 18:33:26 +0000 Subject: [PATCH 078/139] chore(deps): update rust crate clap_complete to v4.5.52 --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d447ee4e408..07913d096cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.51" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2267df7f3c8e74e38268887ea5235d4dfadd39bfff2d56ab82d61776be355e" +checksum = "1a554639e42d0c838336fc4fbedb9e2df3ad1fa4acda149f9126b4ccfcd7900f" dependencies = [ "clap", ] @@ -934,7 +934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2139,7 +2139,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2152,7 +2152,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2396,7 +2396,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] From ee9e91a77c8f1ad29370a45e46a80934cca5444e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 18:30:55 +0000 Subject: [PATCH 079/139] chore(deps): update dawidd6/action-download-artifact action to v10 --- .github/workflows/CICD.yml | 4 ++-- .github/workflows/GnuTests.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c9dcb2e3552..5b022370354 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -443,14 +443,14 @@ jobs: --arg multisize "$SIZE_MULTI" \ '{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json - name: Download the previous individual size result - uses: dawidd6/action-download-artifact@v9 + uses: dawidd6/action-download-artifact@v10 with: workflow: CICD.yml name: individual-size-result repo: uutils/coreutils path: dl - name: Download the previous size result - uses: dawidd6/action-download-artifact@v9 + uses: dawidd6/action-download-artifact@v10 with: workflow: CICD.yml name: size-result diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index fc40fc91f5f..d5c6257e706 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -136,7 +136,7 @@ jobs: working-directory: ${{ steps.vars.outputs.path_GNU }} - name: Retrieve reference artifacts - uses: dawidd6/action-download-artifact@v9 + uses: dawidd6/action-download-artifact@v10 # ref: continue-on-error: true ## don't break the build for missing reference artifacts (may be expired or just not generated yet) with: From c5b445f6f20fccb6aa3c56e044a2dc6f0492dead Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 1 Jun 2025 19:27:06 +0200 Subject: [PATCH 080/139] uucore: num_parser: Clarify origin of pow_with_context And why we use an older minimum Rust version in that piece of code. --- src/uucore/src/lib/features/parser/num_parser.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index c657b3ade8f..84aa82bdd79 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -384,6 +384,8 @@ fn make_error<'a>(overflow: bool, negative: bool) -> ExtendedParserError<'a, Ext // // TODO: Still pending discussion in https://github.com/akubera/bigdecimal-rs/issues/147, // we do lose a little bit of precision, and the last digits may not be correct. +// Note: This has been copied from the latest revision in https://github.com/akubera/bigdecimal-rs/pull/148, +// so it's using minimum Rust version of `bigdecimal-rs`. fn pow_with_context(bd: &BigDecimal, exp: i64, ctx: &Context) -> BigDecimal { if exp == 0 { return 1.into(); @@ -412,6 +414,7 @@ fn pow_with_context(bd: &BigDecimal, exp: i64, ctx: &Context) -> BigDecimal { // Note: 63 - n.leading_zeros() == n.ilog2, but that's only available in recent Rust versions. let muls = (n.count_ones() + (63 - n.leading_zeros()) - 1) as u64; // Note: div_ceil would be nice to use here, but only available in recent Rust versions. + // (see note above about minimum Rust version in use) let margin_extra = (muls + MUL_PER_MARGIN_EXTRA / 2) / MUL_PER_MARGIN_EXTRA; let mut margin = margin_extra + MARGIN_PER_MUL * muls; From 5d75e28b879e837aacd288b7b91b1902e60e9895 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 1 Jun 2025 19:36:58 +0200 Subject: [PATCH 081/139] ls: Simplify TimeStyle::format Also, the comment does not fully apply anymore, so we can leave it more open-ended to figure out how to support locale. --- src/uu/ls/src/ls.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index df55e10f107..b2c98689de8 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -292,22 +292,18 @@ impl TimeStyle { let mut out = StdIoWrite(out); let config = jiff::fmt::strtime::Config::new().lenient(true); - match (self, recent) { - (Self::FullIso, _) => { - tm.format_with_config(&config, "%Y-%m-%d %H:%M:%S.%f %z", &mut out) - } - (Self::LongIso, _) => tm.format_with_config(&config, "%Y-%m-%d %H:%M", &mut out), - (Self::Iso, true) => tm.format_with_config(&config, "%m-%d %H:%M", &mut out), - (Self::Iso, false) => tm.format_with_config(&config, "%Y-%m-%d ", &mut out), - // spell-checker:ignore (word) datetime - //In this version of chrono translating can be done - //The function is chrono::datetime::DateTime::format_localized - //However it's currently still hard to get the current pure-rust-locale - //So it's not yet implemented - (Self::Locale, true) => tm.format_with_config(&config, "%b %e %H:%M", &mut out), - (Self::Locale, false) => tm.format_with_config(&config, "%b %e %Y", &mut out), - (Self::Format(fmt), _) => tm.format_with_config(&config, fmt, &mut out), - } + let fmt = match (self, recent) { + (Self::FullIso, _) => "%Y-%m-%d %H:%M:%S.%f %z", + (Self::LongIso, _) => "%Y-%m-%d %H:%M", + (Self::Iso, true) => "%m-%d %H:%M", + (Self::Iso, false) => "%Y-%m-%d ", + // TODO: Using correct locale string is not implemented. + (Self::Locale, true) => "%b %e %H:%M", + (Self::Locale, false) => "%b %e %Y", + (Self::Format(fmt), _) => fmt, + }; + + tm.format_with_config(&config, fmt, &mut out) } } From 66d1e8a8720627082147eb13da98ad833629bb1c Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sun, 1 Jun 2025 20:00:42 +0200 Subject: [PATCH 082/139] test_date: Expand on test_date_utc_time Using the current time requires a bit of care, but it's nice to have a test that doesn't use a fixed date as input. --- tests/by-util/test_date.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 713717f6afe..c3149834717 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -657,7 +657,39 @@ fn test_date_tz_with_relative_time() { #[test] fn test_date_utc_time() { // Test that -u flag shows correct UTC time - new_ucmd!().arg("-u").arg("+%H:%M").succeeds(); + // We get 2 UTC times just in case we're really unlucky and this runs around + // an hour change. + let utc_hour_1: i32 = new_ucmd!() + .env("TZ", "Asia/Taipei") + .arg("-u") + .arg("+%-H") + .succeeds() + .stdout_str() + .trim_end() + .parse() + .unwrap(); + let tpe_hour: i32 = new_ucmd!() + .env("TZ", "Asia/Taipei") + .arg("+%-H") + .succeeds() + .stdout_str() + .trim_end() + .parse() + .unwrap(); + let utc_hour_2: i32 = new_ucmd!() + .env("TZ", "Asia/Taipei") + .arg("-u") + .arg("+%-H") + .succeeds() + .stdout_str() + .trim_end() + .parse() + .unwrap(); + // Taipei is always 8 hours ahead of UTC (no daylight savings) + assert!( + (tpe_hour - utc_hour_1 + 24) % 24 == 8 || (tpe_hour - utc_hour_2 + 24) % 24 == 8, + "TPE: {tpe_hour} UTC: {utc_hour_1}/{utc_hour_2}" + ); // Test that -u flag shows UTC timezone new_ucmd!() From 546e50846e13bc99e99d443d615bdfe928553c85 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 29 May 2025 10:17:04 +0200 Subject: [PATCH 083/139] cp: rewrite test to remove procfs dependency --- tests/by-util/test_cp.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index cb7eea5cdfb..fc357686136 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs +// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR outfile uufs xattrs // spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel use uucore::display::Quotable; use uutests::util::TestScenario; @@ -2556,22 +2556,21 @@ fn test_cp_reflink_insufficient_permission() { #[cfg(target_os = "linux")] #[test] fn test_closes_file_descriptors() { - use procfs::process::Process; use rlimit::Resource; - let me = Process::myself().unwrap(); + + let pid = std::process::id(); + let fd_path = format!("/proc/{pid}/fd"); // The test suite runs in parallel, we have pipe, sockets // opened by other tests. // So, we take in account the various fd to increase the limit - let number_file_already_opened: u64 = me.fd_count().unwrap().try_into().unwrap(); + let number_file_already_opened: u64 = std::fs::read_dir(fd_path) + .unwrap() + .count() + .try_into() + .unwrap(); let limit_fd: u64 = number_file_already_opened + 9; - // For debugging purposes: - for f in me.fd().unwrap() { - let fd = f.unwrap(); - println!("{fd:?} {:?}", fd.mode()); - } - new_ucmd!() .arg("-r") .arg("--reflink=auto") From 5bbcb4ee630c98e0404797458eaf7b524c3ed60f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 29 May 2025 10:19:44 +0200 Subject: [PATCH 084/139] Cargo.toml: remove procfs dependency --- Cargo.lock | 54 ++++++------------------------------------------------ Cargo.toml | 5 +---- 2 files changed, 7 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07912042234..4e2b95dbd77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,7 +457,6 @@ dependencies = [ "phf", "phf_codegen", "pretty_assertions", - "procfs", "rand 0.9.1", "regex", "rlimit", @@ -686,7 +685,7 @@ dependencies = [ "filedescriptor", "mio", "parking_lot", - "rustix 1.0.1", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -1444,12 +1443,6 @@ dependencies = [ "zlib-rs", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1912,28 +1905,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" -dependencies = [ - "bitflags 2.9.0", - "hex", - "procfs-core", - "rustix 0.38.44", -] - -[[package]] -name = "procfs-core" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" -dependencies = [ - "bitflags 2.9.0", - "hex", -] - [[package]] name = "quick-error" version = "2.0.1" @@ -2149,19 +2120,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.1" @@ -2171,7 +2129,7 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] @@ -2415,7 +2373,7 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.1", + "rustix", "windows-sys 0.59.0", ] @@ -2425,7 +2383,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.1", + "rustix", "windows-sys 0.59.0", ] @@ -2794,7 +2752,7 @@ dependencies = [ "filetime", "indicatif", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "quick-error", "selinux", "uucore", @@ -4168,7 +4126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" dependencies = [ "libc", - "rustix 1.0.1", + "rustix", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1b58faf6b0b..cec7ae21c41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ # coreutils (uutils) # * see the repository LICENSE, README, and CONTRIBUTING files for more information -# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap procfs uuhelp startswith constness expl +# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl [package] name = "coreutils" @@ -526,9 +526,6 @@ hex-literal = "1.0.0" rstest = { workspace = true } ctor = "0.4.1" -[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies] -procfs = { version = "0.17", default-features = false } - [target.'cfg(unix)'.dev-dependencies] nix = { workspace = true, features = ["process", "signal", "user", "term"] } rlimit = "0.10.1" From 80e639377784bd2b386352158c8cb3ce8c6acf61 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:19:26 +0000 Subject: [PATCH 085/139] chore(deps): update rust crate jiff to v0.2.14 --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e2b95dbd77..7fb37fbecea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -912,7 +912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1317,9 +1317,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1327,14 +1327,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "jiff-static" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" dependencies = [ "proc-macro2", "quote", @@ -2130,7 +2130,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2374,7 +2374,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] From dfc4072181aa8bd588742034177a618b7f12dbdb Mon Sep 17 00:00:00 2001 From: Will Shuttleworth Date: Mon, 2 Jun 2025 08:41:25 +0000 Subject: [PATCH 086/139] stty: fix output redirection (#8016) * stty: fix output redirection * Revert "stty: fix output redirection" This reverts commit 604de7c4de6f51a6bcd495ccda11602c12804493. * stty: fix output redirection by trying /dev/tty before stdout --- src/uu/stty/src/stty.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 5cc24a5968e..4e2ceaa3fdf 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -142,7 +142,18 @@ impl<'a> Options<'a> { .custom_flags(O_NONBLOCK) .open(f)?, ), - None => Device::Stdout(stdout()), + // default to /dev/tty, if that does not exist then default to stdout + None => { + if let Ok(f) = std::fs::OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK) + .open("/dev/tty") + { + Device::File(f) + } else { + Device::Stdout(stdout()) + } + } }, settings: matches .get_many::(options::SETTINGS) From 2e8b6fabcc87de73856530dd49bb2b9876d7c2bc Mon Sep 17 00:00:00 2001 From: Etienne Cordonnier Date: Thu, 22 May 2025 21:53:50 +0200 Subject: [PATCH 087/139] stdbuf: add test_libstdbuf_preload This test verifies that libstdbuf correctly gets preloaded, and that there are no architecture mismatch errors. At the moment the test passes when compiled normally, but fails when compiled with cross-rs, due to https://github.com/uutils/coreutils/issues/6591 This passes: ``` cargo test --features stdbuf test_stdbuf::test_libstdbuf_preload -- --nocapture ``` This fails: ``` cross test --target aarch64-unknown-linux-gnu --features stdbuf test_stdbuf::test_libstdbuf_preload -- --nocapture ``` Signed-off-by: Etienne Cordonnier --- tests/by-util/test_stdbuf.rs | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index c4294c6af41..cbd0a5c2b6b 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -2,6 +2,13 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore dyld dylib setvbuf +#[cfg(all( + not(target_os = "windows"), + not(target_os = "openbsd"), + not(target_os = "macos") +))] +use std::process::Command; use uutests::new_ucmd; #[cfg(not(target_os = "windows"))] use uutests::util::TestScenario; @@ -34,6 +41,9 @@ fn test_no_such() { #[test] fn test_stdbuf_unbuffered_stdout() { // This is a basic smoke test + // Note: This test only verifies that stdbuf does not crash and that output is passed through as expected + // for simple, short-lived commands. It does not guarantee that buffering is actually modified or that + // libstdbuf is loaded and functioning correctly. new_ucmd!() .args(&["-o0", "head"]) .pipe_in("The quick brown fox jumps over the lazy dog.") @@ -44,6 +54,9 @@ fn test_stdbuf_unbuffered_stdout() { #[cfg(all(not(target_os = "windows"), not(target_os = "openbsd")))] #[test] fn test_stdbuf_line_buffered_stdout() { + // Note: This test only verifies that stdbuf does not crash and that output is passed through as expected + // for simple, short-lived commands. It does not guarantee that buffering is actually modified or that + // libstdbuf is loaded and functioning correctly. new_ucmd!() .args(&["-oL", "head"]) .pipe_in("The quick brown fox jumps over the lazy dog.") @@ -105,3 +118,63 @@ fn test_stdbuf_invalid_mode_fails() { } } } + +// macos uses DYLD_PRINT_LIBRARIES, not LD_DEBUG, so disable on macos at the moment +#[cfg(all( + not(target_os = "windows"), + not(target_os = "openbsd"), + not(target_os = "macos") +))] +#[test] +fn test_setvbuf_resolution() { + // Run a simple program with LD_DEBUG=symbols to see which setvbuf is being used + // Written in a way that it can run with cross-rs and be used as regression test + // for https://github.com/uutils/coreutils/issues/6591 + + let scene = TestScenario::new(util_name!()); + let coreutils_bin = &scene.bin_path; + + // Test with our own echo (should have the correct architecture even when cross-compiled using cross-rs, + // in which case the "system" echo will be the host architecture) + let uutils_echo_cmd = format!( + "LD_DEBUG=symbols {} stdbuf -oL {} echo test 2>&1", + coreutils_bin.display(), + coreutils_bin.display() + ); + let uutils_output = Command::new("sh") + .arg("-c") + .arg(&uutils_echo_cmd) + .output() + .expect("Failed to run uutils echo test"); + + let uutils_debug = String::from_utf8_lossy(&uutils_output.stdout); + + // Check if libstdbuf.so / libstdbuf.dylib is in the lookup path. The log should contain something like this: + // "symbol=setvbuf; lookup in file=/tmp/.tmp0mfmCg/libstdbuf.so [0]" + let libstdbuf_in_path = uutils_debug.contains("symbol=setvbuf") + && uutils_debug.contains("lookup in file=") + && uutils_debug.contains("libstdbuf"); + + // Check for lack of architecture mismatch error. The potential error message is: + // "ERROR: ld.so: object '/tmp/.tmpCLq8jl/libstdbuf.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored." + let arch_mismatch_line = uutils_debug + .lines() + .find(|line| line.contains("cannot be preloaded")); + println!("LD_DEBUG output: {}", uutils_debug); + let no_arch_mismatch = arch_mismatch_line.is_none(); + + println!("libstdbuf in lookup path: {}", libstdbuf_in_path); + println!("No architecture mismatch: {}", no_arch_mismatch); + if let Some(error_line) = arch_mismatch_line { + println!("Architecture mismatch error: {}", error_line); + } + + assert!( + libstdbuf_in_path, + "libstdbuf should be in lookup path with uutils echo" + ); + assert!( + no_arch_mismatch, + "uutils echo should not show architecture mismatch" + ); +} From 35634b46a0acfc9028743bf023c133a4d0135f13 Mon Sep 17 00:00:00 2001 From: Etienne Cordonnier Date: Wed, 7 May 2025 20:01:49 +0200 Subject: [PATCH 088/139] stdbuf: fix cross-compilation Summary: Partial fix for https://github.com/uutils/coreutils/issues/6591 The current code declare libstdbuf as a build-dependency of stdbuf as a workaround to enforce that libstdbuf is compiled before stdbuf. This breaks cross-compilation, because build-dependencies were compiled for the host architecture, and not for the target architecture. The reason this workaround is necessary is that bindeps is available only in nightly at the moment: https://rust-lang.github.io/rfcs/3028-cargo-binary-dependencies.html This commit replaces the "build-dependency" workaround with another workaround: calling cargo manually to build libstdbuf in the build.rs of stdbuf, in order to ensure that libstdbuf is built before stdbuf. Changes: - Removed cpp/cpp_build dependencies: The cpp, cpp_build, and related dependencies were removed because they made cross-compilation in a build.rs file very complex, since you need to pass proper CXX env variables for cross-compilation, whereas cross-compiling rust code using cargo is quite simple. Provided Rust implementations for getting stdin, stdout, and stderr pointers. Switched from C++/cpp macro-based initialization to using the Rust ctor crate for library initialization. - Remove "feat_require_crate_cpp" which is not needed any more, since stdbuf was the only utility using the cpp crate. Tests: This commit fixes e.g. this test: cross test --target aarch64-unknown-linux-gnu --features stdbuf test_stdbuf::test_libstdbuf_preload -- --nocapture - The "i686" build of stdbuf was also broken (stdbuf 32 bits, but libstdbuf 64 bits) and test_stdbuf::test_libstdbuf_preload of the i686 builds in github CI serves as regression test for this issue, no need to add a cross-rs test for aarch64. - The x86_64 musl build of stdbuf was also broken and was passing tests in CI only because it was compiled with the wrong libc (glibc instead of musl) Signed-off-by: Etienne Cordonnier --- .cargo/config.toml | 5 + Cargo.lock | 379 ++++++++++--------- Cargo.toml | 11 +- deny.toml | 38 +- src/uu/stdbuf/Cargo.toml | 4 +- src/uu/stdbuf/build.rs | 106 ++++-- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 10 +- src/uu/stdbuf/src/libstdbuf/build.rs | 14 +- src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs | 84 +++- src/uu/stdbuf/src/stdbuf.rs | 10 + tests/by-util/test_stdbuf.rs | 80 +++- 11 files changed, 475 insertions(+), 266 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index c6aa207614f..02550b267d0 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,3 +3,8 @@ linker = "x86_64-unknown-redox-gcc" [env] PROJECT_NAME_FOR_VERSION_STRING = "uutils coreutils" + +# libstdbuf must be a shared library, so musl libc can't be linked statically +# https://github.com/rust-lang/rust/issues/82193 +[build] +rustflags = [ "-C", "target-feature=-crt-static" ] diff --git a/Cargo.lock b/Cargo.lock index 4e2b95dbd77..1e9ebde10d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,12 +88,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -172,7 +172,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -194,9 +194,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -274,9 +274,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "shlex", ] @@ -412,7 +412,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] @@ -579,56 +579,6 @@ dependencies = [ "zip", ] -[[package]] -name = "cpp" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bcac3d8234c1fb813358e83d1bb6b0290a3d2b3b5efc6b88bfeaf9d8eec17" -dependencies = [ - "cpp_macros", -] - -[[package]] -name = "cpp_build" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f8638c97fbd79cc6fc80b616e0e74b49bac21014faed590bbc89b7e2676c90" -dependencies = [ - "cc", - "cpp_common", - "lazy_static", - "proc-macro2", - "regex", - "syn", - "unicode-xid", -] - -[[package]] -name = "cpp_common" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fcfea2ee05889597d35e986c2ad0169694320ae5cc8f6d2640a4bb8a884560" -dependencies = [ - "lazy_static", - "proc-macro2", - "syn", -] - -[[package]] -name = "cpp_macros" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d156158fe86e274820f5a53bc9edb0885a6e7113909497aa8d883b69dd171871" -dependencies = [ - "aho-corasick", - "byteorder", - "cpp_common", - "lazy_static", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -678,7 +628,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm_winapi", "derive_more", "document-features", @@ -907,9 +857,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -921,7 +871,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22be12de19decddab85d09f251ec8363f060ccb22ec9c81bc157c0c8433946d8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "log", "scopeguard", "uuid", @@ -1026,9 +976,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fs_extra" @@ -1122,9 +1072,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1133,14 +1083,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1167,9 +1117,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -1225,12 +1175,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -1252,7 +1202,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -1317,9 +1267,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1332,9 +1282,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" dependencies = [ "proc-macro2", "quote", @@ -1377,9 +1327,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -1395,12 +1345,6 @@ dependencies = [ "libc", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.172" @@ -1409,19 +1353,19 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.0", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -1429,7 +1373,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] @@ -1457,25 +1401,19 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" @@ -1483,7 +1421,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -1529,23 +1467,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1554,7 +1492,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -1585,7 +1523,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "filetime", "fsevent-sys", "inotify", @@ -1692,9 +1630,15 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "onig" @@ -1702,7 +1646,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "once_cell", "onig_sys", @@ -1739,9 +1683,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1749,9 +1693,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1860,11 +1804,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.25", ] [[package]] @@ -1879,9 +1823,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.30" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" dependencies = [ "proc-macro2", "syn", @@ -1920,6 +1864,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -1973,7 +1923,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -1982,7 +1932,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] @@ -2007,11 +1957,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2122,11 +2072,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", @@ -2135,9 +2085,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "same-file" @@ -2166,7 +2116,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e37f432dfe840521abd9a72fefdf88ed7ad0f43bbea7d9d1d3d80383e9f4ad13" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "once_cell", "parking_lot", @@ -2282,9 +2232,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -2333,9 +2283,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2349,9 +2299,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.99" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -2371,7 +2321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2577,12 +2527,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unindent" version = "0.2.4" @@ -2612,7 +2556,7 @@ dependencies = [ "thiserror 1.0.69", "time", "utmp-classic-raw", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2622,7 +2566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22c226537a3d6e01c440c1926ca0256dbee2d19b2229ede6fc4863a6493dd831" dependencies = [ "cfg-if", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -3399,8 +3343,7 @@ dependencies = [ name = "uu_stdbuf_libstdbuf" version = "0.1.0" dependencies = [ - "cpp", - "cpp_build", + "ctor", "libc", ] @@ -3709,9 +3652,13 @@ version = "0.1.0" [[package]] name = "uuid" -version = "1.15.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "uutests" @@ -3770,9 +3717,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -3876,7 +3823,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3887,9 +3834,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.60.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -3900,9 +3847,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.59.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", @@ -3928,18 +3875,18 @@ checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -3995,13 +3942,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4014,6 +3977,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4026,6 +3995,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4038,12 +4013,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4056,6 +4043,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4068,6 +4061,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4080,6 +4079,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4092,6 +4097,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.10" @@ -4103,11 +4114,11 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -4148,7 +4159,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive 0.8.25", ] [[package]] @@ -4162,6 +4182,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -4199,14 +4230,12 @@ checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8" [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] diff --git a/Cargo.toml b/Cargo.toml index cec7ae21c41..a69100feaac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,7 +158,6 @@ feat_os_macos = [ feat_os_unix = [ "feat_Tier1", # - "feat_require_crate_cpp", "feat_require_unix", "feat_require_unix_utmpx", "feat_require_unix_hostid", @@ -185,9 +184,7 @@ feat_os_unix_android = [ # # ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities # -# "feat_require_crate_cpp" == set of utilities requiring the `cpp` crate (which fail to compile on several platforms; as of 2020-04-23) -feat_require_crate_cpp = ["stdbuf"] -# "feat_require_unix" == set of utilities requiring support which is only available on unix platforms (as of 2020-04-23) +# "feat_require_unix" == set of utilities requiring support which is only available on unix platforms feat_require_unix = [ "chgrp", "chmod", @@ -204,6 +201,7 @@ feat_require_unix = [ "nohup", "pathchk", "stat", + "stdbuf", "stty", "timeout", "tty", @@ -220,8 +218,6 @@ feat_require_selinux = ["chcon", "runcon"] feat_os_unix_fuchsia = [ "feat_common_core", # - "feat_require_crate_cpp", - # "chgrp", "chmod", "chown", @@ -287,6 +283,7 @@ clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" crossterm = "0.29.0" +ctor = "0.4.1" ctrlc = { version = "3.4.7", features = ["termination"] } dns-lookup = { version = "2.0.4" } exacl = "0.12.0" @@ -502,6 +499,7 @@ yes = { optional = true, version = "0.1.0", package = "uu_yes", path = "src/uu/y [dev-dependencies] chrono = { workspace = true } +ctor = { workspace = true } filetime = { workspace = true } glob = { workspace = true } libc = { workspace = true } @@ -524,7 +522,6 @@ uucore = { workspace = true, features = [ walkdir = { workspace = true } hex-literal = "1.0.0" rstest = { workspace = true } -ctor = "0.4.1" [target.'cfg(unix)'.dev-dependencies] nix = { workspace = true, features = ["process", "signal", "user", "term"] } diff --git a/deny.toml b/deny.toml index 1b1700dcdf4..c15502149b1 100644 --- a/deny.toml +++ b/deny.toml @@ -58,22 +58,42 @@ skip = [ { name = "windows-sys", version = "0.48.0" }, # mio, nu-ansi-term, socket2 { name = "windows-sys", version = "0.52.0" }, + # anstyle-query + { name = "windows-sys", version = "0.59.0" }, # windows-sys - { name = "windows-targets", version = "0.48.0" }, + { name = "windows-targets", version = "0.48.5" }, + # parking_lot_core + { name = "windows-targets", version = "0.52.6" }, # windows-targets - { name = "windows_aarch64_gnullvm", version = "0.48.0" }, + { name = "windows_aarch64_gnullvm", version = "0.48.5" }, # windows-targets - { name = "windows_aarch64_msvc", version = "0.48.0" }, + { name = "windows_aarch64_gnullvm", version = "0.52.6" }, # windows-targets - { name = "windows_i686_gnu", version = "0.48.0" }, + { name = "windows_aarch64_msvc", version = "0.48.5" }, # windows-targets - { name = "windows_i686_msvc", version = "0.48.0" }, + { name = "windows_aarch64_msvc", version = "0.52.6" }, # windows-targets - { name = "windows_x86_64_gnu", version = "0.48.0" }, + { name = "windows_i686_gnu", version = "0.48.5" }, # windows-targets - { name = "windows_x86_64_gnullvm", version = "0.48.0" }, + { name = "windows_i686_gnu", version = "0.52.6" }, # windows-targets - { name = "windows_x86_64_msvc", version = "0.48.0" }, + { name = "windows_i686_gnullvm", version = "0.52.6" }, + # windows-targets + { name = "windows_i686_msvc", version = "0.48.5" }, + # windows-targets + { name = "windows_i686_msvc", version = "0.52.6" }, + # windows-targets + { name = "windows_x86_64_gnu", version = "0.48.5" }, + # windows-targets + { name = "windows_x86_64_gnu", version = "0.52.6" }, + # windows-targets + { name = "windows_x86_64_gnullvm", version = "0.48.5" }, + # windows-targets + { name = "windows_x86_64_gnullvm", version = "0.52.6" }, + # windows-targets + { name = "windows_x86_64_msvc", version = "0.48.5" }, + # windows-targets + { name = "windows_x86_64_msvc", version = "0.52.6" }, # kqueue-sys, onig { name = "bitflags", version = "1.3.2" }, # ansi-width @@ -98,6 +118,8 @@ skip = [ { name = "rand_chacha", version = "0.3.1" }, # rand { name = "rand_core", version = "0.6.4" }, + # utmp-classic + { name = "zerocopy", version = "0.7.35" }, ] # spell-checker: enable diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 5f9e8ffd772..bcfc9fb9411 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -19,12 +19,10 @@ path = "src/stdbuf.rs" [dependencies] clap = { workspace = true } +libstdbuf = { package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } tempfile = { workspace = true } uucore = { workspace = true, features = ["parser"] } -[build-dependencies] -libstdbuf = { version = "0.1.0", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } - [[bin]] name = "stdbuf" path = "src/main.rs" diff --git a/src/uu/stdbuf/build.rs b/src/uu/stdbuf/build.rs index b31a32235a8..abb4069adbf 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -2,14 +2,20 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) dylib libstdbuf deps liblibstdbuf +// spell-checker:ignore (ToDO) bindeps dylib libstdbuf deps liblibstdbuf use std::env; -use std::env::current_exe; use std::fs; use std::path::Path; +use std::process::Command; -#[cfg(not(any(target_vendor = "apple", target_os = "windows")))] +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" +))] mod platform { pub const DYLIB_EXT: &str = ".so"; } @@ -19,31 +25,83 @@ mod platform { pub const DYLIB_EXT: &str = ".dylib"; } -#[cfg(target_os = "windows")] -mod platform { - pub const DYLIB_EXT: &str = ".dll"; -} - fn main() { - let current_exe = current_exe().unwrap(); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/libstdbuf/src/libstdbuf.rs"); + + let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); + let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string()); + + // Create a separate build directory for libstdbuf to avoid conflicts + let build_dir = Path::new(&out_dir).join("libstdbuf-build"); + fs::create_dir_all(&build_dir).expect("Failed to create build directory"); + + // Get the cargo executable + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + + // This manual cargo call ensures that libstdbuf is built before stdbuf.rs is compiled, which is necessary + // for include_bytes!(..."/libstdbuf.so") to work. + // In the future, "bindeps" should be used to simplify the code and avoid the manual cargo call, + // however this is available only in cargo nightly at the moment. + // See the tracking issue: https://github.com/rust-lang/cargo/issues/9096 + let mut cmd = Command::new(&cargo); + cmd.env_clear().envs(env::vars()); + cmd.current_dir(Path::new("src/libstdbuf")).args([ + "build", + "--target-dir", + build_dir.to_str().unwrap(), + ]); + + // Get the current profile + let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()); + + // Pass the release flag if we're in release mode + if profile == "release" || profile == "bench" { + cmd.arg("--release"); + } + + // Pass the target architecture if we're cross-compiling + if !target.is_empty() && target != "unknown" { + cmd.arg("--target").arg(&target); + } - let out_dir_string = env::var("OUT_DIR").unwrap(); - let out_dir = Path::new(&out_dir_string); + let status = cmd.status().expect("Failed to build libstdbuf"); + assert!(status.success(), "Failed to build libstdbuf"); - let deps_dir = current_exe.ancestors().nth(3).unwrap().join("deps"); - dbg!(&deps_dir); + // Copy the built library to OUT_DIR for include_bytes! to find + let lib_name = format!("liblibstdbuf{}", platform::DYLIB_EXT); + let dest_path = Path::new(&out_dir).join(format!("libstdbuf{}", platform::DYLIB_EXT)); - let libstdbuf = deps_dir - .read_dir() - .unwrap() - .flatten() - .find(|entry| { - let n = entry.file_name(); - let name = n.to_string_lossy(); + // Check multiple possible locations for the built library + let possible_paths = if !target.is_empty() && target != "unknown" { + vec![ + build_dir.join(&target).join(&profile).join(&lib_name), + build_dir + .join(&target) + .join(&profile) + .join("deps") + .join(&lib_name), + ] + } else { + vec![ + build_dir.join(&profile).join(&lib_name), + build_dir.join(&profile).join("deps").join(&lib_name), + ] + }; - name.starts_with("liblibstdbuf") && name.ends_with(platform::DYLIB_EXT) - }) - .expect("unable to find libstdbuf"); + // Try to find the library in any of the possible locations + let mut found = false; + for source_path in &possible_paths { + if source_path.exists() { + fs::copy(source_path, &dest_path).expect("Failed to copy libstdbuf library"); + found = true; + break; + } + } - fs::copy(libstdbuf.path(), out_dir.join("libstdbuf.so")).unwrap(); + assert!( + found, + "Could not find built libstdbuf library. Searched in: {:?}.", + possible_paths + ); } diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 3f8511ffaef..01dd3a49d91 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -13,14 +13,8 @@ edition.workspace = true [lib] name = "libstdbuf" path = "src/libstdbuf.rs" -crate-type = [ - "cdylib", - "rlib", -] # XXX: note: the rlib is just to prevent Cargo from spitting out a warning +crate-type = ["cdylib"] [dependencies] -cpp = "0.5.10" +ctor = { workspace = true } libc = { workspace = true } - -[build-dependencies] -cpp_build = "0.5.10" diff --git a/src/uu/stdbuf/src/libstdbuf/build.rs b/src/uu/stdbuf/src/libstdbuf/build.rs index 6dcd6a86912..505cdf68aec 100644 --- a/src/uu/stdbuf/src/libstdbuf/build.rs +++ b/src/uu/stdbuf/src/libstdbuf/build.rs @@ -4,8 +4,18 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) libstdbuf -use cpp_build::Config; +use std::env; fn main() { - Config::new().pic(true).build("src/libstdbuf.rs"); + // Make sure we're building position-independent code for use with LD_PRELOAD + println!("cargo:rustc-link-arg=-fPIC"); + + let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string()); + // Ensure the library doesn't have any undefined symbols (-z flag not supported on macOS) + if !target.contains("apple-darwin") { + println!("cargo:rustc-link-arg=-z"); + println!("cargo:rustc-link-arg=defs"); + } + + println!("cargo:rerun-if-changed=src/libstdbuf.rs"); } diff --git a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs index b151ce68632..d99509880b7 100644 --- a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs +++ b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs @@ -2,34 +2,80 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) IOFBF IOLBF IONBF cstdio setvbuf +// spell-checker:ignore (ToDO) IOFBF IOLBF IONBF setvbuf stderrp stdinp stdoutp -use cpp::cpp; +use ctor::ctor; use libc::{_IOFBF, _IOLBF, _IONBF, FILE, c_char, c_int, fileno, size_t}; use std::env; use std::ptr; -cpp! {{ - #include +// This runs automatically when the library is loaded via LD_PRELOAD +#[ctor] +fn init() { + unsafe { __stdbuf() }; +} - extern "C" { - void __stdbuf(void); +/// # Safety +/// This function is unsafe because it calls a C API +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __stdbuf_get_stdin() -> *mut FILE { + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + { + unsafe extern "C" { + fn __stdinp() -> *mut FILE; + } + unsafe { __stdinp() } + } - void __attribute((constructor)) - __stdbuf_init(void) { - __stdbuf(); + #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] + { + unsafe extern "C" { + static mut stdin: *mut FILE; } + unsafe { stdin } + } +} - FILE *__stdbuf_get_stdin() { return stdin; } - FILE *__stdbuf_get_stdout() { return stdout; } - FILE *__stdbuf_get_stderr() { return stderr; } +/// # Safety +/// This function is unsafe because it calls a C API +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __stdbuf_get_stdout() -> *mut FILE { + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + { + unsafe extern "C" { + fn __stdoutp() -> *mut FILE; + } + unsafe { __stdoutp() } } -}} -unsafe extern "C" { - fn __stdbuf_get_stdin() -> *mut FILE; - fn __stdbuf_get_stdout() -> *mut FILE; - fn __stdbuf_get_stderr() -> *mut FILE; + #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] + { + unsafe extern "C" { + static mut stdout: *mut FILE; + } + unsafe { stdout } + } +} + +/// # Safety +/// This function is unsafe because it calls a C API +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __stdbuf_get_stderr() -> *mut FILE { + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + { + unsafe extern "C" { + fn __stderrp() -> *mut FILE; + } + unsafe { __stderrp() } + } + + #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] + { + unsafe extern "C" { + static mut stderr: *mut FILE; + } + unsafe { stderr } + } } fn set_buffer(stream: *mut FILE, value: &str) { @@ -61,7 +107,9 @@ fn set_buffer(stream: *mut FILE, value: &str) { } /// # Safety -/// ToDO ... (safety note) +/// This function is intended to be called automatically when the library is loaded via LD_PRELOAD. +/// It assumes that the standard streams are valid and that calling setvbuf on them is safe. +/// The caller must ensure this function is only called in a compatible runtime environment. #[unsafe(no_mangle)] pub unsafe extern "C" fn __stdbuf() { if let Ok(val) = env::var("_STDBUF_E") { diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 3b5c3fb9dbb..fa6b2e6302d 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -31,8 +31,18 @@ mod options { pub const COMMAND: &str = "command"; } +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly" +))] const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); +#[cfg(target_vendor = "apple")] +const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.dylib")); + enum BufferType { Default, Line, diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index cbd0a5c2b6b..dec6bfe649c 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -3,12 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore dyld dylib setvbuf -#[cfg(all( - not(target_os = "windows"), - not(target_os = "openbsd"), - not(target_os = "macos") -))] -use std::process::Command; use uutests::new_ucmd; #[cfg(not(target_os = "windows"))] use uutests::util::TestScenario; @@ -37,7 +31,15 @@ fn test_no_such() { .stderr_contains("No such file or directory"); } -#[cfg(all(not(target_os = "windows"), not(target_os = "openbsd")))] +// Disabled on x86_64-unknown-linux-musl because the cross-rs Docker image for this target +// does not provide musl-compiled system utilities (like head), leading to dynamic linker errors +// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD. +#[cfg(all( + not(target_os = "windows"), + not(target_os = "freebsd"), + not(target_os = "openbsd"), + not(all(target_arch = "x86_64", target_env = "musl")) +))] #[test] fn test_stdbuf_unbuffered_stdout() { // This is a basic smoke test @@ -51,7 +53,15 @@ fn test_stdbuf_unbuffered_stdout() { .stdout_is("The quick brown fox jumps over the lazy dog."); } -#[cfg(all(not(target_os = "windows"), not(target_os = "openbsd")))] +// Disabled on x86_64-unknown-linux-musl because the cross-rs Docker image for this target +// does not provide musl-compiled system utilities (like head), leading to dynamic linker errors +// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD. +#[cfg(all( + not(target_os = "windows"), + not(target_os = "freebsd"), + not(target_os = "openbsd"), + not(all(target_arch = "x86_64", target_env = "musl")) +))] #[test] fn test_stdbuf_line_buffered_stdout() { // Note: This test only verifies that stdbuf does not crash and that output is passed through as expected @@ -75,7 +85,15 @@ fn test_stdbuf_no_buffer_option_fails() { .stderr_contains("the following required arguments were not provided:"); } -#[cfg(all(not(target_os = "windows"), not(target_os = "openbsd")))] +// Disabled on x86_64-unknown-linux-musl because the cross-rs Docker image for this target +// does not provide musl-compiled system utilities (like tail), leading to dynamic linker errors +// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD. +#[cfg(all( + not(target_os = "windows"), + not(target_os = "freebsd"), + not(target_os = "openbsd"), + not(all(target_arch = "x86_64", target_env = "musl")) +))] #[test] fn test_stdbuf_trailing_var_arg() { new_ucmd!() @@ -119,16 +137,25 @@ fn test_stdbuf_invalid_mode_fails() { } } -// macos uses DYLD_PRINT_LIBRARIES, not LD_DEBUG, so disable on macos at the moment +// macos uses DYLD_PRINT_LIBRARIES, not LD_DEBUG, so disable on macos at the moment. +// On modern Android (Bionic, API 37+), LD_DEBUG is supported and behaves similarly to glibc. +// On older Android versions (Bionic, API < 37), LD_DEBUG uses integer values instead of strings +// and is sometimes disabled. Disable test on Android for now. +// musl libc dynamic loader does not support LD_DEBUG, so disable on musl targets as well. #[cfg(all( not(target_os = "windows"), not(target_os = "openbsd"), - not(target_os = "macos") + not(target_os = "macos"), + not(target_os = "android"), + not(target_env = "musl") ))] #[test] -fn test_setvbuf_resolution() { - // Run a simple program with LD_DEBUG=symbols to see which setvbuf is being used - // Written in a way that it can run with cross-rs and be used as regression test +fn test_libstdbuf_preload() { + use std::process::Command; + + // Run a simple program with LD_DEBUG=symbols to verify that libstdbuf is loaded correctly + // and that there are no architecture mismatches when preloading the library. + // Note: This does not check which setvbuf implementation is used, as our libstdbuf does not override setvbuf. // for https://github.com/uutils/coreutils/issues/6591 let scene = TestScenario::new(util_name!()); @@ -149,13 +176,24 @@ fn test_setvbuf_resolution() { let uutils_debug = String::from_utf8_lossy(&uutils_output.stdout); - // Check if libstdbuf.so / libstdbuf.dylib is in the lookup path. The log should contain something like this: - // "symbol=setvbuf; lookup in file=/tmp/.tmp0mfmCg/libstdbuf.so [0]" - let libstdbuf_in_path = uutils_debug.contains("symbol=setvbuf") - && uutils_debug.contains("lookup in file=") - && uutils_debug.contains("libstdbuf"); - - // Check for lack of architecture mismatch error. The potential error message is: + // Check if libstdbuf.so / libstdbuf.dylib is in the lookup path. + // With GLIBC, the log should contain something like: + // "symbol=setvbuf; lookup in file=/tmp/.tmp0mfmCg/libstdbuf.so [0]" + // With FreeBSD dynamic loader, the log should contain something like: + // cspell:disable-next-line + // "calling init function for /tmp/.tmpu11rhP/libstdbuf.so at ..." + let libstdbuf_in_path = if cfg!(target_os = "freebsd") { + uutils_debug + .lines() + .any(|line| line.contains("calling init function") && line.contains("libstdbuf")) + } else { + uutils_debug.contains("symbol=setvbuf") + && uutils_debug.contains("lookup in file=") + && uutils_debug.contains("libstdbuf") + }; + + // Check for lack of architecture mismatch error. The potential error message with GLIBC is: + // cspell:disable-next-line // "ERROR: ld.so: object '/tmp/.tmpCLq8jl/libstdbuf.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored." let arch_mismatch_line = uutils_debug .lines() From b2893db5a45a465469539a531f74ad2bdccd3645 Mon Sep 17 00:00:00 2001 From: Charlie-Zheng Date: Mon, 2 Jun 2025 03:14:49 -0600 Subject: [PATCH 089/139] sort: Implement the last changes to make sort-files0-from pass (#8011) --- src/uu/sort/src/merge.rs | 4 +- src/uu/sort/src/sort.rs | 118 +++++++++++++++++++++-------- tests/by-util/test_sort.rs | 149 +++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 31 deletions(-) diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index fb7e2c8bf11..bbd0c2c839e 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -13,7 +13,7 @@ use std::{ cmp::Ordering, - ffi::OsString, + ffi::{OsStr, OsString}, fs::{self, File}, io::{BufWriter, Read, Write}, iter, @@ -38,7 +38,7 @@ use crate::{ /// and replace its occurrences in the inputs with that copy. fn replace_output_file_in_input_files( files: &mut [OsString], - output: Option<&str>, + output: Option<&OsStr>, tmp_dir: &mut TmpDirWrapper, ) -> UResult<()> { let mut copy: Option = None; diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 2e44fba2955..7de7eb1edcb 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -131,7 +131,10 @@ pub enum SortError { }, #[error("open failed: {}: {}", .path.maybe_quote(), strip_errno(.error))] - OpenFailed { path: String, error: std::io::Error }, + OpenFailed { + path: PathBuf, + error: std::io::Error, + }, #[error("failed to parse key {}: {}", .key.quote(), .msg)] ParseKeyError { key: String, msg: String }, @@ -154,11 +157,23 @@ pub enum SortError { #[error("cannot create temporary file in '{}':", .path.display())] TmpFileCreationFailed { path: PathBuf }, + #[error("extra operand '{}'\nfile operands cannot be combined with --files0-from\nTry '{} --help' for more information.", .file.display(), uucore::execution_phrase())] + FileOperandsCombined { file: PathBuf }, + #[error("{error}")] Uft8Error { error: Utf8Error }, #[error("multiple output files specified")] MultipleOutputFiles, + + #[error("when reading file names from stdin, no file name of '-' allowed")] + MinusInStdIn, + + #[error("no input from '{}'", .file.display())] + EmptyInputFile { file: PathBuf }, + + #[error("{}:{}: invalid zero-length file name", .file.display(), .line_num)] + ZeroLengthFileName { file: PathBuf, line_num: usize }, } impl UError for SortError { @@ -204,24 +219,25 @@ impl SortMode { } pub struct Output { - file: Option<(String, File)>, + file: Option<(OsString, File)>, } impl Output { - fn new(name: Option<&str>) -> UResult { + fn new(name: Option<&OsStr>) -> UResult { let file = if let Some(name) = name { + let path = Path::new(name); // This is different from `File::create()` because we don't truncate the output yet. // This allows using the output file as an input file. #[allow(clippy::suspicious_open_options)] let file = OpenOptions::new() .write(true) .create(true) - .open(name) + .open(path) .map_err(|e| SortError::OpenFailed { - path: name.to_owned(), + path: path.to_owned(), error: e, })?; - Some((name.to_owned(), file)) + Some((name.to_os_string(), file)) } else { None }; @@ -239,9 +255,9 @@ impl Output { }) } - fn as_output_name(&self) -> Option<&str> { + fn as_output_name(&self) -> Option<&OsStr> { match &self.file { - Some((name, _file)) => Some(name), + Some((name, _file)) => Some(name.as_os_str()), None => None, } } @@ -1016,6 +1032,8 @@ fn get_rlimit() -> UResult { } } +const STDIN_FILE: &str = "-"; + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -1039,7 +1057,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Prevent -o/--output to be specified multiple times if matches - .get_occurrences::(options::OUTPUT) + .get_occurrences::(options::OUTPUT) .is_some_and(|out| out.len() > 1) { return Err(SortError::MultipleOutputFiles.into()); @@ -1049,21 +1067,45 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // check whether user specified a zero terminated list of files for input, otherwise read files from args let mut files: Vec = if matches.contains_id(options::FILES0_FROM) { - let files0_from: Vec = matches - .get_many::(options::FILES0_FROM) - .map(|v| v.map(ToOwned::to_owned).collect()) + let files0_from: PathBuf = matches + .get_one::(options::FILES0_FROM) + .map(|v| v.into()) .unwrap_or_default(); + // Cannot combine FILES with FILES0_FROM + if let Some(s) = matches.get_one::(options::FILES) { + return Err(SortError::FileOperandsCombined { file: s.into() }.into()); + } + let mut files = Vec::new(); - for path in &files0_from { - let reader = open(path)?; - let buf_reader = BufReader::new(reader); - for line in buf_reader.split(b'\0').flatten() { - files.push(OsString::from( - std::str::from_utf8(&line) - .expect("Could not parse string from zero terminated input."), - )); + + // sort errors with "cannot open: [...]" instead of "cannot read: [...]" here + let reader = open_with_open_failed_error(&files0_from)?; + let buf_reader = BufReader::new(reader); + for (line_num, line) in buf_reader.split(b'\0').flatten().enumerate() { + let f = std::str::from_utf8(&line) + .expect("Could not parse string from zero terminated input."); + match f { + STDIN_FILE => { + return Err(SortError::MinusInStdIn.into()); + } + "" => { + return Err(SortError::ZeroLengthFileName { + file: files0_from, + line_num: line_num + 1, + } + .into()); + } + _ => {} } + + files.push(OsString::from( + std::str::from_utf8(&line) + .expect("Could not parse string from zero terminated input."), + )); + } + if files.is_empty() { + return Err(SortError::EmptyInputFile { file: files0_from }.into()); } files } else { @@ -1212,7 +1254,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if files.is_empty() { /* if no file, default to stdin */ - files.push("-".to_string().into()); + files.push(OsString::from(STDIN_FILE)); } else if settings.check && files.len() != 1 { return Err(UUsageError::new( 2, @@ -1282,8 +1324,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let output = Output::new( matches - .get_one::(options::OUTPUT) - .map(|s| s.as_str()), + .get_one::(options::OUTPUT) + .map(|s| s.as_os_str()), )?; settings.init_precomputed(); @@ -1437,6 +1479,7 @@ pub fn uu_app() -> Command { .short('o') .long(options::OUTPUT) .help("write output to FILENAME instead of stdout") + .value_parser(ValueParser::os_string()) .value_name("FILENAME") .value_hint(clap::ValueHint::FilePath) // To detect multiple occurrences and raise an error @@ -1522,9 +1565,8 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::FILES0_FROM) .long(options::FILES0_FROM) - .help("read input from the files specified by NUL-terminated NUL_FILES") - .value_name("NUL_FILES") - .action(ArgAction::Append) + .help("read input from the files specified by NUL-terminated NUL_FILE") + .value_name("NUL_FILE") .value_parser(ValueParser::os_string()) .value_hint(clap::ValueHint::FilePath), ) @@ -1865,7 +1907,7 @@ fn print_sorted<'a, T: Iterator>>( ) -> UResult<()> { let output_name = output .as_output_name() - .unwrap_or("standard output") + .unwrap_or(OsStr::new("standard output")) .to_owned(); let ctx = || format!("write failed: {}", output_name.maybe_quote()); @@ -1879,13 +1921,12 @@ fn print_sorted<'a, T: Iterator>>( fn open(path: impl AsRef) -> UResult> { let path = path.as_ref(); - if path == "-" { + if path == STDIN_FILE { let stdin = stdin(); return Ok(Box::new(stdin) as Box); } let path = Path::new(path); - match File::open(path) { Ok(f) => Ok(Box::new(f) as Box), Err(error) => Err(SortError::ReadFailed { @@ -1896,6 +1937,25 @@ fn open(path: impl AsRef) -> UResult> { } } +fn open_with_open_failed_error(path: impl AsRef) -> UResult> { + // On error, returns an OpenFailed error instead of a ReadFailed error + let path = path.as_ref(); + if path == STDIN_FILE { + let stdin = stdin(); + return Ok(Box::new(stdin) as Box); + } + + let path = Path::new(path); + match File::open(path) { + Ok(f) => Ok(Box::new(f) as Box), + Err(error) => Err(SortError::OpenFailed { + path: path.to_owned(), + error, + } + .into()), + } +} + fn format_error_message(error: &ParseSizeError, s: &str, option: &str) -> String { // NOTE: // GNU's sort echos affected flag, -S or --buffer-size, depending on user's selection diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 988360ca696..0e20f1c9ee1 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1354,3 +1354,152 @@ fn test_multiple_output_files() { .fails_with_code(2) .stderr_is("sort: multiple output files specified\n"); } + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "f-extra-arg" +fn test_files0_from_extra_arg() { + new_ucmd!() + .args(&["--files0-from", "-", "foo"]) + .fails_with_code(2) + .stderr_contains( + "sort: extra operand 'foo'\nfile operands cannot be combined with --files0-from\n", + ) + .no_stdout(); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "missing" +fn test_files0_from_missing() { + new_ucmd!() + .args(&["--files0-from", "missing_file"]) + .fails_with_code(2) + .stderr_only( + #[cfg(not(windows))] + "sort: open failed: missing_file: No such file or directory\n", + #[cfg(windows)] + "sort: open failed: missing_file: The system cannot find the file specified.\n", + ); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "minus-in-stdin" +fn test_files0_from_minus_in_stdin() { + new_ucmd!() + .args(&["--files0-from", "-"]) + .pipe_in("-") + .fails_with_code(2) + .stderr_only("sort: when reading file names from stdin, no file name of '-' allowed\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "empty" +fn test_files0_from_empty() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + scene + .ucmd() + .args(&["--files0-from", "file"]) + .fails_with_code(2) + .stderr_only("sort: no input from 'file'\n"); +} + +#[cfg(target_os = "linux")] +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "empty-non-regular" +fn test_files0_from_empty_non_regular() { + new_ucmd!() + .args(&["--files0-from", "/dev/null"]) + .fails_with_code(2) + .stderr_only("sort: no input from '/dev/null'\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "nul-1" +fn test_files0_from_nul() { + new_ucmd!() + .args(&["--files0-from", "-"]) + .pipe_in("\0") + .fails_with_code(2) + .stderr_only("sort: -:1: invalid zero-length file name\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "nul-2" +fn test_files0_from_nul2() { + new_ucmd!() + .args(&["--files0-from", "-"]) + .pipe_in("\0\0") + .fails_with_code(2) + .stderr_only("sort: -:1: invalid zero-length file name\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "1" +fn test_files0_from_1() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + at.append("file", "a"); + scene + .ucmd() + .args(&["--files0-from", "-"]) + .pipe_in("file") + .succeeds() + .stdout_only("a\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "1a" +fn test_files0_from_1a() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + at.append("file", "a"); + scene + .ucmd() + .args(&["--files0-from", "-"]) + .pipe_in("file\0") + .succeeds() + .stdout_only("a\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "2" +fn test_files0_from_2() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + at.append("file", "a"); + scene + .ucmd() + .args(&["--files0-from", "-"]) + .pipe_in("file\0file") + .succeeds() + .stdout_only("a\na\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "2a" +fn test_files0_from_2a() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + at.append("file", "a"); + scene + .ucmd() + .args(&["--files0-from", "-"]) + .pipe_in("file\0file\0") + .succeeds() + .stdout_only("a\na\n"); +} + +#[test] +// Test for GNU tests/sort/sort-files0-from.pl "zero-len" +fn test_files0_from_zero_length() { + new_ucmd!() + .args(&["--files0-from", "-"]) + .pipe_in("g\0\0b\0\0") + .fails_with_code(2) + .stderr_only("sort: -:2: invalid zero-length file name\n"); +} From dfc4e3efe5558cfb89fce34cb72335a4c9dad6b1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Jun 2025 16:12:03 +0200 Subject: [PATCH 090/139] rm: do "early return" earlier in uumain --- src/uu/rm/src/rm.rs | 115 ++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 863336f5d14..a72857131f3 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -119,6 +119,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let force_flag = matches.get_flag(OPT_FORCE); + if files.is_empty() && !force_flag { + // Still check by hand and not use clap + // Because "rm -f" is a thing + return Err(UUsageError::new(1, "missing operand")); + } + // If -f(--force) is before any -i (or variants) we want prompts else no prompts let force_prompt_never: bool = force_flag && { let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); @@ -130,71 +136,66 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }) }; - if files.is_empty() && !force_flag { - // Still check by hand and not use clap - // Because "rm -f" is a thing - return Err(UUsageError::new(1, "missing operand")); - } else { - let options = Options { - force: force_flag, - interactive: { - if force_prompt_never { - InteractiveMode::Never - } else if matches.get_flag(OPT_PROMPT) { - InteractiveMode::Always - } else if matches.get_flag(OPT_PROMPT_MORE) { - InteractiveMode::Once - } else if matches.contains_id(OPT_INTERACTIVE) { - match matches.get_one::(OPT_INTERACTIVE).unwrap().as_str() { - "never" => InteractiveMode::Never, - "once" => InteractiveMode::Once, - "always" => InteractiveMode::Always, - val => { - return Err(USimpleError::new( - 1, - format!("Invalid argument to interactive ({val})"), - )); - } + let options = Options { + force: force_flag, + interactive: { + if force_prompt_never { + InteractiveMode::Never + } else if matches.get_flag(OPT_PROMPT) { + InteractiveMode::Always + } else if matches.get_flag(OPT_PROMPT_MORE) { + InteractiveMode::Once + } else if matches.contains_id(OPT_INTERACTIVE) { + match matches.get_one::(OPT_INTERACTIVE).unwrap().as_str() { + "never" => InteractiveMode::Never, + "once" => InteractiveMode::Once, + "always" => InteractiveMode::Always, + val => { + return Err(USimpleError::new( + 1, + format!("Invalid argument to interactive ({val})"), + )); } - } else { - InteractiveMode::PromptProtected } - }, - one_fs: matches.get_flag(OPT_ONE_FILE_SYSTEM), - preserve_root: !matches.get_flag(OPT_NO_PRESERVE_ROOT), - recursive: matches.get_flag(OPT_RECURSIVE), - dir: matches.get_flag(OPT_DIR), - verbose: matches.get_flag(OPT_VERBOSE), - __presume_input_tty: if matches.get_flag(PRESUME_INPUT_TTY) { - Some(true) } else { - None + InteractiveMode::PromptProtected + } + }, + one_fs: matches.get_flag(OPT_ONE_FILE_SYSTEM), + preserve_root: !matches.get_flag(OPT_NO_PRESERVE_ROOT), + recursive: matches.get_flag(OPT_RECURSIVE), + dir: matches.get_flag(OPT_DIR), + verbose: matches.get_flag(OPT_VERBOSE), + __presume_input_tty: if matches.get_flag(PRESUME_INPUT_TTY) { + Some(true) + } else { + None + }, + }; + if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) { + let msg: String = format!( + "remove {} {}{}", + files.len(), + if files.len() > 1 { + "arguments" + } else { + "argument" }, - }; - if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) { - let msg: String = format!( - "remove {} {}{}", - files.len(), - if files.len() > 1 { - "arguments" - } else { - "argument" - }, - if options.recursive { - " recursively?" - } else { - "?" - } - ); - if !prompt_yes!("{msg}") { - return Ok(()); + if options.recursive { + " recursively?" + } else { + "?" } + ); + if !prompt_yes!("{msg}") { + return Ok(()); } + } - if remove(&files, &options) { - return Err(1.into()); - } + if remove(&files, &options) { + return Err(1.into()); } + Ok(()) } From 25c7ed5b1e8213312148a951673804cbe579ad6d Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Jun 2025 16:19:33 +0200 Subject: [PATCH 091/139] rm: set "after help" in uu_app instead of uumain --- src/uu/rm/src/rm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a72857131f3..3364007b17b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -110,7 +110,7 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from(args)?; let files: Vec<&OsStr> = matches .get_many::(ARG_FILES) @@ -204,6 +204,7 @@ pub fn uu_app() -> Command { .version(uucore::crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) + .after_help(AFTER_HELP) .infer_long_args(true) .args_override_self(true) .arg( From 3eede813db9259e57cfee6cb254f312d73aa04c0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Jun 2025 16:22:40 +0200 Subject: [PATCH 092/139] rm: remove some unnecessary type information --- src/uu/rm/src/rm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 3364007b17b..3f48e311b45 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -112,7 +112,7 @@ static ARG_FILES: &str = "files"; pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; - let files: Vec<&OsStr> = matches + let files: Vec<_> = matches .get_many::(ARG_FILES) .map(|v| v.map(OsString::as_os_str).collect()) .unwrap_or_default(); @@ -126,7 +126,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } // If -f(--force) is before any -i (or variants) we want prompts else no prompts - let force_prompt_never: bool = force_flag && { + let force_prompt_never = force_flag && { let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); ![OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] .iter() From 862565cd1a7f75c1193f2bdfea0e8705ca4c2945 Mon Sep 17 00:00:00 2001 From: Dylan Ullrich Date: Mon, 2 Jun 2025 22:54:05 -0700 Subject: [PATCH 093/139] Merge pull request #8047 from eldyl/feat/add_wezterm_to_terms Feat/add wezterm to terms --- src/uucore/src/lib/features/colors.rs | 1 + tests/fixtures/dircolors/internal.expected | 1 + 2 files changed, 2 insertions(+) diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 885ae2fe967..2792de7c5a0 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -38,6 +38,7 @@ pub static TERMS: &[&str] = &[ "terminator", "tmux*", "vt100", + "wezterm*", "xterm*", ]; diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected index feea46455f4..38f43f19588 100644 --- a/tests/fixtures/dircolors/internal.expected +++ b/tests/fixtures/dircolors/internal.expected @@ -32,6 +32,7 @@ TERM st TERM terminator TERM tmux* TERM vt100 +TERM wezterm* TERM xterm* # Below are the color init strings for the basic file types. # One can use codes for 256 or more colors supported by modern terminals. From c939b6096bc6f448207bf7fe9f70c80b19070b7a Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 3 Jun 2025 09:33:31 +0200 Subject: [PATCH 094/139] uucore: locale: Use if let instead of match Recent cargo clippy prefers to use if let for single pattern. For some reason it only triggers on one of the LANG restore case though, but we can just fix them all. --- src/uucore/src/lib/mods/locale.rs | 42 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 858c0508922..7bd3c098899 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -739,13 +739,14 @@ invalid-syntax = This is { $missing assert_eq!(result.unwrap(), "es-ES"); // Restore original LANG value - match original_lang { - Some(val) => unsafe { + if let Some(val) = original_lang { + unsafe { env::set_var("LANG", val); - }, - None => unsafe { + } + } else { + unsafe { env::remove_var("LANG"); - }, + } } } @@ -764,11 +765,12 @@ invalid-syntax = This is { $missing assert_eq!(result.unwrap().to_string(), "en-US"); // Restore original LANG value - match original_lang { - Some(val) => unsafe { + if let Some(val) = original_lang { + unsafe { env::set_var("LANG", val); - }, - None => {} // Was already unset + } + } else { + {} // Was already unset } } @@ -791,13 +793,14 @@ invalid-syntax = This is { $missing assert_eq!(message, "Bonjour, le monde!"); // Restore original LANG value - match original_lang { - Some(val) => unsafe { + if let Some(val) = original_lang { + unsafe { env::set_var("LANG", val); - }, - None => unsafe { + } + } else { + unsafe { env::remove_var("LANG"); - }, + } } }) .join() @@ -823,13 +826,14 @@ invalid-syntax = This is { $missing assert_eq!(message, "Hello, world!"); // Restore original LANG value - match original_lang { - Some(val) => unsafe { + if let Some(val) = original_lang { + unsafe { env::set_var("LANG", val); - }, - None => unsafe { + } + } else { + unsafe { env::remove_var("LANG"); - }, + } } }) .join() From 0c9f2f11f4b9a69db432a1118892590f6e63f902 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 08:04:43 +0000 Subject: [PATCH 095/139] chore(deps): update rust crate bytecount to v0.6.9 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11db5d59acd..eb7cf43809d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,9 +262,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "byteorder" From bfbdd5275d4f2164f99cfea42073b0a68f79f520 Mon Sep 17 00:00:00 2001 From: Vikram Kangotra <61800198+vikram-kangotra@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:42:36 +0530 Subject: [PATCH 096/139] cp: migrate from quick-error to thiserror (#7989) * cp: migrate from quick-error to thiserror fixes: #7916 * Remove quick-error Now that we have migrated to thiserror, we can remove quick-error * cp: fix test failures * cp: fix fmt error --- Cargo.lock | 8 +- Cargo.toml | 1 - src/uu/cp/Cargo.toml | 2 +- src/uu/cp/src/copydir.rs | 4 +- src/uu/cp/src/cp.rs | 242 ++++++++++++++++----------- src/uu/cp/src/platform/linux.rs | 8 +- src/uu/cp/src/platform/macos.rs | 10 +- src/uu/cp/src/platform/other.rs | 8 +- src/uu/cp/src/platform/other_unix.rs | 9 +- 9 files changed, 171 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11db5d59acd..dcbb8c8112e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1849,12 +1849,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.40" @@ -2697,8 +2691,8 @@ dependencies = [ "indicatif", "libc", "linux-raw-sys", - "quick-error", "selinux", + "thiserror 2.0.12", "uucore", "walkdir", "xattr", diff --git a/Cargo.toml b/Cargo.toml index a69100feaac..88adba12652 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -322,7 +322,6 @@ parse_datetime = "0.9.0" phf = "0.11.2" phf_codegen = "0.11.2" platform-info = "2.0.3" -quick-error = "2.0.1" rand = { version = "0.9.0", features = ["small_rng"] } rand_core = "0.9.0" rayon = "1.10" diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index fd5b4696e03..2f57835907f 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -22,7 +22,6 @@ clap = { workspace = true } filetime = { workspace = true } libc = { workspace = true } linux-raw-sys = { workspace = true } -quick-error = { workspace = true } selinux = { workspace = true, optional = true } uucore = { workspace = true, features = [ "backup-control", @@ -37,6 +36,7 @@ uucore = { workspace = true, features = [ ] } walkdir = { workspace = true } indicatif = { workspace = true } +thiserror = { workspace = true } [target.'cfg(unix)'.dependencies] xattr = { workspace = true } diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index d2e367c5c19..be81b260f28 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -26,7 +26,7 @@ use uucore::uio_error; use walkdir::{DirEntry, WalkDir}; use crate::{ - CopyResult, Error, Options, aligned_ancestors, context_for, copy_attributes, copy_file, + CopyResult, CpError, Options, aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, }; @@ -266,7 +266,7 @@ fn copy_direntry( // TODO What other kinds of errors, if any, should // cause us to continue walking the directory? match err { - Error::IoErrContext(e, _) if e.kind() == io::ErrorKind::PermissionDenied => { + CpError::IoErrContext(e, _) if e.kind() == io::ErrorKind::PermissionDenied => { show!(uio_error!( e, "cannot open {} for reading", diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 06f0b79657d..7ca39f6e36a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -4,22 +4,22 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO -use quick_error::quick_error; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::ffi::OsString; +use std::fmt::Display; use std::fs::{self, Metadata, OpenOptions, Permissions}; -use std::io; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf, StripPrefixError}; +use std::{fmt, io}; #[cfg(all(unix, not(target_os = "android")))] use uucore::fsxattr::copy_xattrs; use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser}; use filetime::FileTime; use indicatif::{ProgressBar, ProgressStyle}; -use quick_error::ResultExt; +use thiserror::Error; use platform::copy_on_write; use uucore::display::Quotable; @@ -45,65 +45,94 @@ use crate::copydir::copy_directory; mod copydir; mod platform; -quick_error! { - #[derive(Debug)] - pub enum Error { - /// Simple io::Error wrapper - IoErr(err: io::Error) { from() source(err) display("{err}")} +#[derive(Debug, Error)] +pub enum CpError { + /// Simple io::Error wrapper + #[error("{0}")] + IoErr(#[from] io::Error), - /// Wrapper for io::Error with path context - IoErrContext(err: io::Error, path: String) { - display("{path}: {err}") - context(path: &'a str, err: io::Error) -> (err, path.to_owned()) - context(context: String, err: io::Error) -> (err, context) - source(err) - } + /// Wrapper for io::Error with path context + #[error("{1}: {0}")] + IoErrContext(io::Error, String), - /// General copy error - Error(err: String) { - display("{err}") - from(err: String) -> (err) - from(err: &'static str) -> (err.to_string()) - } + /// General copy error + #[error("{0}")] + Error(String), + + /// Represents the state when a non-fatal error has occurred + /// and not all files were copied. + #[error("Not all files were copied")] + NotAllFilesCopied, - /// Represents the state when a non-fatal error has occurred - /// and not all files were copied. - NotAllFilesCopied {} + /// Simple walkdir::Error wrapper + #[error("{0}")] + WalkDirErr(#[from] walkdir::Error), - /// Simple walkdir::Error wrapper - WalkDirErr(err: walkdir::Error) { from() display("{err}") source(err) } + /// Simple std::path::StripPrefixError wrapper + #[error(transparent)] + StripPrefixError(#[from] StripPrefixError), - /// Simple std::path::StripPrefixError wrapper - StripPrefixError(err: StripPrefixError) { from() } + /// Result of a skipped file + /// Currently happens when "no" is selected in interactive mode or when + /// `no-clobber` flag is set and destination is already present. + /// `exit with error` is used to determine which exit code should be returned. + #[error("Skipped copying file (exit with error = {0})")] + Skipped(bool), - /// Result of a skipped file - /// Currently happens when "no" is selected in interactive mode or when - /// `no-clobber` flag is set and destination is already present. - /// `exit with error` is used to determine which exit code should be returned. - Skipped(exit_with_error:bool) { } + /// Result of a skipped file + #[error("{0}")] + InvalidArgument(String), - /// Result of a skipped file - InvalidArgument(description: String) { display("{description}") } + /// All standard options are included as an an implementation + /// path, but those that are not implemented yet should return + /// a NotImplemented error. + #[error("Option '{0}' not yet implemented.")] + NotImplemented(String), - /// All standard options are included as an an implementation - /// path, but those that are not implemented yet should return - /// a NotImplemented error. - NotImplemented(opt: String) { display("Option '{opt}' not yet implemented.") } + /// Invalid arguments to backup + #[error(transparent)] + Backup(#[from] BackupError), + + #[error("'{}' is not a directory", .0.display())] + NotADirectory(PathBuf), +} - /// Invalid arguments to backup - Backup(description: String) { display("{description}\nTry '{} --help' for more information.", uucore::execution_phrase()) } +// Manual impl for &str +impl From<&'static str> for CpError { + fn from(s: &'static str) -> Self { + Self::Error(s.to_string()) + } +} + +impl From for CpError { + fn from(s: String) -> Self { + Self::Error(s) + } +} - NotADirectory(path: PathBuf) { display("'{}' is not a directory", path.display()) } +#[derive(Debug)] +pub struct BackupError(String); + +impl Display for BackupError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "cp: {}\nTry '{} --help' for more information.", + self.0, + uucore::execution_phrase() + ) } } -impl UError for Error { +impl std::error::Error for BackupError {} + +impl UError for CpError { fn code(&self) -> i32 { EXIT_ERR } } -pub type CopyResult = Result; +pub type CopyResult = Result; /// Specifies how to overwrite files. #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] @@ -803,7 +832,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match error { // Error::NotAllFilesCopied is non-fatal, but the error // code should still be EXIT_ERR as does GNU cp - Error::NotAllFilesCopied => {} + CpError::NotAllFilesCopied => {} // Else we caught a fatal bubbled-up error, log it to stderr _ => show_error!("{error}"), }; @@ -940,7 +969,7 @@ impl Attributes { } } - pub fn parse_iter(values: impl Iterator) -> Result + pub fn parse_iter(values: impl Iterator) -> CopyResult where T: AsRef, { @@ -953,7 +982,7 @@ impl Attributes { /// Tries to match string containing a parameter to preserve with the corresponding entry in the /// Attributes struct. - fn parse_single_string(value: &str) -> Result { + fn parse_single_string(value: &str) -> CopyResult { let value = value.to_lowercase(); if value == "all" { @@ -970,7 +999,7 @@ impl Attributes { "link" | "links" => &mut new.links, "xattr" => &mut new.xattr, _ => { - return Err(Error::InvalidArgument(format!( + return Err(CpError::InvalidArgument(format!( "invalid attribute {}", value.quote() ))); @@ -998,14 +1027,14 @@ impl Options { && matches.value_source(not_implemented_opt) == Some(clap::parser::ValueSource::CommandLine) { - return Err(Error::NotImplemented(not_implemented_opt.to_string())); + return Err(CpError::NotImplemented(not_implemented_opt.to_string())); } } let recursive = matches.get_flag(options::RECURSIVE) || matches.get_flag(options::ARCHIVE); let backup_mode = match backup_control::determine_backup_mode(matches) { - Err(e) => return Err(Error::Backup(format!("{e}"))), + Err(e) => return Err(CpError::Backup(BackupError(format!("{e}")))), Ok(mode) => mode, }; let update_mode = update_control::determine_update_mode(matches); @@ -1015,7 +1044,7 @@ impl Options { .get_one::(update_control::arguments::OPT_UPDATE) .is_some_and(|v| v == "none" || v == "none-fail") { - return Err(Error::InvalidArgument( + return Err(CpError::InvalidArgument( "--backup is mutually exclusive with -n or --update=none-fail".to_string(), )); } @@ -1032,7 +1061,7 @@ impl Options { if let Some(dir) = &target_dir { if !dir.is_dir() { - return Err(Error::NotADirectory(dir.clone())); + return Err(CpError::NotADirectory(dir.clone())); } }; // cp follows POSIX conventions for overriding options such as "-a", @@ -1120,7 +1149,7 @@ impl Options { #[cfg(not(feature = "selinux"))] if let Preserve::Yes { required } = attributes.context { let selinux_disabled_error = - Error::Error("SELinux was not enabled during the compile time!".to_string()); + CpError::Error("SELinux was not enabled during the compile time!".to_owned()); if required { return Err(selinux_disabled_error); } else { @@ -1163,7 +1192,7 @@ impl Options { "auto" => ReflinkMode::Auto, "never" => ReflinkMode::Never, value => { - return Err(Error::InvalidArgument(format!( + return Err(CpError::InvalidArgument(format!( "invalid argument {} for \'reflink\'", value.quote() ))); @@ -1180,7 +1209,7 @@ impl Options { "auto" => SparseMode::Auto, "never" => SparseMode::Never, _ => { - return Err(Error::InvalidArgument(format!( + return Err(CpError::InvalidArgument(format!( "invalid argument {val} for \'sparse\'" ))); } @@ -1298,14 +1327,14 @@ fn parse_path_args( } /// When handling errors, we don't always want to show them to the user. This function handles that. -fn show_error_if_needed(error: &Error) { +fn show_error_if_needed(error: &CpError) { match error { // When using --no-clobber, we don't want to show // an error message - Error::NotAllFilesCopied => { + CpError::NotAllFilesCopied => { // Need to return an error code } - Error::Skipped(_) => { + CpError::Skipped(_) => { // touch a b && echo "n"|cp -i a b && echo $? // should return an error from GNU 9.2 } @@ -1382,7 +1411,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult // There is already a file and it isn't a symlink (managed in a different place) if copied_destinations.contains(&dest) && options.backup != BackupMode::Numbered { // If the target file was already created in this cp call, do not overwrite - return Err(Error::Error(format!( + return Err(CpError::Error(format!( "will not overwrite just-created '{}' with '{}'", dest.display(), source.display() @@ -1401,7 +1430,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult &mut copied_files, ) { show_error_if_needed(&error); - if !matches!(error, Error::Skipped(false)) { + if !matches!(error, CpError::Skipped(false)) { non_fatal_errors = true; } } else { @@ -1416,7 +1445,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult } if non_fatal_errors { - Err(Error::NotAllFilesCopied) + Err(CpError::NotAllFilesCopied) } else { Ok(()) } @@ -1564,7 +1593,7 @@ impl OverwriteMode { if debug { println!("skipped {}", path.quote()); } - Err(Error::Skipped(false)) + Err(CpError::Skipped(false)) } Self::Interactive(_) => { let prompt_yes_result = if let Some((octal, human_readable)) = @@ -1581,7 +1610,7 @@ impl OverwriteMode { if prompt_yes_result { Ok(()) } else { - Err(Error::Skipped(true)) + Err(CpError::Skipped(true)) } } Self::Clobber(_) => Ok(()), @@ -1650,7 +1679,8 @@ pub(crate) fn copy_attributes( attributes: &Attributes, ) -> CopyResult<()> { let context = &*format!("{} -> {}", source.quote(), dest.quote()); - let source_metadata = fs::symlink_metadata(source).context(context)?; + let source_metadata = + fs::symlink_metadata(source).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; // Ownership must be changed first to avoid interfering with mode change. #[cfg(unix)] @@ -1665,7 +1695,9 @@ pub(crate) fn copy_attributes( // gnu compatibility: cp doesn't report an error if it fails to set the ownership. let _ = wrap_chown( dest, - &dest.symlink_metadata().context(context)?, + &dest + .symlink_metadata() + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?, Some(dest_uid), Some(dest_gid), false, @@ -1684,12 +1716,13 @@ pub(crate) fn copy_attributes( // do nothing, since every symbolic link has the same // permissions. if !dest.is_symlink() { - fs::set_permissions(dest, source_metadata.permissions()).context(context)?; + fs::set_permissions(dest, source_metadata.permissions()) + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; // FIXME: Implement this for windows as well #[cfg(feature = "feat_acl")] exacl::getfacl(source, None) .and_then(|acl| exacl::setfacl(&[dest], &acl, None)) - .map_err(|err| Error::Error(err.to_string()))?; + .map_err(|err| CpError::Error(err.to_string()))?; } Ok(()) @@ -1713,14 +1746,14 @@ pub(crate) fn copy_attributes( if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) { if let Some(context) = context { if let Err(e) = context.set_for_path(dest, false, false) { - return Err(Error::Error(format!( + return Err(CpError::Error(format!( "failed to set the security context of {}: {e}", dest.display() ))); } } } else { - return Err(Error::Error(format!( + return Err(CpError::Error(format!( "failed to get security context of {}", source.display() ))); @@ -1760,19 +1793,29 @@ fn symlink_file( ) -> CopyResult<()> { #[cfg(not(windows))] { - std::os::unix::fs::symlink(source, dest).context(format!( - "cannot create symlink {} to {}", - get_filename(dest).unwrap_or("invalid file name").quote(), - get_filename(source).unwrap_or("invalid file name").quote() - ))?; + std::os::unix::fs::symlink(source, dest).map_err(|e| { + CpError::IoErrContext( + e, + format!( + "cannot create symlink {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ), + ) + })?; } #[cfg(windows)] { - std::os::windows::fs::symlink_file(source, dest).context(format!( - "cannot create symlink {} to {}", - get_filename(dest).unwrap_or("invalid file name").quote(), - get_filename(source).unwrap_or("invalid file name").quote() - ))?; + std::os::windows::fs::symlink_file(source, dest).map_err(|e| { + CpError::IoErrContext( + e, + format!( + "cannot create symlink {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ), + ) + })?; } if let Ok(file_info) = FileInformation::from_path(dest, false) { symlinked_files.insert(file_info); @@ -1867,7 +1910,7 @@ fn handle_existing_dest( if options.debug { println!("skipped {}", dest.quote()); } - return Err(Error::Skipped(false)); + return Err(CpError::Skipped(false)); } if options.update != UpdateMode::IfOlder { @@ -1947,7 +1990,7 @@ fn delete_dest_if_needed_and_allowed( &FileInformation::from_path( source, options.dereference(source_in_command_line) - ).context(format!("cannot stat {}", source.quote()))? + ).map_err(|e| CpError::IoErrContext(e, format!("cannot stat {}", source.quote())))? ) } } @@ -2088,11 +2131,16 @@ fn handle_copy_mode( } else { fs::hard_link(source, dest) } - .context(format!( - "cannot create hard link {} to {}", - get_filename(dest).unwrap_or("invalid file name").quote(), - get_filename(source).unwrap_or("invalid file name").quote() - ))?; + .map_err(|e| { + CpError::IoErrContext( + e, + format!( + "cannot create hard link {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ), + ) + })?; } CopyMode::Copy => { copy_helper( @@ -2137,7 +2185,10 @@ fn handle_copy_mode( return Ok(PerformedAction::Skipped); } UpdateMode::NoneFail => { - return Err(Error::Error(format!("not replacing '{}'", dest.display()))); + return Err(CpError::Error(format!( + "not replacing '{}'", + dest.display() + ))); } UpdateMode::IfOlder => { let dest_metadata = fs::symlink_metadata(dest)?; @@ -2209,7 +2260,10 @@ fn calculate_dest_permissions( context: &str, ) -> CopyResult { if dest.exists() { - Ok(dest.symlink_metadata().context(context)?.permissions()) + Ok(dest + .symlink_metadata() + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))? + .permissions()) } else { #[cfg(unix)] { @@ -2259,7 +2313,7 @@ fn copy_file( .map(|info| symlinked_files.contains(&info)) .unwrap_or(false) { - return Err(Error::Error(format!( + return Err(CpError::Error(format!( "will not copy '{}' through just-created symlink '{}'", source.display(), dest.display() @@ -2269,7 +2323,7 @@ fn copy_file( // Example: "cp file1 dir1/file1 tmp" where "tmp" is a directory containing a symlink "file1" pointing to a file named "foo". // foo will contain the contents of "file1" and "dir1/file1" will not be copied over to "tmp/file1" if copied_destinations.contains(dest) { - return Err(Error::Error(format!( + return Err(CpError::Error(format!( "will not copy '{}' through just-created symlink '{}'", source.display(), dest.display() @@ -2286,7 +2340,7 @@ fn copy_file( && !is_symlink_loop(dest) && std::env::var_os("POSIXLY_CORRECT").is_none() { - return Err(Error::Error(format!( + return Err(CpError::Error(format!( "not writing through dangling symlink '{}'", dest.display() ))); @@ -2368,7 +2422,7 @@ fn copy_file( // in the destination tree. if let Some(new_source) = copied_files.get( &FileInformation::from_path(source, options.dereference(source_in_command_line)) - .context(format!("cannot stat {}", source.quote()))?, + .map_err(|e| CpError::IoErrContext(e, format!("cannot stat {}", source.quote())))?, ) { fs::hard_link(new_source, dest)?; @@ -2462,7 +2516,7 @@ fn copy_file( if let Err(e) = uucore::selinux::set_selinux_security_context(dest, options.context.as_ref()) { - return Err(Error::Error(format!("SELinux error: {}", e))); + return Err(CpError::Error(format!("SELinux error: {}", e))); } } @@ -2542,7 +2596,7 @@ fn copy_helper( } if path_ends_with_terminator(dest) && !dest.is_dir() { - return Err(Error::NotADirectory(dest.to_path_buf())); + return Err(CpError::NotADirectory(dest.to_path_buf())); } if source_is_fifo && options.recursive && !options.copy_contents { diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index 9bf257f8276..c4606d43649 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -14,11 +14,11 @@ use std::os::unix::io::AsRawFd; use std::path::Path; use uucore::buf_copy; -use quick_error::ResultExt; - use uucore::mode::get_umask; -use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +use crate::{ + CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode, +}; /// The fallback behavior for [`clone`] on failed system call. #[derive(Clone, Copy)] @@ -404,7 +404,7 @@ pub(crate) fn copy_on_write( return Err("`--reflink=always` can be used only with --sparse=auto".into()); } }; - result.context(context)?; + result.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; Ok(copy_debug) } diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 35879c29df7..be2e43a9562 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -9,11 +9,12 @@ use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; -use quick_error::ResultExt; use uucore::buf_copy; use uucore::mode::get_umask; -use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +use crate::{ + CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode, +}; /// Copies `source` to `dest` using copy-on-write if possible. /// @@ -104,14 +105,15 @@ pub(crate) fn copy_on_write( let context = buf_copy::copy_stream(&mut src_file, &mut dst_file) .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other)) - .context(context)?; + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; if source_is_fifo { dst_file.set_permissions(src_file.metadata()?.permissions())?; } context } else { - fs::copy(source, dest).context(context)? + fs::copy(source, dest) + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))? } } }; diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 7ca1a5ded7c..9df2f99939a 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -6,9 +6,9 @@ use std::fs; use std::path::Path; -use quick_error::ResultExt; - -use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +use crate::{ + CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode, +}; /// Copies `source` to `dest` for systems without copy-on-write pub(crate) fn copy_on_write( @@ -31,7 +31,7 @@ pub(crate) fn copy_on_write( reflink: OffloadReflinkDebug::Unsupported, sparse_detection: SparseDebug::Unsupported, }; - fs::copy(source, dest).context(context)?; + fs::copy(source, dest).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; Ok(copy_debug) } diff --git a/src/uu/cp/src/platform/other_unix.rs b/src/uu/cp/src/platform/other_unix.rs index aa8fed3fab1..94799139ac5 100644 --- a/src/uu/cp/src/platform/other_unix.rs +++ b/src/uu/cp/src/platform/other_unix.rs @@ -7,11 +7,12 @@ use std::fs::{self, File, OpenOptions}; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; -use quick_error::ResultExt; use uucore::buf_copy; use uucore::mode::get_umask; -use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; +use crate::{ + CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode, +}; /// Copies `source` to `dest` for systems without copy-on-write pub(crate) fn copy_on_write( @@ -48,7 +49,7 @@ pub(crate) fn copy_on_write( buf_copy::copy_stream(&mut src_file, &mut dst_file) .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other)) - .context(context)?; + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; if source_is_fifo { dst_file.set_permissions(src_file.metadata()?.permissions())?; @@ -56,7 +57,7 @@ pub(crate) fn copy_on_write( return Ok(copy_debug); } - fs::copy(source, dest).context(context)?; + fs::copy(source, dest).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; Ok(copy_debug) } From 3904d5b6147980fb28b6f6913112b110c948d531 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 3 Jun 2025 16:12:52 +0200 Subject: [PATCH 097/139] uptime: move imports into test function to simplify the cfg handling --- tests/by-util/test_uptime.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/by-util/test_uptime.rs b/tests/by-util/test_uptime.rs index 7ec71cebad9..fb625bd6272 100644 --- a/tests/by-util/test_uptime.rs +++ b/tests/by-util/test_uptime.rs @@ -8,21 +8,10 @@ #[cfg(not(any(target_os = "openbsd", target_os = "freebsd")))] use uutests::at_and_ucmd; -use uutests::new_ucmd; use uutests::util::TestScenario; -use uutests::util_name; +use uutests::{new_ucmd, util_name}; -#[cfg(not(any(target_os = "macos", target_os = "openbsd", target_env = "musl")))] -use bincode::{config, serde::encode_to_vec}; use regex::Regex; -#[cfg(not(any(target_os = "macos", target_os = "openbsd", target_env = "musl")))] -use serde::Serialize; -#[cfg(not(any(target_os = "macos", target_os = "openbsd", target_env = "musl")))] -use serde_big_array::BigArray; -#[cfg(not(any(target_os = "macos", target_os = "openbsd", target_env = "musl")))] -use std::fs::File; -#[cfg(not(any(target_os = "macos", target_os = "openbsd", target_env = "musl")))] -use std::{io::Write, path::PathBuf}; #[test] fn test_invalid_arg() { @@ -110,6 +99,12 @@ fn test_uptime_with_non_existent_file() { )] #[allow(clippy::too_many_lines, clippy::items_after_statements)] fn test_uptime_with_file_containing_valid_boot_time_utmpx_record() { + use bincode::{config, serde::encode_to_vec}; + use serde::Serialize; + use serde_big_array::BigArray; + use std::fs::File; + use std::{io::Write, path::PathBuf}; + // This test will pass for freebsd but we currently don't support changing the utmpx file for // freebsd. let ts = TestScenario::new(util_name!()); From 8426c1480c63d1ec38c0cfc364caa118cdf90145 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Fri, 30 May 2025 10:30:21 +0200 Subject: [PATCH 098/139] sort: Make use of ExtendedBigDecimal in -g sorting This provides better precision than f64, which we need. Fixed #8031. --- Cargo.lock | 1 + fuzz/Cargo.lock | 1 + src/uu/sort/Cargo.toml | 3 ++ src/uu/sort/src/chunks.rs | 8 ++++-- src/uu/sort/src/sort.rs | 59 ++++++++++++++++++++++++--------------- 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b5d5ead095..3d8b677af33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3287,6 +3287,7 @@ dependencies = [ name = "uu_sort" version = "0.1.0" dependencies = [ + "bigdecimal", "binary-heap-plus", "clap", "compare", diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index cd9aae5a479..35b360c77ed 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1345,6 +1345,7 @@ dependencies = [ name = "uu_sort" version = "0.1.0" dependencies = [ + "bigdecimal", "binary-heap-plus", "clap", "compare", diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 67676d809f7..80c8ba364b7 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,3 +1,5 @@ +# spell-checker:ignore bigdecimal + [package] name = "uu_sort" description = "sort ~ (uutils) sort input lines" @@ -18,6 +20,7 @@ workspace = true path = "src/sort.rs" [dependencies] +bigdecimal = { workspace = true } binary-heap-plus = { workspace = true } clap = { workspace = true } compare = { workspace = true } diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 8f423701ac0..18b2547dc71 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -17,7 +17,9 @@ use memchr::memchr_iter; use self_cell::self_cell; use uucore::error::{UResult, USimpleError}; -use crate::{GeneralF64ParseResult, GlobalSettings, Line, SortError, numeric_str_cmp::NumInfo}; +use crate::{ + GeneralBigDecimalParseResult, GlobalSettings, Line, SortError, numeric_str_cmp::NumInfo, +}; self_cell!( /// The chunk that is passed around between threads. @@ -41,7 +43,7 @@ pub struct ChunkContents<'a> { pub struct LineData<'a> { pub selections: Vec<&'a str>, pub num_infos: Vec, - pub parsed_floats: Vec, + pub parsed_floats: Vec, pub line_num_floats: Vec>, } @@ -100,7 +102,7 @@ pub struct RecycledChunk { lines: Vec>, selections: Vec<&'static str>, num_infos: Vec, - parsed_floats: Vec, + parsed_floats: Vec, line_num_floats: Vec>, buffer: Vec, } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7de7eb1edcb..39c7e4ac4a2 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -7,7 +7,7 @@ // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html // https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html -// spell-checker:ignore (misc) HFKJFK Mbdfhn getrlimit RLIMIT_NOFILE rlim +// spell-checker:ignore (misc) HFKJFK Mbdfhn getrlimit RLIMIT_NOFILE rlim bigdecimal extendedbigdecimal mod check; mod chunks; @@ -17,6 +17,7 @@ mod merge; mod numeric_str_cmp; mod tmp_dir; +use bigdecimal::BigDecimal; use chunks::LineData; use clap::builder::ValueParser; use clap::{Arg, ArgAction, Command}; @@ -44,7 +45,9 @@ use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; use uucore::error::{FromIo, strip_errno}; use uucore::error::{UError, UResult, USimpleError, UUsageError, set_exit_code}; +use uucore::extendedbigdecimal::ExtendedBigDecimal; use uucore::line_ending::LineEnding; +use uucore::parser::num_parser::{ExtendedParser, ExtendedParserError}; use uucore::parser::parse_size::{ParseSizeError, Parser}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::version_cmp::version_cmp; @@ -450,7 +453,7 @@ impl Default for KeySettings { } } enum Selection<'a> { - AsF64(GeneralF64ParseResult), + AsBigDecimal(GeneralBigDecimalParseResult), WithNumInfo(&'a str, NumInfo), Str(&'a str), } @@ -492,7 +495,7 @@ impl<'a> Line<'a> { .map(|selector| (selector, selector.get_selection(line, token_buffer))) { match selection { - Selection::AsF64(parsed_float) => line_data.parsed_floats.push(parsed_float), + Selection::AsBigDecimal(parsed_float) => line_data.parsed_floats.push(parsed_float), Selection::WithNumInfo(str, num_info) => { line_data.num_infos.push(num_info); line_data.selections.push(str); @@ -904,8 +907,8 @@ impl FieldSelector { range = &range[num_range]; Selection::WithNumInfo(range, info) } else if self.settings.mode == SortMode::GeneralNumeric { - // Parse this number as f64, as this is the requirement for general numeric sorting. - Selection::AsF64(general_f64_parse(&range[get_leading_gen(range)])) + // Parse this number as BigDecimal, as this is the requirement for general numeric sorting. + Selection::AsBigDecimal(general_bd_parse(&range[get_leading_gen(range)])) } else { // This is not a numeric sort, so we don't need a NumCache. Selection::Str(range) @@ -1791,35 +1794,45 @@ fn get_leading_gen(input: &str) -> Range { leading_whitespace_len..input.len() } -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -pub enum GeneralF64ParseResult { +#[derive(Clone, PartialEq, PartialOrd, Debug)] +pub enum GeneralBigDecimalParseResult { Invalid, - NaN, - NegInfinity, - Number(f64), + Nan, + MinusInfinity, + Number(BigDecimal), Infinity, } -/// Parse the beginning string into a GeneralF64ParseResult. -/// Using a GeneralF64ParseResult instead of f64 is necessary to correctly order floats. +/// Parse the beginning string into a GeneralBigDecimalParseResult. +/// Using a GeneralBigDecimalParseResult instead of ExtendedBigDecimal is necessary to correctly order floats. #[inline(always)] -fn general_f64_parse(a: &str) -> GeneralF64ParseResult { - // The actual behavior here relies on Rust's implementation of parsing floating points. - // For example "nan", "inf" (ignoring the case) and "infinity" are only parsed to floats starting from 1.53. - // TODO: Once our minimum supported Rust version is 1.53 or above, we should add tests for those cases. - match a.parse::() { - Ok(a) if a.is_nan() => GeneralF64ParseResult::NaN, - Ok(a) if a == f64::NEG_INFINITY => GeneralF64ParseResult::NegInfinity, - Ok(a) if a == f64::INFINITY => GeneralF64ParseResult::Infinity, - Ok(a) => GeneralF64ParseResult::Number(a), - Err(_) => GeneralF64ParseResult::Invalid, +fn general_bd_parse(a: &str) -> GeneralBigDecimalParseResult { + // Parse digits, and fold in recoverable errors + let ebd = match ExtendedBigDecimal::extended_parse(a) { + Err(ExtendedParserError::NotNumeric) => return GeneralBigDecimalParseResult::Invalid, + Err(ExtendedParserError::PartialMatch(ebd, _)) + | Err(ExtendedParserError::Overflow(ebd)) + | Err(ExtendedParserError::Underflow(ebd)) + | Ok(ebd) => ebd, + }; + + match ebd { + ExtendedBigDecimal::BigDecimal(bd) => GeneralBigDecimalParseResult::Number(bd), + ExtendedBigDecimal::Infinity => GeneralBigDecimalParseResult::Infinity, + ExtendedBigDecimal::MinusInfinity => GeneralBigDecimalParseResult::MinusInfinity, + // Minus zero and zero are equal + ExtendedBigDecimal::MinusZero => GeneralBigDecimalParseResult::Number(0.into()), + ExtendedBigDecimal::Nan | ExtendedBigDecimal::MinusNan => GeneralBigDecimalParseResult::Nan, } } /// Compares two floats, with errors and non-numerics assumed to be -inf. /// Stops coercing at the first non-numeric char. /// We explicitly need to convert to f64 in this case. -fn general_numeric_compare(a: &GeneralF64ParseResult, b: &GeneralF64ParseResult) -> Ordering { +fn general_numeric_compare( + a: &GeneralBigDecimalParseResult, + b: &GeneralBigDecimalParseResult, +) -> Ordering { a.partial_cmp(b).unwrap() } From edc1e5def6472ca708d2af8974070d87bb6046b0 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 3 Jun 2025 09:21:35 +0200 Subject: [PATCH 099/139] uucore: num_parser: Improve scale conversion to i64 It turns out repeatedly calling i64::MAX.into() and i64::MIN.into() is actually very expensive. Just do the conversion first, and if it fails, we know why. Sadly there is still a conversion happening under the hood in `-exponent + scale`, but that'd need to be fixed in Bigint. Improves sort -g performance by ~5%. --- src/uucore/src/lib/features/parser/num_parser.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index 84aa82bdd79..8d08f7703cc 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -465,16 +465,15 @@ fn construct_extended_big_decimal<'a>( let bd = if scale == 0 && exponent.is_zero() { BigDecimal::from_bigint(signed_digits, 0) } else if base == Base::Decimal { - let new_scale = BigInt::from(scale) - exponent; + let new_scale = -exponent + scale; // BigDecimal "only" supports i64 scale. - // Note that new_scale is a negative exponent: large value causes an underflow, small value an overflow. - if new_scale > i64::MAX.into() { - return Err(make_error(false, negative)); - } else if new_scale < i64::MIN.into() { - return Err(make_error(true, negative)); + // Note that new_scale is a negative exponent: large positive value causes an underflow, large negative values an overflow. + if let Some(new_scale) = new_scale.to_i64() { + BigDecimal::from_bigint(signed_digits, new_scale) + } else { + return Err(make_error(new_scale.is_negative(), negative)); } - BigDecimal::from_bigint(signed_digits, new_scale.to_i64().unwrap()) } else if base == Base::Hexadecimal { // pow "only" supports u32 values, just error out if given more than 2**32 fractional digits. if scale > u32::MAX.into() { From 3b18316337c7965b5b6cc793aff404956003e21c Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 3 Jun 2025 10:05:27 +0200 Subject: [PATCH 100/139] uucore: num_parser: Optimize parse_digits_count parse_digits_count is a significant hotspot in parsing code. In particular, any add/mul operation on BigUint is fairly slow, so it's better to accumulate digits in a u64, then add them to the resulting BigUint. Saves about 15-20% performance in `sort -g`. --- .../src/lib/features/parser/num_parser.rs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index 8d08f7703cc..879cb8d5bd5 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -71,12 +71,37 @@ impl Base { let mut digits: Option = digits; let mut count: u64 = 0; let mut rest = str; + + // Doing operations on BigUint is really expensive, so we do as much as we + // can on u64, then add them to the BigUint. + let mut digits_tmp: u64 = 0; + let mut count_tmp: u64 = 0; + let mut mul_tmp: u64 = 1; while let Some(d) = rest.chars().next().and_then(|c| self.digit(c)) { - (digits, count) = ( - Some(digits.unwrap_or_default() * *self as u8 + d), - count + 1, + (digits_tmp, count_tmp, mul_tmp) = ( + digits_tmp * *self as u64 + d, + count_tmp + 1, + mul_tmp * *self as u64, ); rest = &rest[1..]; + // In base 16, we parse 4 bits at a time, so we can parse 16 digits at most in a u64. + if count_tmp >= 15 { + // Accumulate what we have so far + (digits, count) = ( + Some(digits.unwrap_or_default() * mul_tmp + digits_tmp), + count + count_tmp, + ); + // Reset state + (digits_tmp, count_tmp, mul_tmp) = (0, 0, 1); + } + } + + // Accumulate the leftovers (if any) + if mul_tmp > 1 { + (digits, count) = ( + Some(digits.unwrap_or_default() * mul_tmp + digits_tmp), + count + count_tmp, + ); } (digits, count, rest) } From 7457a76f405a6e92cff3343d54243a4dd2f8bf6a Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Tue, 3 Jun 2025 15:13:43 +0200 Subject: [PATCH 101/139] uucore: num_parser: Optimize bigdecimal create when exponent is 0 Makes creating float number without an exponent part quite a bit faster. Saves about 9% speed in sort -g. --- .../src/lib/features/parser/num_parser.rs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index 879cb8d5bd5..35a8892e819 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -67,15 +67,15 @@ impl Base { &self, str: &'a str, digits: Option, - ) -> (Option, u64, &'a str) { + ) -> (Option, i64, &'a str) { let mut digits: Option = digits; - let mut count: u64 = 0; + let mut count: i64 = 0; let mut rest = str; // Doing operations on BigUint is really expensive, so we do as much as we // can on u64, then add them to the BigUint. let mut digits_tmp: u64 = 0; - let mut count_tmp: u64 = 0; + let mut count_tmp: i64 = 0; let mut mul_tmp: u64 = 1; while let Some(d) = rest.chars().next().and_then(|c| self.digit(c)) { (digits_tmp, count_tmp, mul_tmp) = ( @@ -290,7 +290,7 @@ impl ExtendedParser for ExtendedBigDecimal { } } -fn parse_digits(base: Base, str: &str, fractional: bool) -> (Option, u64, &str) { +fn parse_digits(base: Base, str: &str, fractional: bool) -> (Option, i64, &str) { // Parse the integral part of the number let (digits, rest) = base.parse_digits(str); @@ -472,7 +472,7 @@ fn construct_extended_big_decimal<'a>( digits: BigUint, negative: bool, base: Base, - scale: u64, + scale: i64, exponent: BigInt, ) -> Result> { if digits == BigUint::zero() { @@ -490,14 +490,19 @@ fn construct_extended_big_decimal<'a>( let bd = if scale == 0 && exponent.is_zero() { BigDecimal::from_bigint(signed_digits, 0) } else if base == Base::Decimal { - let new_scale = -exponent + scale; - - // BigDecimal "only" supports i64 scale. - // Note that new_scale is a negative exponent: large positive value causes an underflow, large negative values an overflow. - if let Some(new_scale) = new_scale.to_i64() { - BigDecimal::from_bigint(signed_digits, new_scale) + if exponent.is_zero() { + // Optimization: Converting scale to Bigint and back is relatively slow. + BigDecimal::from_bigint(signed_digits, scale) } else { - return Err(make_error(new_scale.is_negative(), negative)); + let new_scale = -exponent + scale; + + // BigDecimal "only" supports i64 scale. + // Note that new_scale is a negative exponent: large positive value causes an underflow, large negative values an overflow. + if let Some(new_scale) = new_scale.to_i64() { + BigDecimal::from_bigint(signed_digits, new_scale) + } else { + return Err(make_error(new_scale.is_negative(), negative)); + } } } else if base == Base::Hexadecimal { // pow "only" supports u32 values, just error out if given more than 2**32 fractional digits. From e39037c6996371c0c436ea37734672394145c5a4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 21:41:01 +0200 Subject: [PATCH 102/139] GNU CI: also build selinux with --release-build --- .github/workflows/GnuTests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index d5c6257e706..108bbc67d42 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -194,7 +194,7 @@ jobs: - name: Selinux - Build for selinux tests run: | - lima bash -c "cd ~/work/uutils/ && bash util/build-gnu.sh" + lima bash -c "cd ~/work/uutils/ && bash util/build-gnu.sh --release-build" lima bash -c "mkdir -p ~/work/gnu/tests-selinux/" - name: Selinux - Run selinux tests From 4050f68e6e71292521ab8317fb6ce8ed50f8785c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 22:26:58 +0200 Subject: [PATCH 103/139] uucore_procs: use authors.workspace --- src/uucore_procs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 8d0fb09bb1e..bd6b48edd1d 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -2,11 +2,11 @@ [package] name = "uucore_procs" description = "uutils ~ 'uucore' proc-macros" -authors = ["Roy Ivy III "] repository = "https://github.com/uutils/coreutils/tree/main/src/uucore_procs" # readme = "README.md" keywords = ["cross-platform", "proc-macros", "uucore", "uutils"] # categories = ["os"] +authors.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true From f46e096d3500133e09b975309a958fe3ef27740c Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 3 Jun 2025 18:59:36 -0400 Subject: [PATCH 104/139] Add common pre-commit hooks Pre-commits are usually used to minimize busy work by the contributors, e.g., by fixing extra spacing, formatting, etc. This PR adds various basic text file checks to the repo. I also made yaml spacing a bit cleaner. I was a bit surprised it is used for `cargo clippy` because you wouldn't want clippy's auto-fixes to be auto-applied by CI, so usually GitHub workflow simply checks runs it regularly. This is outside of the scope for this PR, but perhaps it should be removed here? --- .pre-commit-config.yaml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 534487abc8f..8498f42eadb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,41 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks repos: -- repo: local + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 hooks: - - id: rust-linting + - id: check-added-large-files + - id: check-executables-have-shebangs + - id: check-json + - id: check-shebang-scripts-are-executable + exclude: '.+\.rs' # would be triggered by #![some_attribute] + - id: check-symlinks + - id: check-toml + - id: check-yaml + args: [ --allow-multiple-documents ] + - id: destroyed-symlinks + - id: end-of-file-fixer + - id: mixed-line-ending + args: [ --fix=lf ] + - id: trailing-whitespace + + - repo: local + hooks: + - id: rust-linting name: Rust linting description: Run cargo fmt on files included in the commit. entry: cargo +stable fmt -- pass_filenames: true types: [file, rust] language: system - - id: rust-clippy + - id: rust-clippy name: Rust clippy description: Run cargo clippy on files included in the commit. entry: cargo +stable clippy --workspace --all-targets --all-features -- -D warnings pass_filenames: false types: [file, rust] language: system - - id: cspell + - id: cspell name: Code spell checker (cspell) description: Run cspell to check for spelling errors. entry: cspell --no-must-find-files -- From a2f9543a14ee3dc775298f8e7d034555e80e954e Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Wed, 4 Jun 2025 09:05:49 +0200 Subject: [PATCH 105/139] docs/src/extensions: Sort uses arbitrary precision decimal numbers --- docs/src/extensions.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/src/extensions.md b/docs/src/extensions.md index af6119da51a..81457c29d7e 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -153,6 +153,15 @@ See also comments under `printf` for formatting precision and differences. `seq` provides `-t`/`--terminator` to set the terminator character. +## `sort` + +When sorting with `-g`/`--general-numeric-sort`, arbitrary precision decimal numbers +are parsed and compared, unlike GNU coreutils that uses platform-specific long +double floating point numbers. + +Extremely large or small values can still overflow or underflow to infinity or zero, +see note in `seq`. + ## `ls` GNU `ls` provides two ways to use a long listing format: `-l` and `--format=long`. We support a From ee1a6d2e6680be2e50d2c7969ea8ff66ef76f425 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Wed, 4 Jun 2025 09:33:17 +0200 Subject: [PATCH 106/139] test_sort: Add more sort use cases test_g_float comes from GNU test, the other one is manually crafted. --- tests/by-util/test_sort.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0e20f1c9ee1..6ae2bb80652 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1503,3 +1503,27 @@ fn test_files0_from_zero_length() { .fails_with_code(2) .stderr_only("sort: -:2: invalid zero-length file name\n"); } + +#[test] +// Test for GNU tests/sort/sort-float.sh +fn test_g_float() { + let input = "0\n-3.3621031431120935063e-4932\n3.3621031431120935063e-4932\n"; + let output = "-3.3621031431120935063e-4932\n0\n3.3621031431120935063e-4932\n"; + new_ucmd!() + .args(&["-g"]) + .pipe_in(input) + .succeeds() + .stdout_is(output); +} + +#[test] +// Test misc numbers ("'a" is not interpreted as literal, trailing text is ignored...) +fn test_g_misc() { + let input = "1\n100\n90\n'a\n85hello\n"; + let output = "'a\n1\n85hello\n90\n100\n"; + new_ucmd!() + .args(&["-g"]) + .pipe_in(input) + .succeeds() + .stdout_is(output); +} From 8c98f433bffbfdece502a565e2e176a414939f87 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Wed, 4 Jun 2025 09:54:44 +0200 Subject: [PATCH 107/139] test_sort: Add one more test checking arbitrary precision handling --- tests/by-util/test_sort.rs | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 6ae2bb80652..3fcd4e97874 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1527,3 +1527,43 @@ fn test_g_misc() { .succeeds() .stdout_is(output); } + +#[test] +// Test numbers with a large number of digits, where only the last digit is different. +// We use scientific notation to make sure string sorting does not correctly order them. +fn test_g_arbitrary() { + let input = [ + // GNU coreutils doesn't handle those correctly as they don't fit exactly in long double + "3", + "3.000000000000000000000000000000000000000000000000000000000000000004", + "0.3000000000000000000000000000000000000000000000000000000000000000002e1", + "0.03000000000000000000000000000000000000000000000000000000000000000003e2", + "0.003000000000000000000000000000000000000000000000000000000000000000001e3", + // GNU coreutils does handle those correctly though + "10", + "10.000000000000004", + "1.0000000000000002e1", + "0.10000000000000003e2", + "0.010000000000000001e3", + ] + .join("\n"); + let output = [ + "3", + "0.003000000000000000000000000000000000000000000000000000000000000000001e3", + "0.3000000000000000000000000000000000000000000000000000000000000000002e1", + "0.03000000000000000000000000000000000000000000000000000000000000000003e2", + "3.000000000000000000000000000000000000000000000000000000000000000004", + "10", + "0.010000000000000001e3", + "1.0000000000000002e1", + "0.10000000000000003e2", + "10.000000000000004", + ] + .join("\n") + + "\n"; + new_ucmd!() + .args(&["-g"]) + .pipe_in(input) + .succeeds() + .stdout_is(output); +} From a7a493a604be66bddd5cd947d67078506961d3aa Mon Sep 17 00:00:00 2001 From: ALBIN BABU VARGHESE Date: Wed, 4 Jun 2025 11:25:53 -0400 Subject: [PATCH 108/139] mv: moving dangling symlinks into directories (#8064) * Fix linting and style issues * Change condition to not fail for dangling symlinks The function `move_files_into_dir` had a condition that failed when a dangling symlink was moved into a folder, which resulted in a file or directory doesn't exist error * Added a test case --- src/uu/mv/src/mv.rs | 2 +- tests/by-util/test_mv.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index edfa505c521..c7f920204cd 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -526,7 +526,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) }; for sourcepath in files { - if !sourcepath.exists() { + if sourcepath.symlink_metadata().is_err() { show!(MvError::NoSuchFile(sourcepath.quote().to_string())); continue; } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 577f6a75899..b20c4b2b644 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -443,6 +443,19 @@ fn test_mv_same_hardlink() { .stderr_is(format!("mv: '{file_a}' and '{file_b}' are the same file\n")); } +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_mv_dangling_symlink_to_folder() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.symlink_file("404", "abc"); + at.mkdir("x"); + + ucmd.arg("abc").arg("x").succeeds(); + + assert!(at.symlink_exists("x/abc")); +} + #[test] #[cfg(all(unix, not(target_os = "android")))] fn test_mv_same_symlink() { From 42cfb3d8bef979c39396e87765b71b023df9b840 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 08:36:02 +0200 Subject: [PATCH 109/139] locale: show the error in case of issue --- src/uucore/src/lib/mods/locale.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 7bd3c098899..ba701240c97 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -140,13 +140,21 @@ fn create_bundle( path: locale_path.clone(), })?; - let resource = FluentResource::try_new(ftl_file).map_err(|_| { - LocalizationError::Parse(format!( - "Failed to parse localization resource for {}: {}", - locale, - locale_path.display() - )) - })?; + let resource = FluentResource::try_new(ftl_file.clone()).map_err( + |(_partial_resource, mut errs): (FluentResource, Vec)| { + let first_err = errs.remove(0); + // Attempt to extract the snippet from the original ftl_file + let snippet = if let Some(range) = first_err.slice.clone() { + ftl_file.get(range).unwrap_or("").to_string() + } else { + String::new() + }; + LocalizationError::ParseResource { + error: first_err, + snippet, + } + }, + )?; let mut bundle = FluentBundle::new(vec![locale.clone()]); From d8bb7875cf75b14233486a1ffe1e28d0fe0a4a11 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 08:52:01 +0200 Subject: [PATCH 110/139] l10n: adjust the code to use help_about & help_usage --- src/uu/base32/src/base32.rs | 23 ++++++++++++----------- src/uu/base64/src/base64.rs | 20 +++++++++++--------- src/uu/basename/src/basename.rs | 8 +++----- src/uu/basenc/src/basenc.rs | 11 +++++------ src/uu/cat/src/cat.rs | 7 +++---- src/uu/chcon/src/chcon.rs | 7 +++---- src/uu/chgrp/src/chgrp.rs | 7 +++---- src/uu/chmod/src/chmod.rs | 7 +++---- src/uu/chown/src/chown.rs | 8 +++----- src/uu/chroot/src/chroot.rs | 6 +++--- src/uu/cksum/src/cksum.rs | 10 ++++------ src/uu/comm/src/comm.rs | 7 +++---- src/uu/cp/src/cp.rs | 11 +++++------ src/uu/csplit/src/csplit.rs | 10 ++++------ src/uu/cut/src/cut.rs | 10 ++++------ src/uu/date/src/date.rs | 7 +++---- src/uu/dd/src/dd.rs | 10 ++++------ src/uu/df/src/df.rs | 10 ++++------ src/uu/dircolors/src/dircolors.rs | 10 ++++------ src/uu/dirname/src/dirname.rs | 12 ++++++------ src/uu/du/src/du.rs | 10 ++++------ src/uu/echo/src/echo.rs | 10 ++++------ src/uu/env/src/env.rs | 10 ++++------ src/uu/expand/src/expand.rs | 7 +++---- src/uu/factor/src/factor.rs | 7 +++---- src/uu/false/src/false.rs | 4 ++-- src/uu/fmt/src/fmt.rs | 7 +++---- src/uu/fold/src/fold.rs | 7 +++---- src/uu/groups/src/groups.rs | 7 +++---- src/uu/hashsum/src/hashsum.rs | 7 +++---- src/uu/head/src/head.rs | 7 +++---- src/uu/hostid/src/hostid.rs | 7 +++---- src/uu/hostname/src/hostname.rs | 7 +++---- src/uu/id/src/id.rs | 12 ++++++------ src/uu/install/src/install.rs | 13 ++++++++----- src/uu/join/src/join.rs | 7 +++---- src/uu/kill/src/kill.rs | 7 +++---- src/uu/link/src/link.rs | 7 +++---- src/uu/ln/src/ln.rs | 11 +++++------ src/uu/logname/src/logname.rs | 7 +++---- src/uu/ls/src/ls.rs | 10 ++++------ src/uu/mkdir/src/mkdir.rs | 12 ++++++------ src/uu/mkfifo/src/mkfifo.rs | 6 +++--- src/uu/mknod/src/mknod.rs | 10 ++++------ src/uu/mktemp/src/mktemp.rs | 7 +++---- src/uu/more/src/more.rs | 7 +++---- src/uu/mv/src/mv.rs | 11 +++++------ src/uu/nice/src/nice.rs | 7 +++---- src/uu/nl/src/nl.rs | 10 ++++------ src/uu/nohup/src/nohup.rs | 10 ++++------ src/uu/nproc/src/nproc.rs | 7 +++---- src/uu/numfmt/src/numfmt.rs | 10 ++++------ src/uu/od/src/od.rs | 10 ++++------ src/uu/paste/src/paste.rs | 7 +++---- src/uu/pathchk/src/pathchk.rs | 7 +++---- src/uu/pinky/src/pinky.rs | 8 +++----- src/uu/pr/src/pr.rs | 10 ++++------ src/uu/printenv/src/printenv.rs | 7 +++---- src/uu/printf/src/printf.rs | 10 ++++------ src/uu/ptx/src/ptx.rs | 7 +++---- src/uu/pwd/src/pwd.rs | 7 +++---- src/uu/readlink/src/readlink.rs | 7 +++---- src/uu/realpath/src/realpath.rs | 7 +++---- src/uu/rm/src/rm.rs | 10 ++++------ src/uu/rmdir/src/rmdir.rs | 7 +++---- src/uu/runcon/src/runcon.rs | 7 +++---- src/uu/seq/src/seq.rs | 7 +++---- src/uu/shred/src/shred.rs | 10 ++++------ src/uu/shuf/src/shuf.rs | 6 +++--- src/uu/sleep/src/sleep.rs | 9 ++++----- src/uu/sort/src/sort.rs | 10 ++++------ src/uu/split/src/split.rs | 10 ++++------ src/uu/stat/src/stat.rs | 7 +++---- src/uu/stdbuf/src/stdbuf.rs | 10 +++++----- src/uu/stty/src/stty.rs | 8 +++----- src/uu/sum/src/sum.rs | 7 +++---- src/uu/sync/src/sync.rs | 7 +++---- src/uu/tac/src/tac.rs | 6 +++--- src/uu/tail/src/args.rs | 7 +++---- src/uu/tee/src/tee.rs | 10 ++++------ src/uu/test/src/test.rs | 16 ++++------------ src/uu/timeout/src/timeout.rs | 7 +++---- src/uu/touch/src/touch.rs | 7 +++---- src/uu/tr/src/tr.rs | 12 ++++++------ src/uu/true/src/true.rs | 4 ++-- src/uu/truncate/src/truncate.rs | 10 ++++------ src/uu/tsort/src/tsort.rs | 7 +++---- src/uu/tty/src/tty.rs | 7 +++---- src/uu/uname/src/uname.rs | 7 +++---- src/uu/unexpand/src/unexpand.rs | 7 +++---- src/uu/uniq/src/uniq.rs | 10 ++++------ src/uu/unlink/src/unlink.rs | 7 +++---- src/uu/uptime/src/uptime.rs | 8 +++----- src/uu/users/src/users.rs | 8 +++----- src/uu/wc/src/wc.rs | 7 +++---- src/uu/who/src/who.rs | 8 +++----- src/uu/whoami/src/whoami.rs | 7 +++---- src/uu/yes/src/yes.rs | 7 +++---- 98 files changed, 362 insertions(+), 478 deletions(-) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index e14e83921e2..37bf01e3219 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -5,24 +5,25 @@ pub mod base_common; -use base_common::ReadSeek; use clap::Command; -use uucore::{encoding::Format, error::UResult, help_about, help_usage}; - -const ABOUT: &str = help_about!("base32.md"); -const USAGE: &str = help_usage!("base32.md"); +use uucore::{encoding::Format, error::UResult, locale::get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = Format::Base32; - - let config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; - - let mut input: Box = base_common::get_input(&config)?; - + let (about, usage) = get_info(); + let config = base_common::parse_base_cmd_args(args, about, usage)?; + let mut input = base_common::get_input(&config)?; base_common::handle_input(&mut input, format, config) } pub fn uu_app() -> Command { - base_common::base_app(ABOUT, USAGE) + let (about, usage) = get_info(); + base_common::base_app(about, usage) +} + +fn get_info() -> (&'static str, &'static str) { + let about: &'static str = Box::leak(get_message("base32-about").into_boxed_str()); + let usage: &'static str = Box::leak(get_message("base32-usage").into_boxed_str()); + (about, usage) } diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 86eb75bf119..cd3ab66bded 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -5,22 +5,24 @@ use clap::Command; use uu_base32::base_common; -use uucore::{encoding::Format, error::UResult, help_about, help_usage}; - -const ABOUT: &str = help_about!("base64.md"); -const USAGE: &str = help_usage!("base64.md"); +use uucore::{encoding::Format, error::UResult, locale::get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = Format::Base64; - - let config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; - + let (about, usage) = get_info(); + let config = base_common::parse_base_cmd_args(args, about, usage)?; let mut input = base_common::get_input(&config)?; - base_common::handle_input(&mut input, format, config) } pub fn uu_app() -> Command { - base_common::base_app(ABOUT, USAGE) + let (about, usage) = get_info(); + base_common::base_app(about, usage) +} + +fn get_info() -> (&'static str, &'static str) { + let about: &'static str = Box::leak(get_message("base64-about").into_boxed_str()); + let usage: &'static str = Box::leak(get_message("base64-usage").into_boxed_str()); + (about, usage) } diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index a40fcc18534..62046a15d60 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -12,9 +12,7 @@ use uucore::error::{UResult, UUsageError}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; -static ABOUT: &str = help_about!("basename.md"); - -const USAGE: &str = help_usage!("basename.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static MULTIPLE: &str = "multiple"; @@ -77,8 +75,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("basename-about")) + .override_usage(format_usage(&get_message("basename-usage"))) .infer_long_args(true) .arg( Arg::new(options::MULTIPLE) diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index 10090765232..9da71e9deae 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -8,15 +8,11 @@ use clap::{Arg, ArgAction, Command}; use uu_base32::base_common::{self, BASE_CMD_PARSE_ERROR, Config}; use uucore::error::UClapError; +use uucore::locale::get_message; use uucore::{ encoding::Format, error::{UResult, UUsageError}, }; -use uucore::{help_about, help_usage}; - -const ABOUT: &str = help_about!("basenc.md"); -const USAGE: &str = help_usage!("basenc.md"); - const ENCODINGS: &[(&str, Format, &str)] = &[ ("base64", Format::Base64, "same as 'base64' program"), ("base64url", Format::Base64Url, "file- and url-safe base64"), @@ -47,7 +43,10 @@ const ENCODINGS: &[(&str, Format, &str)] = &[ ]; pub fn uu_app() -> Command { - let mut command = base_common::base_app(ABOUT, USAGE); + let about: &'static str = Box::leak(get_message("basenc-about").into_boxed_str()); + let usage: &'static str = Box::leak(get_message("basenc-usage").into_boxed_str()); + + let mut command = base_common::base_app(about, usage); for encoding in ENCODINGS { let raw_arg = Arg::new(encoding.0) .long(encoding.0) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 45fbe6cebf3..d60262e1d7c 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -30,8 +30,7 @@ use uucore::{fast_inc::fast_inc_one, format_usage, help_about, help_usage}; #[cfg(any(target_os = "linux", target_os = "android"))] mod splice; -const USAGE: &str = help_usage!("cat.md"); -const ABOUT: &str = help_about!("cat.md"); +use uucore::locale::{self, get_message}; // Allocate 32 digits for the line number. // An estimate is that we can print about 1e8 lines/seconds, so 32 digits @@ -275,8 +274,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("cat-usage"))) + .about(get_message("cat-about")) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 2b1ff2e8f97..eaf820d2d6f 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -24,8 +24,7 @@ mod fts; use errors::*; -const ABOUT: &str = help_about!("chcon.md"); -const USAGE: &str = help_usage!("chcon.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static HELP: &str = "help"; @@ -151,8 +150,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("chcon-about")) + .override_usage(format_usage(&get_message("chcon-usage"))) .infer_long_args(true) .disable_help_flag(true) .args_override_self(true) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 1763bbfeb73..fa5daedb930 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -16,8 +16,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; -const ABOUT: &str = help_about!("chgrp.md"); -const USAGE: &str = help_usage!("chgrp.md"); +use uucore::locale::{self, get_message}; fn parse_gid_from_str(group: &str) -> Result { if let Some(gid_str) = group.strip_prefix(':') { @@ -99,8 +98,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("chgrp-about")) + .override_usage(format_usage(&get_message("chgrp-usage"))) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index dfe30485919..b899a237c38 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -19,8 +19,7 @@ use uucore::mode; use uucore::perms::{TraverseSymlinks, configure_symlink_and_recursion}; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; -const ABOUT: &str = help_about!("chmod.md"); -const USAGE: &str = help_usage!("chmod.md"); +use uucore::locale::{self, get_message}; const LONG_USAGE: &str = help_section!("after help", "chmod.md"); mod options { @@ -159,8 +158,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("chmod-about")) + .override_usage(format_usage(&get_message("chmod-usage"))) .args_override_self(true) .infer_long_args(true) .no_binary_name(true) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 4389d92f663..af6cbc843f5 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -17,9 +17,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; -static ABOUT: &str = help_about!("chown.md"); - -const USAGE: &str = help_usage!("chown.md"); +use uucore::locale::{self, get_message}; fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult { let filter = if let Some(spec) = matches.get_one::(options::FROM) { @@ -79,8 +77,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("chown-about")) + .override_usage(format_usage(&get_message("chown-usage"))) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 15c7bac4d8d..48c5de17b2a 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -19,7 +19,7 @@ use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{format_usage, help_about, help_usage, show}; -static ABOUT: &str = help_about!("chroot.md"); +use uucore::locale::{self, get_message}; static USAGE: &str = help_usage!("chroot.md"); mod options { @@ -237,8 +237,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("chroot-about")) + .override_usage(format_usage(&get_message("chroot-usage"))) .infer_long_args(true) .trailing_var_arg(true) .arg( diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index a1a9115d9a0..0532579975c 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -26,9 +26,7 @@ use uucore::{ sum::Digest, }; -const USAGE: &str = help_usage!("cksum.md"); -const ABOUT: &str = help_about!("cksum.md"); -const AFTER_HELP: &str = help_section!("after help", "cksum.md"); +use uucore::locale::{self, get_message}; #[derive(Debug, PartialEq)] enum OutputFormat { @@ -343,8 +341,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("cksum-about")) + .override_usage(format_usage(&get_message("cksum-usage"))) .infer_long_args(true) .args_override_self(true) .arg( @@ -468,7 +466,7 @@ pub fn uu_app() -> Command { ) .action(ArgAction::SetTrue), ) - .after_help(AFTER_HELP) + .after_help(get_message("cksum-after-help")) } #[cfg(test)] diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 11752c331a5..c67b8665a11 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -15,8 +15,7 @@ use uucore::{format_usage, help_about, help_usage}; use clap::{Arg, ArgAction, ArgMatches, Command}; -const ABOUT: &str = help_about!("comm.md"); -const USAGE: &str = help_usage!("comm.md"); +use uucore::locale::{self, get_message}; mod options { pub const COLUMN_1: &str = "1"; @@ -314,8 +313,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("comm-about")) + .override_usage(format_usage(&get_message("comm-usage"))) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7ca39f6e36a..37c2367cd66 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -451,9 +451,7 @@ fn show_debug(copy_debug: &CopyDebug) { ); } -const ABOUT: &str = help_about!("cp.md"); -const USAGE: &str = help_usage!("cp.md"); -const AFTER_HELP: &str = help_section!("after help", "cp.md"); +use uucore::locale::{self, get_message}; static EXIT_ERR: i32 = 1; @@ -523,10 +521,11 @@ pub fn uu_app() -> Command { ]; Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("cp-about")) + .override_usage(format_usage(&get_message("cp-usage"))) .after_help(format!( - "{AFTER_HELP}\n\n{}", + "{}\n\n{}", + get_message("cp-after-help"), backup_control::BACKUP_CONTROL_LONG_HELP )) .infer_long_args(true) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 621823aebba..ed025da625b 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -25,9 +25,7 @@ mod split_name; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; -const ABOUT: &str = help_about!("csplit.md"); -const AFTER_HELP: &str = help_section!("after help", "csplit.md"); -const USAGE: &str = help_usage!("csplit.md"); +use uucore::locale::{self, get_message}; mod options { pub const SUFFIX_FORMAT: &str = "suffix-format"; @@ -631,8 +629,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("csplit-about")) + .override_usage(format_usage(&get_message("csplit-usage"))) .args_override_self(true) .infer_long_args(true) .arg( @@ -697,7 +695,7 @@ pub fn uu_app() -> Command { .action(ArgAction::Append) .required(true), ) - .after_help(AFTER_HELP) + .after_help(get_message("csplit-after-help")) } #[cfg(test)] diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 49f5445f36f..4d9241b6856 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -24,9 +24,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_error, sho mod matcher; mod searcher; -const USAGE: &str = help_usage!("cut.md"); -const ABOUT: &str = help_about!("cut.md"); -const AFTER_HELP: &str = help_section!("after help", "cut.md"); +use uucore::locale::{self, get_message}; struct Options<'a> { out_delimiter: Option<&'a [u8]>, @@ -580,9 +578,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) - .after_help(AFTER_HELP) + .override_usage(format_usage(&get_message("cut-usage"))) + .about(get_message("cut-about")) + .after_help(get_message("cut-after-help")) .infer_long_args(true) // While `args_override_self(true)` for some arguments, such as `-d` // and `--output-delimiter`, is consistent to the behavior of GNU cut, diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index aff353fee6c..a2e5be2d0ed 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -30,8 +30,7 @@ const MINUTES: &str = "minutes"; const SECONDS: &str = "seconds"; const NS: &str = "ns"; -const ABOUT: &str = help_about!("date.md"); -const USAGE: &str = help_usage!("date.md"); +use uucore::locale::{self, get_message}; const OPT_DATE: &str = "date"; const OPT_FORMAT: &str = "format"; @@ -290,8 +289,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("date-about")) + .override_usage(format_usage(&get_message("date-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_DATE) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4de05246f43..3c1d1e42800 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -62,9 +62,7 @@ use uucore::error::{USimpleError, set_exit_code}; use uucore::show_if_err; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; -const ABOUT: &str = help_about!("dd.md"); -const AFTER_HELP: &str = help_section!("after help", "dd.md"); -const USAGE: &str = help_usage!("dd.md"); +use uucore::locale::{self, get_message}; const BUF_INIT_BYTE: u8 = 0xDD; /// Final settings after parsing @@ -1427,9 +1425,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("dd-about")) + .override_usage(format_usage(&get_message("dd-usage"))) + .after_help(get_message("dd-after-help")) .infer_long_args(true) .arg(Arg::new(options::OPERANDS).num_args(1..)) } diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 9e2bb6920af..c5d1a5aa46b 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -29,9 +29,7 @@ use crate::filesystem::Filesystem; use crate::filesystem::FsError; use crate::table::Table; -const ABOUT: &str = help_about!("df.md"); -const USAGE: &str = help_usage!("df.md"); -const AFTER_HELP: &str = help_section!("after help", "df.md"); +use uucore::locale::{self, get_message}; static OPT_HELP: &str = "help"; static OPT_ALL: &str = "all"; @@ -452,9 +450,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("df-about")) + .override_usage(format_usage(&get_message("df-usage"))) + .after_help(get_message("df-after-help")) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 4fb9228eb5f..ea5d5082528 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -26,9 +26,7 @@ mod options { pub const FILE: &str = "FILE"; } -const USAGE: &str = help_usage!("dircolors.md"); -const ABOUT: &str = help_about!("dircolors.md"); -const AFTER_HELP: &str = help_section!("after help", "dircolors.md"); +use uucore::locale::{self, get_message}; #[derive(PartialEq, Eq, Debug)] pub enum OutputFmt { @@ -245,9 +243,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("dircolors-about")) + .after_help(get_message("dircolors-after-help")) + .override_usage(format_usage(&get_message("dircolors-usage"))) .args_override_self(true) .infer_long_args(true) .arg( diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index de8740f8970..5eb6ca4b35e 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -10,9 +10,7 @@ use uucore::error::{UResult, UUsageError}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_section, help_usage}; -const ABOUT: &str = help_about!("dirname.md"); -const USAGE: &str = help_usage!("dirname.md"); -const AFTER_HELP: &str = help_section!("after help", "dirname.md"); +use uucore::locale::{self, get_message}; mod options { pub const ZERO: &str = "zero"; @@ -21,7 +19,9 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(get_message("dirname-after-help")) + .try_get_matches_from(args)?; let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); @@ -61,9 +61,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) - .about(ABOUT) + .about(get_message("dirname-about")) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) + .override_usage(format_usage(&get_message("dirname-usage"))) .args_override_self(true) .infer_long_args(true) .arg( diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 0b268888136..c25d66f5deb 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -70,9 +70,7 @@ mod options { pub const FILE: &str = "FILE"; } -const ABOUT: &str = help_about!("du.md"); -const AFTER_HELP: &str = help_section!("after help", "du.md"); -const USAGE: &str = help_usage!("du.md"); +use uucore::locale::{self, get_message}; struct TraversalOptions { all: bool, @@ -824,9 +822,9 @@ fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("du-about")) + .after_help(get_message("du-after-help")) + .override_usage(format_usage(&get_message("du-usage"))) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 4df76634843..b6e699ce20c 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -12,9 +12,7 @@ use uucore::error::{UResult, USimpleError}; use uucore::format::{FormatChar, OctalParsing, parse_escape_only}; use uucore::{format_usage, help_about, help_section, help_usage}; -const ABOUT: &str = help_about!("echo.md"); -const USAGE: &str = help_usage!("echo.md"); -const AFTER_HELP: &str = help_section!("after help", "echo.md"); +use uucore::locale::{self, get_message}; mod options { pub const STRING: &str = "STRING"; @@ -141,9 +139,9 @@ pub fn uu_app() -> Command { .trailing_var_arg(true) .allow_hyphen_values(true) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("echo-about")) + .after_help(get_message("echo-after-help")) + .override_usage(format_usage(&get_message("echo-usage"))) .arg( Arg::new(options::NO_NEWLINE) .short('n') diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 51479b5902f..0555dbe60ac 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -74,9 +74,7 @@ impl From for EnvError { } } -const ABOUT: &str = help_about!("env.md"); -const USAGE: &str = help_usage!("env.md"); -const AFTER_HELP: &str = help_section!("after help", "env.md"); +use uucore::locale::{self, get_message}; mod options { pub const IGNORE_ENVIRONMENT: &str = "ignore-environment"; @@ -222,9 +220,9 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { pub fn uu_app() -> Command { Command::new(crate_name!()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("env-about")) + .override_usage(format_usage(&get_message("env-usage"))) + .after_help(get_message("env-after-help")) .infer_long_args(true) .trailing_var_arg(true) .arg( diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 4c37393b4ac..a7cb0928c18 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -18,8 +18,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, set_exit_code}; use uucore::{format_usage, help_about, help_usage, show_error}; -const ABOUT: &str = help_about!("expand.md"); -const USAGE: &str = help_usage!("expand.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static TABS: &str = "tabs"; @@ -253,9 +252,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) + .about(get_message("expand-about")) .after_help(LONG_HELP) - .override_usage(format_usage(USAGE)) + .override_usage(format_usage(&get_message("expand-usage"))) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 2c8d1661c05..f00d05eca8d 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -16,8 +16,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; use uucore::{format_usage, help_about, help_usage, show_error, show_warning}; -const ABOUT: &str = help_about!("factor.md"); -const USAGE: &str = help_usage!("factor.md"); +use uucore::locale::{self, get_message}; mod options { pub static EXPONENTS: &str = "exponents"; @@ -122,8 +121,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("factor-about")) + .override_usage(format_usage(&get_message("factor-usage"))) .infer_long_args(true) .disable_help_flag(true) .args_override_self(true) diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index adf3593ea0d..a1e15ba421d 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -7,7 +7,7 @@ use std::{ffi::OsString, io::Write}; use uucore::error::{UResult, set_exit_code}; use uucore::help_about; -const ABOUT: &str = help_about!("false.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) + .about(get_message("false-about")) // We provide our own help and version options, to ensure maximum compatibility with GNU. .disable_help_flag(true) .disable_version_flag(true) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 8366af6cdba..f8d5cdfa89e 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -18,8 +18,7 @@ use parasplit::ParagraphStream; mod linebreak; mod parasplit; -const ABOUT: &str = help_about!("fmt.md"); -const USAGE: &str = help_usage!("fmt.md"); +use uucore::locale::{self, get_message}; const MAX_WIDTH: usize = 2500; const DEFAULT_GOAL: usize = 70; const DEFAULT_WIDTH: usize = 75; @@ -336,8 +335,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("fmt-about")) + .override_usage(format_usage(&get_message("fmt-usage"))) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 0aba9c57ee4..1735c56424c 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -15,8 +15,7 @@ use uucore::{format_usage, help_about, help_usage}; const TAB_WIDTH: usize = 8; -const USAGE: &str = help_usage!("fold.md"); -const ABOUT: &str = help_about!("fold.md"); +use uucore::locale::{self, get_message}; mod options { pub const BYTES: &str = "bytes"; @@ -60,8 +59,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("fold-usage"))) + .about(get_message("fold-about")) .infer_long_args(true) .arg( Arg::new(options::BYTES) diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 6f7fbf5fed2..dc2e7a6310d 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -18,8 +18,7 @@ use clap::{Arg, ArgAction, Command}; mod options { pub const USERS: &str = "USERNAME"; } -const ABOUT: &str = help_about!("groups.md"); -const USAGE: &str = help_usage!("groups.md"); +use uucore::locale::{self, get_message}; #[derive(Debug, Error)] enum GroupsError { @@ -82,8 +81,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("groups-about")) + .override_usage(format_usage(&get_message("groups-usage"))) .infer_long_args(true) .arg( Arg::new(options::USERS) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index cd8ca912df5..2a81a0b5668 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -30,8 +30,7 @@ use uucore::sum::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shak use uucore::{format_usage, help_about, help_usage}; const NAME: &str = "hashsum"; -const ABOUT: &str = help_about!("hashsum.md"); -const USAGE: &str = help_usage!("hashsum.md"); +use uucore::locale::{self, get_message}; struct Options { algoname: &'static str, @@ -318,8 +317,8 @@ pub fn uu_app_common() -> Command { const TEXT_HELP: &str = "read in text mode (default)"; Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("hashsum-about")) + .override_usage(format_usage(&get_message("hashsum-usage"))) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 573926a7bb2..35fe4611209 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -20,8 +20,7 @@ use uucore::{format_usage, help_about, help_usage, show}; const BUF_SIZE: usize = 65536; -const ABOUT: &str = help_about!("head.md"); -const USAGE: &str = help_usage!("head.md"); +use uucore::locale::{self, get_message}; mod options { pub const BYTES_NAME: &str = "BYTES"; @@ -72,8 +71,8 @@ type HeadResult = Result; pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("head-about")) + .override_usage(format_usage(&get_message("head-usage"))) .infer_long_args(true) .arg( Arg::new(options::BYTES_NAME) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index a01151dde17..cee59b7b0b9 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -9,8 +9,7 @@ use clap::Command; use libc::{c_long, gethostid}; use uucore::{error::UResult, format_usage, help_about, help_usage}; -const USAGE: &str = help_usage!("hostid.md"); -const ABOUT: &str = help_about!("hostid.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -22,8 +21,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("hostid-about")) + .override_usage(format_usage(&get_message("hostid-usage"))) .infer_long_args(true) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 29b2bb6ba1e..514e3db1fdc 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -21,8 +21,7 @@ use uucore::{ format_usage, help_about, help_usage, }; -const ABOUT: &str = help_about!("hostname.md"); -const USAGE: &str = help_usage!("hostname.md"); +use uucore::locale::{self, get_message}; static OPT_DOMAIN: &str = "domain"; static OPT_IP_ADDRESS: &str = "ip-address"; @@ -76,8 +75,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("hostname-about")) + .override_usage(format_usage(&get_message("hostname-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_DOMAIN) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 314d12d6804..413ba06b37e 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -59,9 +59,7 @@ macro_rules! cstr2cow { }; } -const ABOUT: &str = help_about!("id.md"); -const USAGE: &str = help_usage!("id.md"); -const AFTER_HELP: &str = help_section!("after help", "id.md"); +use uucore::locale::{self, get_message}; #[cfg(not(feature = "selinux"))] static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)"; @@ -119,7 +117,9 @@ struct State { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(get_message("id-after-help")) + .try_get_matches_from(args)?; let users: Vec = matches .get_many::(options::ARG_USERS) @@ -327,8 +327,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("id-about")) + .override_usage(format_usage(&get_message("id-usage"))) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index c4590240bea..3bfd542c1aa 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -140,8 +140,7 @@ impl Behavior { } } -const ABOUT: &str = help_about!("install.md"); -const USAGE: &str = help_usage!("install.md"); +use uucore::locale::{self, get_message}; static OPT_COMPARE: &str = "compare"; static OPT_DIRECTORY: &str = "directory"; @@ -185,8 +184,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("install-about")) + .override_usage(format_usage(&get_message("install-usage"))) .infer_long_args(true) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) @@ -516,7 +515,11 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { return Err(UUsageError::new(1, "missing file operand")); } if b.no_target_dir && paths.len() > 2 { - return Err(InstallError::ExtraOperand(paths[2].clone(), format_usage(USAGE)).into()); + return Err(InstallError::ExtraOperand( + paths[2].clone(), + format_usage(&get_message("install-usage")), + ) + .into()); } // get the target from either "-t foo" param or from the last given paths argument diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 0c6816cb649..838cce325a7 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -21,8 +21,7 @@ use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("join.md"); -const USAGE: &str = help_usage!("join.md"); +use uucore::locale::{self, get_message}; #[derive(Debug, Error)] enum JoinError { @@ -855,8 +854,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("join-about")) + .override_usage(format_usage(&get_message("join-usage"))) .infer_long_args(true) .arg( Arg::new("a") diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 8d8aa0b614d..8d78edd9875 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -14,8 +14,7 @@ use uucore::error::{FromIo, UResult, USimpleError}; use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value}; use uucore::{format_usage, help_about, help_usage, show}; -static ABOUT: &str = help_about!("kill.md"); -const USAGE: &str = help_usage!("kill.md"); +use uucore::locale::{self, get_message}; // When the -l option is selected, the program displays the type of signal related to a certain // value or string. In case of a value, the program should control the lower 8 bits, but there is @@ -104,8 +103,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("kill-about")) + .override_usage(format_usage(&get_message("kill-usage"))) .infer_long_args(true) .allow_negative_numbers(true) .arg( diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 31f1239d86c..1fcb0186f52 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -11,8 +11,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_usage}; -static ABOUT: &str = help_about!("link.md"); -const USAGE: &str = help_usage!("link.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static FILES: &str = "FILES"; @@ -36,8 +35,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("link-about")) + .override_usage(format_usage(&get_message("link-usage"))) .infer_long_args(true) .arg( Arg::new(options::FILES) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index c464076481e..ec3c2638ed7 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -70,9 +70,7 @@ impl UError for LnError { } } -const ABOUT: &str = help_about!("ln.md"); -const USAGE: &str = help_usage!("ln.md"); -const AFTER_HELP: &str = help_section!("after help", "ln.md"); +use uucore::locale::{self, get_message}; mod options { pub const FORCE: &str = "force"; @@ -93,7 +91,8 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let after_help = format!( - "{AFTER_HELP}\n\n{}", + "{}\n\n{}", + get_message("ln-after-help"), backup_control::BACKUP_CONTROL_LONG_HELP ); @@ -144,8 +143,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("ln-about")) + .override_usage(format_usage(&get_message("ln-usage"))) .infer_long_args(true) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 5437bbae344..a6cdde7df4e 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -25,8 +25,7 @@ fn get_userlogin() -> Option { } } -const ABOUT: &str = help_about!("logname.md"); -const USAGE: &str = help_usage!("logname.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -43,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(uucore::util_name()) + .about(get_message("logname-about")) .infer_long_args(true) } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index b2c98689de8..33975b217cc 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -82,9 +82,7 @@ static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not e #[cfg(feature = "selinux")] static CONTEXT_HELP_TEXT: &str = "print any security context of each file"; -const ABOUT: &str = help_about!("ls.md"); -const AFTER_HELP: &str = help_section!("after help", "ls.md"); -const USAGE: &str = help_usage!("ls.md"); +use uucore::locale::{self, get_message}; pub mod options { pub mod format { @@ -1179,8 +1177,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("ls-usage"))) + .about(get_message("ls-about")) .infer_long_args(true) .disable_help_flag(true) .args_override_self(true) @@ -1869,7 +1867,7 @@ pub fn uu_app() -> Command { .value_hint(clap::ValueHint::AnyPath) .value_parser(ValueParser::os_string()), ) - .after_help(AFTER_HELP) + .after_help(get_message("ls-after-help")) } /// Represents a Path along with it's associated data. diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index adef62eee7a..6c181e55f2a 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -20,9 +20,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_if_err}; static DEFAULT_PERM: u32 = 0o777; -const ABOUT: &str = help_about!("mkdir.md"); -const USAGE: &str = help_usage!("mkdir.md"); -const AFTER_HELP: &str = help_section!("after help", "mkdir.md"); +use uucore::locale::{self, get_message}; mod options { pub const MODE: &str = "mode"; @@ -103,7 +101,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Linux-specific options, not implemented // opts.optflag("Z", "context", "set SELinux security context" + // " of each created directory to CTX"), - let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(get_message("mkdir-after-help")) + .try_get_matches_from(args)?; let dirs = matches .get_many::(options::DIRS) @@ -133,8 +133,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("mkdir-about")) + .override_usage(format_usage(&get_message("mkdir-usage"))) .infer_long_args(true) .arg( Arg::new(options::MODE) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 24c057ebc94..7d3df867d52 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -13,7 +13,7 @@ use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; static USAGE: &str = help_usage!("mkfifo.md"); -static ABOUT: &str = help_about!("mkfifo.md"); +use uucore::locale::{self, get_message}; mod options { pub static MODE: &str = "mode"; @@ -86,8 +86,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("mkfifo-usage"))) + .about(get_message("mkfifo-about")) .infer_long_args(true) .arg( Arg::new(options::MODE) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 076e639ccfb..801b2256a1b 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -14,9 +14,7 @@ use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError, set_exit_code}; use uucore::{format_usage, help_about, help_section, help_usage}; -const ABOUT: &str = help_about!("mknod.md"); -const USAGE: &str = help_usage!("mknod.md"); -const AFTER_HELP: &str = help_section!("after help", "mknod.md"); +use uucore::locale::{self, get_message}; const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -159,9 +157,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) - .about(ABOUT) + .override_usage(format_usage(&get_message("mknod-usage"))) + .after_help(get_message("mknod-after-help")) + .about(get_message("mknod-about")) .infer_long_args(true) .arg( Arg::new(options::MODE) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index d689115a6c6..21f61660fb7 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -25,8 +25,7 @@ use rand::Rng; use tempfile::Builder; use thiserror::Error; -const ABOUT: &str = help_about!("mktemp.md"); -const USAGE: &str = help_usage!("mktemp.md"); +use uucore::locale::{self, get_message}; static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX"; @@ -347,8 +346,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("mktemp-about")) + .override_usage(format_usage(&get_message("mktemp-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_DIRECTORY) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 97d911f4f3e..c07846d19d8 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -26,8 +26,7 @@ use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::{display::Quotable, show}; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("more.md"); -const USAGE: &str = help_usage!("more.md"); +use uucore::locale::{self, get_message}; const BELL: char = '\x07'; // Printing this character will ring the bell // The prompt to be displayed at the top of the screen when viewing multiple files, @@ -155,8 +154,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("more-about")) + .override_usage(format_usage(&get_message("more-usage"))) .version(uucore::crate_version!()) .infer_long_args(true) .arg( diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index edfa505c521..195c79e11f4 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -123,9 +123,7 @@ pub enum OverwriteMode { Force, } -const ABOUT: &str = help_about!("mv.md"); -const USAGE: &str = help_usage!("mv.md"); -const AFTER_HELP: &str = help_section!("after help", "mv.md"); +use uucore::locale::{self, get_message}; static OPT_FORCE: &str = "force"; static OPT_INTERACTIVE: &str = "interactive"; @@ -205,10 +203,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("mv-about")) + .override_usage(format_usage(&get_message("mv-usage"))) .after_help(format!( - "{AFTER_HELP}\n\n{}", + "{}\n\n{}", + get_message("mv-after-help"), backup_control::BACKUP_CONTROL_LONG_HELP )) .infer_long_args(true) diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 05ae2fa94da..927d9ad4b37 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -21,8 +21,7 @@ pub mod options { pub static COMMAND: &str = "COMMAND"; } -const ABOUT: &str = help_about!("nice.md"); -const USAGE: &str = help_usage!("nice.md"); +use uucore::locale::{self, get_message}; fn is_prefix_of(maybe_prefix: &str, target: &str, min_match: usize) -> bool { if maybe_prefix.len() < min_match || maybe_prefix.len() > target.len() { @@ -186,8 +185,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("nice-about")) + .override_usage(format_usage(&get_message("nice-usage"))) .trailing_var_arg(true) .infer_long_args(true) .version(uucore::crate_version!()) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 6380417e0e8..370d9e780f7 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -12,9 +12,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_error}; mod helper; -const ABOUT: &str = help_about!("nl.md"); -const AFTER_HELP: &str = help_section!("after help", "nl.md"); -const USAGE: &str = help_usage!("nl.md"); +use uucore::locale::{self, get_message}; // Settings store options used by nl to produce its output. pub struct Settings { @@ -222,10 +220,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) - .about(ABOUT) + .about(get_message("nl-about")) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .override_usage(format_usage(&get_message("nl-usage"))) + .after_help(get_message("nl-after-help")) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 73003a16416..4fa8ed37eda 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -19,9 +19,7 @@ use uucore::display::Quotable; use uucore::error::{UClapError, UError, UResult, set_exit_code}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; -const ABOUT: &str = help_about!("nohup.md"); -const AFTER_HELP: &str = help_section!("after help", "nohup.md"); -const USAGE: &str = help_usage!("nohup.md"); +use uucore::locale::{self, get_message}; static NOHUP_OUT: &str = "nohup.out"; // exit codes that match the GNU implementation static EXIT_CANCELED: i32 = 125; @@ -92,9 +90,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("nohup-about")) + .after_help(get_message("nohup-after-help")) + .override_usage(format_usage(&get_message("nohup-usage"))) .arg( Arg::new(options::CMD) .hide(true) diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index a3a80724dc9..008cd00505c 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -23,8 +23,7 @@ pub const _SC_NPROCESSORS_CONF: libc::c_int = 1001; static OPT_ALL: &str = "all"; static OPT_IGNORE: &str = "ignore"; -const ABOUT: &str = help_about!("nproc.md"); -const USAGE: &str = help_usage!("nproc.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -94,8 +93,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("nproc-about")) + .override_usage(format_usage(&get_message("nproc-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_ALL) diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index cfa7c30d898..c82ff9453dc 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -24,9 +24,7 @@ pub mod format; pub mod options; mod units; -const ABOUT: &str = help_about!("numfmt.md"); -const AFTER_HELP: &str = help_section!("after help", "numfmt.md"); -const USAGE: &str = help_usage!("numfmt.md"); +use uucore::locale::{self, get_message}; fn handle_args<'a>(args: impl Iterator, options: &NumfmtOptions) -> UResult<()> { for l in args { @@ -277,9 +275,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("numfmt-about")) + .after_help(get_message("numfmt-after-help")) + .override_usage(format_usage(&get_message("numfmt-usage"))) .allow_negative_numbers(true) .infer_long_args(true) .arg( diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 652a0ce3f51..1a728c937f5 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -50,9 +50,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_error, sho const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes -const ABOUT: &str = help_about!("od.md"); -const USAGE: &str = help_usage!("od.md"); -const AFTER_HELP: &str = help_section!("after help", "od.md"); +use uucore::locale::{self, get_message}; pub(crate) mod options { pub const HELP: &str = "help"; @@ -253,9 +251,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("od-about")) + .override_usage(format_usage(&get_message("od-usage"))) + .after_help(get_message("od-after-help")) .trailing_var_arg(true) .dont_delimit_trailing_values(true) .infer_long_args(true) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 98679b74635..64c27384e07 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -14,8 +14,7 @@ use uucore::error::{UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("paste.md"); -const USAGE: &str = help_usage!("paste.md"); +use uucore::locale::{self, get_message}; mod options { pub const DELIMITER: &str = "delimiters"; @@ -43,8 +42,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("paste-about")) + .override_usage(format_usage(&get_message("paste-usage"))) .infer_long_args(true) .arg( Arg::new(options::SERIAL) diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 183d67a0beb..d27df4c5110 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -20,8 +20,7 @@ enum Mode { Both, // a combination of `Basic` and `Extra` } -const ABOUT: &str = help_about!("pathchk.md"); -const USAGE: &str = help_usage!("pathchk.md"); +use uucore::locale::{self, get_message}; mod options { pub const POSIX: &str = "posix"; @@ -80,8 +79,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("pathchk-about")) + .override_usage(format_usage(&get_message("pathchk-usage"))) .infer_long_args(true) .arg( Arg::new(options::POSIX) diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 8246f86556e..5b1ea9ddf7e 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -20,9 +20,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -const ABOUT: &str = help_about!("pinky.md"); - -const USAGE: &str = help_usage!("pinky.md"); +use uucore::locale::{self, get_message}; mod options { pub const LONG_FORMAT: &str = "long_format"; @@ -44,8 +42,8 @@ use platform::uumain; pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("pinky-about")) + .override_usage(format_usage(&get_message("pinky-usage"))) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index e24f9cf18e5..0741f17c5b3 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -20,9 +20,7 @@ use uucore::display::Quotable; use uucore::error::UResult; use uucore::{format_usage, help_about, help_section, help_usage}; -const ABOUT: &str = help_about!("pr.md"); -const USAGE: &str = help_usage!("pr.md"); -const AFTER_HELP: &str = help_section!("after help", "pr.md"); +use uucore::locale::{self, get_message}; const TAB: char = '\t'; const LINES_PER_PAGE: usize = 66; const LINES_PER_PAGE_FOR_FORM_FEED: usize = 63; @@ -153,9 +151,9 @@ enum PrError { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("pr-about")) + .after_help(get_message("pr-after-help")) + .override_usage(format_usage(&get_message("pr-usage"))) .infer_long_args(true) .args_override_self(true) .disable_help_flag(true) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 4584a09b858..85925bf8ff6 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -7,8 +7,7 @@ use clap::{Arg, ArgAction, Command}; use std::env; use uucore::{error::UResult, format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("printenv.md"); -const USAGE: &str = help_usage!("printenv.md"); +use uucore::locale::{self, get_message}; static OPT_NULL: &str = "null"; @@ -56,8 +55,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("printenv-about")) + .override_usage(format_usage(&get_message("printenv-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_NULL) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 887ad4107a7..bc36305cc98 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -11,9 +11,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes const VERSION: &str = "version"; const HELP: &str = "help"; -const USAGE: &str = help_usage!("printf.md"); -const ABOUT: &str = help_about!("printf.md"); -const AFTER_HELP: &str = help_section!("after help", "printf.md"); +use uucore::locale::{self, get_message}; mod options { pub const FORMAT: &str = "FORMAT"; @@ -81,9 +79,9 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .allow_hyphen_values(true) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("printf-about")) + .after_help(get_message("printf-after-help")) + .override_usage(format_usage(&get_message("printf-usage"))) .disable_help_flag(true) .disable_version_flag(true) .arg( diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index bb5b2928283..82e7cec5201 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -19,8 +19,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, UUsageError}; use uucore::{format_usage, help_about, help_usage}; -const USAGE: &str = help_usage!("ptx.md"); -const ABOUT: &str = help_about!("ptx.md"); +use uucore::locale::{self, get_message}; #[derive(Debug)] enum OutFormat { @@ -765,9 +764,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) - .about(ABOUT) + .about(get_message("ptx-about")) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) + .override_usage(format_usage(&get_message("ptx-usage"))) .infer_long_args(true) .arg( Arg::new(options::FILE) diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index b924af241fe..b7d9ea69d78 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -13,8 +13,7 @@ use uucore::{format_usage, help_about, help_usage}; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; -const ABOUT: &str = help_about!("pwd.md"); -const USAGE: &str = help_usage!("pwd.md"); +use uucore::locale::{self, get_message}; const OPT_LOGICAL: &str = "logical"; const OPT_PHYSICAL: &str = "physical"; @@ -141,8 +140,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("pwd-about")) + .override_usage(format_usage(&get_message("pwd-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_LOGICAL) diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 211422e035f..d05efa61051 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -15,8 +15,7 @@ use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage, show_error}; -const ABOUT: &str = help_about!("readlink.md"); -const USAGE: &str = help_usage!("readlink.md"); +use uucore::locale::{self, get_message}; const OPT_CANONICALIZE: &str = "canonicalize"; const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; @@ -102,8 +101,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("readlink-about")) + .override_usage(format_usage(&get_message("readlink-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_CANONICALIZE) diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 94532b75505..3a947d9a070 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -21,8 +21,7 @@ use uucore::{ show_if_err, }; -static ABOUT: &str = help_about!("realpath.md"); -const USAGE: &str = help_usage!("realpath.md"); +use uucore::locale::{self, get_message}; static OPT_QUIET: &str = "quiet"; static OPT_STRIP: &str = "strip"; @@ -89,8 +88,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("realpath-about")) + .override_usage(format_usage(&get_message("realpath-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_QUIET) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 3f48e311b45..367ae04fb10 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -90,9 +90,7 @@ impl Default for Options { } } -const ABOUT: &str = help_about!("rm.md"); -const USAGE: &str = help_usage!("rm.md"); -const AFTER_HELP: &str = help_section!("after help", "rm.md"); +use uucore::locale::{self, get_message}; static OPT_DIR: &str = "dir"; static OPT_INTERACTIVE: &str = "interactive"; @@ -202,9 +200,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("rm-about")) + .override_usage(format_usage(&get_message("rm-usage"))) + .after_help(get_message("rm-after-help")) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 8c9dc12b665..8be21e6182b 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -16,8 +16,7 @@ use uucore::error::{UResult, set_exit_code, strip_errno}; use uucore::{format_usage, help_about, help_usage, show_error, util_name}; -static ABOUT: &str = help_about!("rmdir.md"); -const USAGE: &str = help_usage!("rmdir.md"); +use uucore::locale::{self, get_message}; static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; static OPT_PARENTS: &str = "parents"; static OPT_VERBOSE: &str = "verbose"; @@ -165,8 +164,8 @@ struct Opts { pub fn uu_app() -> Command { Command::new(util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("rmdir-about")) + .override_usage(format_usage(&get_message("rmdir-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_IGNORE_FAIL_NON_EMPTY) diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index 658aa33b252..01f692ff312 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -23,8 +23,7 @@ mod errors; use errors::error_exit_status; use errors::{Error, Result, RunconError}; -const ABOUT: &str = help_about!("runcon.md"); -const USAGE: &str = help_usage!("runcon.md"); +use uucore::locale::{self, get_message}; const DESCRIPTION: &str = help_section!("after help", "runcon.md"); pub mod options { @@ -90,9 +89,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) + .about(get_message("runcon-about")) .after_help(DESCRIPTION) - .override_usage(format_usage(USAGE)) + .override_usage(format_usage(&get_message("runcon-usage"))) .infer_long_args(true) .arg( Arg::new(options::COMPUTE) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index af7ca2f84d0..bd7d9b3f257 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -28,8 +28,7 @@ mod numberparse; use crate::error::SeqError; use crate::number::PreciseNumber; -const ABOUT: &str = help_about!("seq.md"); -const USAGE: &str = help_usage!("seq.md"); +use uucore::locale::{self, get_message}; const OPT_SEPARATOR: &str = "separator"; const OPT_TERMINATOR: &str = "terminator"; @@ -222,8 +221,8 @@ pub fn uu_app() -> Command { .trailing_var_arg(true) .infer_long_args(true) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("seq-about")) + .override_usage(format_usage(&get_message("seq-usage"))) .arg( Arg::new(OPT_SEPARATOR) .short('s') diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 30777e28acd..d42d652fcfb 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -20,9 +20,7 @@ use uucore::parser::parse_size::parse_size_u64; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; -const ABOUT: &str = help_about!("shred.md"); -const USAGE: &str = help_usage!("shred.md"); -const AFTER_HELP: &str = help_section!("after help", "shred.md"); +use uucore::locale::{self, get_message}; pub mod options { pub const FORCE: &str = "force"; @@ -316,9 +314,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("shred-about")) + .after_help(get_message("shred-after-help")) + .override_usage(format_usage(&get_message("shred-usage"))) .infer_long_args(true) .arg( Arg::new(options::FORCE) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 9c08ea28d6f..73ee2b37164 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -30,7 +30,7 @@ enum Mode { } static USAGE: &str = help_usage!("shuf.md"); -static ABOUT: &str = help_about!("shuf.md"); +use uucore::locale::{self, get_message}; struct Options { head_count: usize, @@ -143,9 +143,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) - .about(ABOUT) + .about(get_message("shuf-about")) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) + .override_usage(format_usage(&get_message("shuf-usage"))) .infer_long_args(true) .arg( Arg::new(options::ECHO) diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 2b533eadebb..ff5b9ae2df7 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -15,8 +15,7 @@ use uucore::{ use clap::{Arg, ArgAction, Command}; -static ABOUT: &str = help_about!("sleep.md"); -const USAGE: &str = help_usage!("sleep.md"); +use uucore::locale::{self, get_message}; static AFTER_HELP: &str = help_section!("after help", "sleep.md"); mod options { @@ -47,9 +46,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("sleep-about")) + .after_help(get_message("sleep-after-help")) + .override_usage(format_usage(&get_message("sleep-usage"))) .infer_long_args(true) .arg( Arg::new(options::NUMBER) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7de7eb1edcb..06ae5770d6a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -52,9 +52,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_error}; use crate::tmp_dir::TmpDirWrapper; -const ABOUT: &str = help_about!("sort.md"); -const USAGE: &str = help_usage!("sort.md"); -const AFTER_HELP: &str = help_section!("after help", "sort.md"); +use uucore::locale::{self, get_message}; mod options { pub mod modes { @@ -1340,9 +1338,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("sort-about")) + .after_help(get_message("sort-after-help")) + .override_usage(format_usage(&get_message("sort-usage"))) .infer_long_args(true) .disable_help_flag(true) .disable_version_flag(true) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 64548ea387d..777cc2479a1 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -46,9 +46,7 @@ static OPT_IO_BLKSIZE: &str = "-io-blksize"; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; -const ABOUT: &str = help_about!("split.md"); -const USAGE: &str = help_usage!("split.md"); -const AFTER_HELP: &str = help_section!("after help", "split.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -229,9 +227,9 @@ fn handle_preceding_options( pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(AFTER_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("split-about")) + .after_help(get_message("split-after-help")) + .override_usage(format_usage(&get_message("split-usage"))) .infer_long_args(true) // strategy (mutually exclusive) .arg( diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 16a96c3807d..ca82955b685 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -28,8 +28,7 @@ use std::os::unix::prelude::OsStrExt; use std::path::Path; use std::{env, fs}; -const ABOUT: &str = help_about!("stat.md"); -const USAGE: &str = help_usage!("stat.md"); +use uucore::locale::{self, get_message}; const LONG_USAGE: &str = help_section!("long usage", "stat.md"); mod options { @@ -1149,8 +1148,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("stat-about")) + .override_usage(format_usage(&get_message("stat-usage"))) .infer_long_args(true) .arg( Arg::new(options::DEREFERENCE) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index fa6b2e6302d..644a5f2ce29 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -14,11 +14,11 @@ use std::process; use tempfile::TempDir; use tempfile::tempdir; use uucore::error::{FromIo, UClapError, UResult, USimpleError, UUsageError}; +use uucore::format_usage; use uucore::parser::parse_size::parse_size_u64; use uucore::{format_usage, help_about, help_section, help_usage}; -const ABOUT: &str = help_about!("stdbuf.md"); -const USAGE: &str = help_usage!("stdbuf.md"); +use uucore::locale::{self, get_message}; const LONG_HELP: &str = help_section!("after help", "stdbuf.md"); mod options { @@ -204,9 +204,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .after_help(LONG_HELP) - .override_usage(format_usage(USAGE)) + .about(get_message("stdbuf-about")) + .after_help(get_message("stdbuf-after-help")) + .override_usage(format_usage(&get_message("stdbuf-usage"))) .trailing_var_arg(true) .infer_long_args(true) .arg( diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 4e2ceaa3fdf..e60e48cb22f 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -21,6 +21,7 @@ use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; use uucore::error::{UResult, USimpleError}; +use uucore::locale::get_message; use uucore::{format_usage, help_about, help_usage}; #[cfg(not(any( @@ -34,9 +35,6 @@ use uucore::{format_usage, help_about, help_usage}; use flags::BAUD_RATES; use flags::{CONTROL_CHARS, CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; -const USAGE: &str = help_usage!("stty.md"); -const SUMMARY: &str = help_about!("stty.md"); - #[derive(Clone, Copy, Debug)] pub struct Flag { name: &'static str, @@ -476,8 +474,8 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(SUMMARY) + .override_usage(format_usage(&get_message("stty-usage"))) + .about(get_message("stty-about")) .infer_long_args(true) .arg( Arg::new(options::ALL) diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 1aec0ef98c3..57bdbccc6cc 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -13,8 +13,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; -const USAGE: &str = help_usage!("sum.md"); -const ABOUT: &str = help_about!("sum.md"); +use uucore::locale::{self, get_message}; fn bsd_sum(mut reader: impl Read) -> std::io::Result<(usize, u16)> { let mut buf = [0; 4096]; @@ -138,8 +137,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("sum-usage"))) + .about(get_message("sum-about")) .infer_long_args(true) .arg( Arg::new(options::FILE) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index b0221d3d764..0f45a74a1cb 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -19,8 +19,7 @@ use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("sync.md"); -const USAGE: &str = help_usage!("sync.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static FILE_SYSTEM: &str = "file-system"; @@ -222,8 +221,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("sync-about")) + .override_usage(format_usage(&get_message("sync-usage"))) .infer_long_args(true) .arg( Arg::new(options::FILE_SYSTEM) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 4496c2ce120..bcad03b137e 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -22,7 +22,7 @@ use uucore::{format_usage, help_about, help_usage, show}; use crate::error::TacError; static USAGE: &str = help_usage!("tac.md"); -static ABOUT: &str = help_about!("tac.md"); +use uucore::locale::{self, get_message}; mod options { pub static BEFORE: &str = "before"; @@ -58,8 +58,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("tac-usage"))) + .about(get_message("tac-about")) .infer_long_args(true) .arg( Arg::new(options::BEFORE) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 61448388c29..329f5c58d03 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -18,8 +18,7 @@ use uucore::parser::parse_time; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_usage, show_warning}; -const ABOUT: &str = help_about!("tail.md"); -const USAGE: &str = help_usage!("tail.md"); +use uucore::locale::{self, get_message}; pub mod options { pub mod verbosity { @@ -464,8 +463,8 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("tail-about")) + .override_usage(format_usage(&get_message("tail-usage"))) .infer_long_args(true) .arg( Arg::new(options::BYTES) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index b16caeb9327..e366e147496 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -19,9 +19,7 @@ use uucore::{format_usage, help_about, help_section, help_usage, show_error}; #[cfg(unix)] use uucore::signals::{enable_pipe_errors, ignore_interrupts}; -const ABOUT: &str = help_about!("tee.md"); -const USAGE: &str = help_usage!("tee.md"); -const AFTER_HELP: &str = help_section!("after help", "tee.md"); +use uucore::locale::{self, get_message}; mod options { pub const APPEND: &str = "append"; @@ -97,9 +95,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("tee-about")) + .override_usage(format_usage(&get_message("tee-usage"))) + .after_help(get_message("tee-after-help")) .infer_long_args(true) // Since we use value-specific help texts for "--output-error", clap's "short help" and "long help" differ. // However, this is something that the GNU tests explicitly test for, so we *always* show the long help instead. diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index e71e7b19166..16c179bcf52 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -21,31 +21,23 @@ use uucore::error::{UResult, USimpleError}; use uucore::process::{getegid, geteuid}; use uucore::{format_usage, help_about, help_section}; -const ABOUT: &str = help_about!("test.md"); +use uucore::locale::{self, get_message}; // The help_usage method replaces util name (the first word) with {}. // And, The format_usage method replaces {} with execution_phrase ( e.g. test or [ ). // However, This test command has two util names. // So, we use test or [ instead of {} so that the usage string is correct. -const USAGE: &str = "\ -test EXPRESSION -[ -[ EXPRESSION ] -[ ] -[ OPTION -]"; // We use after_help so that this comes after the usage string (it would come before if we used about) -const AFTER_HELP: &str = help_section!("after help", "test.md"); pub fn uu_app() -> Command { // Disable printing of -h and -v as valid alternatives for --help and --version, // since we don't recognize -h and -v as help/version flags. Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .after_help(AFTER_HELP) + .about(get_message("test-about")) + .override_usage(format_usage(&get_message("test-usage"))) + .after_help(get_message("test-after-help")) } #[uucore::main] diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 9de3358015c..f7ab937253b 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -25,8 +25,7 @@ use uucore::{ signals::{signal_by_name_or_value, signal_name_by_value}, }; -const ABOUT: &str = help_about!("timeout.md"); -const USAGE: &str = help_usage!("timeout.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static FOREGROUND: &str = "foreground"; @@ -122,8 +121,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new("timeout") .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("timeout-about")) + .override_usage(format_usage(&get_message("timeout-usage"))) .arg( Arg::new(options::FOREGROUND) .long(options::FOREGROUND) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 6749933f094..22f546496ee 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -83,8 +83,7 @@ pub enum Source { Now, } -const ABOUT: &str = help_about!("touch.md"); -const USAGE: &str = help_usage!("touch.md"); +use uucore::locale::{self, get_message}; pub mod options { // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. @@ -258,8 +257,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("touch-about")) + .override_usage(format_usage(&get_message("touch-usage"))) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 10a636fd7b6..3e3cc61cb52 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -20,9 +20,7 @@ use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show}; -const ABOUT: &str = help_about!("tr.md"); -const AFTER_HELP: &str = help_section!("after help", "tr.md"); -const USAGE: &str = help_usage!("tr.md"); +use uucore::locale::{self, get_message}; mod options { pub const COMPLEMENT: &str = "complement"; @@ -34,7 +32,9 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(get_message("tr-after-help")) + .try_get_matches_from(args)?; let delete_flag = matches.get_flag(options::DELETE); let complement_flag = matches.get_flag(options::COMPLEMENT); @@ -164,8 +164,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("tr-about")) + .override_usage(format_usage(&get_message("tr-usage"))) .infer_long_args(true) .trailing_var_arg(true) .arg( diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 29dae0ba61c..89a302a459d 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -7,7 +7,7 @@ use std::{ffi::OsString, io::Write}; use uucore::error::{UResult, set_exit_code}; use uucore::help_about; -const ABOUT: &str = help_about!("true.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -43,7 +43,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) + .about(get_message("true-about")) // We provide our own help and version options, to ensure maximum compatibility with GNU. .disable_help_flag(true) .disable_version_flag(true) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 056163fa3ad..ccf9c642ed4 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -71,9 +71,7 @@ impl TruncateMode { } } -const ABOUT: &str = help_about!("truncate.md"); -const AFTER_HELP: &str = help_section!("after help", "truncate.md"); -const USAGE: &str = help_usage!("truncate.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static IO_BLOCKS: &str = "io-blocks"; @@ -86,7 +84,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() - .after_help(AFTER_HELP) + .after_help(get_message("truncate-after-help")) .try_get_matches_from(args) .map_err(|e| { e.print().expect("Error writing clap::Error"); @@ -117,8 +115,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("truncate-about")) + .override_usage(format_usage(&get_message("truncate-usage"))) .infer_long_args(true) .arg( Arg::new(options::IO_BLOCKS) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 3f7e89e99ef..ce8ee050ff8 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -11,8 +11,7 @@ use uucore::display::Quotable; use uucore::error::{UError, UResult}; use uucore::{format_usage, help_about, help_usage, show}; -const ABOUT: &str = help_about!("tsort.md"); -const USAGE: &str = help_usage!("tsort.md"); +use uucore::locale::{self, get_message}; mod options { pub const FILE: &str = "file"; @@ -76,8 +75,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("tsort-usage"))) + .about(get_message("tsort-about")) .infer_long_args(true) .arg( Arg::new(options::FILE) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 35dc1f08678..1673ba389b7 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -12,8 +12,7 @@ use std::io::{IsTerminal, Write}; use uucore::error::{UResult, set_exit_code}; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("tty.md"); -const USAGE: &str = help_usage!("tty.md"); +use uucore::locale::{self, get_message}; mod options { pub const SILENT: &str = "silent"; @@ -58,8 +57,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("tty-about")) + .override_usage(format_usage(&get_message("tty-usage"))) .infer_long_args(true) .arg( Arg::new(options::SILENT) diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 2e2b5f42fd2..7c43049e733 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -12,8 +12,7 @@ use uucore::{ format_usage, help_about, help_usage, }; -const ABOUT: &str = help_about!("uname.md"); -const USAGE: &str = help_usage!("uname.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static ALL: &str = "all"; @@ -146,8 +145,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("uname-about")) + .override_usage(format_usage(&get_message("uname-usage"))) .infer_long_args(true) .arg( Arg::new(options::ALL) diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index fb17b971d57..12ea29d8403 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -17,8 +17,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; -const USAGE: &str = help_usage!("unexpand.md"); -const ABOUT: &str = help_about!("unexpand.md"); +use uucore::locale::{self, get_message}; const DEFAULT_TABSTOP: usize = 8; @@ -157,8 +156,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .override_usage(format_usage(USAGE)) - .about(ABOUT) + .override_usage(format_usage(&get_message("unexpand-usage"))) + .about(get_message("unexpand-about")) .infer_long_args(true) .arg( Arg::new(options::FILE) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 2d54a508226..9147b3392f3 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -17,9 +17,7 @@ use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::posix::{OBSOLETE, posix_version}; use uucore::{format_usage, help_about, help_section, help_usage}; -const ABOUT: &str = help_about!("uniq.md"); -const USAGE: &str = help_usage!("uniq.md"); -const AFTER_HELP: &str = help_section!("after help", "uniq.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static ALL_REPEATED: &str = "all-repeated"; @@ -593,10 +591,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("uniq-about")) + .override_usage(format_usage(&get_message("uniq-usage"))) .infer_long_args(true) - .after_help(AFTER_HELP) + .after_help(get_message("uniq-after-help")) .arg( Arg::new(options::ALL_REPEATED) .short('D') diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 09a1b0f1233..311a8482bdf 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -14,8 +14,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("unlink.md"); -const USAGE: &str = help_usage!("unlink.md"); +use uucore::locale::{self, get_message}; static OPT_PATH: &str = "FILE"; #[uucore::main] @@ -30,8 +29,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("unlink-about")) + .override_usage(format_usage(&get_message("unlink-usage"))) .infer_long_args(true) .arg( Arg::new(OPT_PATH) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index e001a64a8ef..25743af7849 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -31,9 +31,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -const ABOUT: &str = help_about!("uptime.md"); - -const USAGE: &str = help_usage!("uptime.md"); +use uucore::locale::{self, get_message}; pub mod options { pub static SINCE: &str = "since"; @@ -78,8 +76,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { let cmd = Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("uptime-about")) + .override_usage(format_usage(&get_message("uptime-usage"))) .infer_long_args(true) .arg( Arg::new(options::SINCE) diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 192ed0b579d..ff0f4190e84 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -26,9 +26,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -const ABOUT: &str = help_about!("users.md"); - -const USAGE: &str = help_usage!("users.md"); +use uucore::locale::{self, get_message}; #[cfg(target_os = "openbsd")] const OPENBSD_UTMP_FILE: &str = "/var/run/utmp"; @@ -97,8 +95,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("users-about")) + .override_usage(format_usage(&get_message("users-usage"))) .infer_long_args(true) .arg( Arg::new(ARG_FILE) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 47abfe2102f..f10c74d8254 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -113,8 +113,7 @@ impl<'a> Settings<'a> { } } -const ABOUT: &str = help_about!("wc.md"); -const USAGE: &str = help_usage!("wc.md"); +use uucore::locale::{self, get_message}; mod options { pub static BYTES: &str = "bytes"; @@ -397,8 +396,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("wc-about")) + .override_usage(format_usage(&get_message("wc-usage"))) .infer_long_args(true) .args_override_self(true) .arg( diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 2203bbbd119..9fb6d1c195f 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -37,9 +37,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -const ABOUT: &str = help_about!("who.md"); - -const USAGE: &str = help_usage!("who.md"); +use uucore::locale::{self, get_message}; #[cfg(target_os = "linux")] static RUNLEVEL_HELP: &str = "print current runlevel"; @@ -52,8 +50,8 @@ use platform::uumain; pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("who-about")) + .override_usage(format_usage(&get_message("who-usage"))) .infer_long_args(true) .arg( Arg::new(options::ALL) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index a1fe6e62239..4e1cc795c88 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -13,8 +13,7 @@ use uucore::{format_usage, help_about, help_usage}; mod platform; -const ABOUT: &str = help_about!("whoami.md"); -const USAGE: &str = help_usage!("whoami.md"); +use uucore::locale::{self, get_message}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -32,7 +31,7 @@ pub fn whoami() -> UResult { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("whoami-about")) + .override_usage(uucore::util_name()) .infer_long_args(true) } diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index df77be28947..33e922c7699 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -14,8 +14,7 @@ use uucore::error::{UResult, USimpleError}; use uucore::signals::enable_pipe_errors; use uucore::{format_usage, help_about, help_usage}; -const ABOUT: &str = help_about!("yes.md"); -const USAGE: &str = help_usage!("yes.md"); +use uucore::locale::{self, get_message}; // it's possible that using a smaller or larger buffer might provide better performance on some // systems, but honestly this is good enough @@ -39,8 +38,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) + .about(get_message("yes-about")) + .override_usage(format_usage(&get_message("yes-usage"))) .arg( Arg::new("STRING") .value_parser(ValueParser::os_string()) From 28c16dd6d15c337065e75b0f8465943dadd25026 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 17:42:27 +0200 Subject: [PATCH 111/139] l10n: convert the PROG.md command to fluent --- src/uu/base32/locales/en-US.ftl | 9 ++ src/uu/base64/locales/en-US.ftl | 9 ++ src/uu/basename/locales/en-US.ftl | 4 + src/uu/basenc/locales/en-US.ftl | 7 + src/uu/cat/locales/en-US.ftl | 3 + src/uu/chcon/locales/en-US.ftl | 5 + src/uu/chgrp/locales/en-US.ftl | 3 + src/uu/chmod/locales/en-US.ftl | 6 + src/uu/chown/locales/en-US.ftl | 3 + src/uu/chroot/locales/en-US.ftl | 2 + src/uu/cksum/locales/en-US.ftl | 16 ++ src/uu/comm/locales/en-US.ftl | 8 + src/uu/cp/locales/en-US.ftl | 17 ++ src/uu/csplit/locales/en-US.ftl | 3 + src/uu/cut/locales/en-US.ftl | 92 +++++++++++ src/uu/date/locales/en-US.ftl | 76 +++++++++ src/uu/dd/locales/en-US.ftl | 115 +++++++++++++ src/uu/df/locales/en-US.ftl | 10 ++ src/uu/dircolors/locales/en-US.ftl | 5 + src/uu/dirname/locales/en-US.ftl | 4 + src/uu/du/locales/en-US.ftl | 16 ++ src/uu/echo/locales/en-US.ftl | 18 +++ src/uu/env/locales/en-US.ftl | 3 + src/uu/expand/locales/en-US.ftl | 3 + src/uu/expr/locales/en-US.ftl | 45 ++++++ src/uu/factor/locales/en-US.ftl | 3 + src/uu/false/locales/en-US.ftl | 5 + src/uu/fmt/locales/en-US.ftl | 2 + src/uu/fold/locales/en-US.ftl | 3 + src/uu/groups/locales/en-US.ftl | 3 + src/uu/hashsum/locales/en-US.ftl | 2 + src/uu/head/locales/en-US.ftl | 6 + src/uu/hostid/locales/en-US.ftl | 2 + src/uu/hostname/locales/en-US.ftl | 2 + src/uu/id/locales/en-US.ftl | 10 ++ src/uu/install/locales/en-US.ftl | 3 + src/uu/join/locales/en-US.ftl | 5 + src/uu/kill/locales/en-US.ftl | 2 + src/uu/link/locales/en-US.ftl | 2 + src/uu/ln/locales/en-US.ftl | 13 ++ src/uu/logname/locales/en-US.ftl | 1 + src/uu/ls/locales/en-US.ftl | 4 + src/uu/mkdir/locales/en-US.ftl | 3 + src/uu/mkfifo/locales/en-US.ftl | 2 + src/uu/mknod/locales/en-US.ftl | 17 ++ src/uu/mktemp/locales/en-US.ftl | 2 + src/uu/more/locales/en-US.ftl | 2 + src/uu/mv/locales/en-US.ftl | 16 ++ src/uu/nice/locales/en-US.ftl | 5 + src/uu/nl/locales/en-US.ftl | 15 ++ src/uu/nohup/locales/en-US.ftl | 7 + src/uu/nproc/locales/en-US.ftl | 4 + src/uu/numfmt/locales/en-US.ftl | 36 +++++ src/uu/od/locales/en-US.ftl | 41 +++++ src/uu/paste/locales/en-US.ftl | 3 + src/uu/pathchk/locales/en-US.ftl | 2 + src/uu/pinky/locales/en-US.ftl | 2 + src/uu/pr/locales/en-US.ftl | 19 +++ src/uu/printenv/locales/en-US.ftl | 2 + src/uu/printf/locales/en-US.ftl | 249 +++++++++++++++++++++++++++++ src/uu/ptx/locales/en-US.ftl | 6 + src/uu/pwd/locales/en-US.ftl | 2 + src/uu/readlink/locales/en-US.ftl | 2 + src/uu/realpath/locales/en-US.ftl | 2 + src/uu/rm/locales/en-US.ftl | 14 ++ src/uu/rmdir/locales/en-US.ftl | 2 + src/uu/runcon/locales/en-US.ftl | 10 ++ src/uu/seq/locales/en-US.ftl | 4 + src/uu/shred/locales/en-US.ftl | 37 +++++ src/uu/shuf/locales/en-US.ftl | 6 + src/uu/sleep/locales/en-US.ftl | 8 + src/uu/sort/locales/en-US.ftl | 11 ++ src/uu/split/locales/en-US.ftl | 16 ++ src/uu/stat/locales/en-US.ftl | 54 +++++++ src/uu/stdbuf/locales/en-US.ftl | 16 ++ src/uu/stty/locales/en-US.ftl | 4 + src/uu/sum/locales/en-US.ftl | 4 + src/uu/sync/locales/en-US.ftl | 2 + src/uu/tac/locales/en-US.ftl | 2 + src/uu/tail/locales/en-US.ftl | 6 + src/uu/tee/locales/en-US.ftl | 3 + src/uu/test/locales/en-US.ftl | 71 ++++++++ src/uu/timeout/locales/en-US.ftl | 2 + src/uu/touch/locales/en-US.ftl | 2 + src/uu/tr/locales/en-US.ftl | 3 + src/uu/true/locales/en-US.ftl | 5 + src/uu/truncate/locales/en-US.ftl | 18 +++ src/uu/tsort/locales/en-US.ftl | 5 + src/uu/tty/locales/en-US.ftl | 2 + src/uu/uname/locales/en-US.ftl | 3 + src/uu/unexpand/locales/en-US.ftl | 3 + src/uu/uniq/locales/en-US.ftl | 7 + src/uu/unlink/locales/en-US.ftl | 3 + src/uu/uptime/locales/en-US.ftl | 4 + src/uu/users/locales/en-US.ftl | 2 + src/uu/vdir/locales/en-US.ftl | 5 + src/uu/wc/locales/en-US.ftl | 3 + src/uu/who/locales/en-US.ftl | 2 + src/uu/whoami/locales/en-US.ftl | 1 + src/uu/yes/locales/en-US.ftl | 2 + 100 files changed, 1331 insertions(+) create mode 100644 src/uu/base32/locales/en-US.ftl create mode 100644 src/uu/base64/locales/en-US.ftl create mode 100644 src/uu/basename/locales/en-US.ftl create mode 100644 src/uu/basenc/locales/en-US.ftl create mode 100644 src/uu/cat/locales/en-US.ftl create mode 100644 src/uu/chcon/locales/en-US.ftl create mode 100644 src/uu/chgrp/locales/en-US.ftl create mode 100644 src/uu/chmod/locales/en-US.ftl create mode 100644 src/uu/chown/locales/en-US.ftl create mode 100644 src/uu/chroot/locales/en-US.ftl create mode 100644 src/uu/cksum/locales/en-US.ftl create mode 100644 src/uu/comm/locales/en-US.ftl create mode 100644 src/uu/cp/locales/en-US.ftl create mode 100644 src/uu/csplit/locales/en-US.ftl create mode 100644 src/uu/cut/locales/en-US.ftl create mode 100644 src/uu/date/locales/en-US.ftl create mode 100644 src/uu/dd/locales/en-US.ftl create mode 100644 src/uu/df/locales/en-US.ftl create mode 100644 src/uu/dircolors/locales/en-US.ftl create mode 100644 src/uu/dirname/locales/en-US.ftl create mode 100644 src/uu/du/locales/en-US.ftl create mode 100644 src/uu/echo/locales/en-US.ftl create mode 100644 src/uu/env/locales/en-US.ftl create mode 100644 src/uu/expand/locales/en-US.ftl create mode 100644 src/uu/expr/locales/en-US.ftl create mode 100644 src/uu/factor/locales/en-US.ftl create mode 100644 src/uu/false/locales/en-US.ftl create mode 100644 src/uu/fmt/locales/en-US.ftl create mode 100644 src/uu/fold/locales/en-US.ftl create mode 100644 src/uu/groups/locales/en-US.ftl create mode 100644 src/uu/hashsum/locales/en-US.ftl create mode 100644 src/uu/head/locales/en-US.ftl create mode 100644 src/uu/hostid/locales/en-US.ftl create mode 100644 src/uu/hostname/locales/en-US.ftl create mode 100644 src/uu/id/locales/en-US.ftl create mode 100644 src/uu/install/locales/en-US.ftl create mode 100644 src/uu/join/locales/en-US.ftl create mode 100644 src/uu/kill/locales/en-US.ftl create mode 100644 src/uu/link/locales/en-US.ftl create mode 100644 src/uu/ln/locales/en-US.ftl create mode 100644 src/uu/logname/locales/en-US.ftl create mode 100644 src/uu/ls/locales/en-US.ftl create mode 100644 src/uu/mkdir/locales/en-US.ftl create mode 100644 src/uu/mkfifo/locales/en-US.ftl create mode 100644 src/uu/mknod/locales/en-US.ftl create mode 100644 src/uu/mktemp/locales/en-US.ftl create mode 100644 src/uu/more/locales/en-US.ftl create mode 100644 src/uu/mv/locales/en-US.ftl create mode 100644 src/uu/nice/locales/en-US.ftl create mode 100644 src/uu/nl/locales/en-US.ftl create mode 100644 src/uu/nohup/locales/en-US.ftl create mode 100644 src/uu/nproc/locales/en-US.ftl create mode 100644 src/uu/numfmt/locales/en-US.ftl create mode 100644 src/uu/od/locales/en-US.ftl create mode 100644 src/uu/paste/locales/en-US.ftl create mode 100644 src/uu/pathchk/locales/en-US.ftl create mode 100644 src/uu/pinky/locales/en-US.ftl create mode 100644 src/uu/pr/locales/en-US.ftl create mode 100644 src/uu/printenv/locales/en-US.ftl create mode 100644 src/uu/printf/locales/en-US.ftl create mode 100644 src/uu/ptx/locales/en-US.ftl create mode 100644 src/uu/pwd/locales/en-US.ftl create mode 100644 src/uu/readlink/locales/en-US.ftl create mode 100644 src/uu/realpath/locales/en-US.ftl create mode 100644 src/uu/rm/locales/en-US.ftl create mode 100644 src/uu/rmdir/locales/en-US.ftl create mode 100644 src/uu/runcon/locales/en-US.ftl create mode 100644 src/uu/seq/locales/en-US.ftl create mode 100644 src/uu/shred/locales/en-US.ftl create mode 100644 src/uu/shuf/locales/en-US.ftl create mode 100644 src/uu/sleep/locales/en-US.ftl create mode 100644 src/uu/sort/locales/en-US.ftl create mode 100644 src/uu/split/locales/en-US.ftl create mode 100644 src/uu/stat/locales/en-US.ftl create mode 100644 src/uu/stdbuf/locales/en-US.ftl create mode 100644 src/uu/stty/locales/en-US.ftl create mode 100644 src/uu/sum/locales/en-US.ftl create mode 100644 src/uu/sync/locales/en-US.ftl create mode 100644 src/uu/tac/locales/en-US.ftl create mode 100644 src/uu/tail/locales/en-US.ftl create mode 100644 src/uu/tee/locales/en-US.ftl create mode 100644 src/uu/test/locales/en-US.ftl create mode 100644 src/uu/timeout/locales/en-US.ftl create mode 100644 src/uu/touch/locales/en-US.ftl create mode 100644 src/uu/tr/locales/en-US.ftl create mode 100644 src/uu/true/locales/en-US.ftl create mode 100644 src/uu/truncate/locales/en-US.ftl create mode 100644 src/uu/tsort/locales/en-US.ftl create mode 100644 src/uu/tty/locales/en-US.ftl create mode 100644 src/uu/uname/locales/en-US.ftl create mode 100644 src/uu/unexpand/locales/en-US.ftl create mode 100644 src/uu/uniq/locales/en-US.ftl create mode 100644 src/uu/unlink/locales/en-US.ftl create mode 100644 src/uu/uptime/locales/en-US.ftl create mode 100644 src/uu/users/locales/en-US.ftl create mode 100644 src/uu/vdir/locales/en-US.ftl create mode 100644 src/uu/wc/locales/en-US.ftl create mode 100644 src/uu/who/locales/en-US.ftl create mode 100644 src/uu/whoami/locales/en-US.ftl create mode 100644 src/uu/yes/locales/en-US.ftl diff --git a/src/uu/base32/locales/en-US.ftl b/src/uu/base32/locales/en-US.ftl new file mode 100644 index 00000000000..e3d2beb98a6 --- /dev/null +++ b/src/uu/base32/locales/en-US.ftl @@ -0,0 +1,9 @@ +base32-about = encode/decode data and print to standard output + With no FILE, or when FILE is -, read standard input. + + The data are encoded as described for the base32 alphabet in RFC 4648. + When decoding, the input may contain newlines in addition + to the bytes of the formal base32 alphabet. Use --ignore-garbage + to attempt to recover from any other non-alphabet bytes in the + encoded stream. +base32-usage = base32 [OPTION]... [FILE] diff --git a/src/uu/base64/locales/en-US.ftl b/src/uu/base64/locales/en-US.ftl new file mode 100644 index 00000000000..8919205654c --- /dev/null +++ b/src/uu/base64/locales/en-US.ftl @@ -0,0 +1,9 @@ +base64-about = encode/decode data and print to standard output + With no FILE, or when FILE is -, read standard input. + + The data are encoded as described for the base64 alphabet in RFC 3548. + When decoding, the input may contain newlines in addition + to the bytes of the formal base64 alphabet. Use --ignore-garbage + to attempt to recover from any other non-alphabet bytes in the + encoded stream. +base64-usage = base64 [OPTION]... [FILE] diff --git a/src/uu/basename/locales/en-US.ftl b/src/uu/basename/locales/en-US.ftl new file mode 100644 index 00000000000..fd0a8335b42 --- /dev/null +++ b/src/uu/basename/locales/en-US.ftl @@ -0,0 +1,4 @@ +basename-about = Print NAME with any leading directory components removed + If specified, also remove a trailing SUFFIX +basename-usage = basename [-z] NAME [SUFFIX] + basename OPTION... NAME... diff --git a/src/uu/basenc/locales/en-US.ftl b/src/uu/basenc/locales/en-US.ftl new file mode 100644 index 00000000000..520292c85dd --- /dev/null +++ b/src/uu/basenc/locales/en-US.ftl @@ -0,0 +1,7 @@ +basenc-about = Encode/decode data and print to standard output + With no FILE, or when FILE is -, read standard input. + + When decoding, the input may contain newlines in addition to the bytes of + the formal alphabet. Use --ignore-garbage to attempt to recover + from any other non-alphabet bytes in the encoded stream. +basenc-usage = basenc [OPTION]... [FILE] diff --git a/src/uu/cat/locales/en-US.ftl b/src/uu/cat/locales/en-US.ftl new file mode 100644 index 00000000000..108000d5aa1 --- /dev/null +++ b/src/uu/cat/locales/en-US.ftl @@ -0,0 +1,3 @@ +cat-about = Concatenate FILE(s), or standard input, to standard output + With no FILE, or when FILE is -, read standard input. +cat-usage = cat [OPTION]... [FILE]... diff --git a/src/uu/chcon/locales/en-US.ftl b/src/uu/chcon/locales/en-US.ftl new file mode 100644 index 00000000000..66c51f5af5a --- /dev/null +++ b/src/uu/chcon/locales/en-US.ftl @@ -0,0 +1,5 @@ +chcon-about = Change the SELinux security context of each FILE to CONTEXT. + With --reference, change the security context of each FILE to that of RFILE. +chcon-usage = chcon [OPTION]... CONTEXT FILE... + chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... + chcon [OPTION]... --reference=RFILE FILE... diff --git a/src/uu/chgrp/locales/en-US.ftl b/src/uu/chgrp/locales/en-US.ftl new file mode 100644 index 00000000000..5d399aa4da2 --- /dev/null +++ b/src/uu/chgrp/locales/en-US.ftl @@ -0,0 +1,3 @@ +chgrp-about = Change the group of each FILE to GROUP. +chgrp-usage = chgrp [OPTION]... GROUP FILE... + chgrp [OPTION]... --reference=RFILE FILE... diff --git a/src/uu/chmod/locales/en-US.ftl b/src/uu/chmod/locales/en-US.ftl new file mode 100644 index 00000000000..c0d4d39abff --- /dev/null +++ b/src/uu/chmod/locales/en-US.ftl @@ -0,0 +1,6 @@ +chmod-about = Change the mode of each FILE to MODE. + With --reference, change the mode of each FILE to that of RFILE. +chmod-usage = chmod [OPTION]... MODE[,MODE]... FILE... + chmod [OPTION]... OCTAL-MODE FILE... + chmod [OPTION]... --reference=RFILE FILE... +chmod-after-help = Each MODE is of the form [ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+. diff --git a/src/uu/chown/locales/en-US.ftl b/src/uu/chown/locales/en-US.ftl new file mode 100644 index 00000000000..4c8f2a46af3 --- /dev/null +++ b/src/uu/chown/locales/en-US.ftl @@ -0,0 +1,3 @@ +chown-about = Change file owner and group +chown-usage = chown [OPTION]... [OWNER][:[GROUP]] FILE... + chown [OPTION]... --reference=RFILE FILE... diff --git a/src/uu/chroot/locales/en-US.ftl b/src/uu/chroot/locales/en-US.ftl new file mode 100644 index 00000000000..86687fea8b6 --- /dev/null +++ b/src/uu/chroot/locales/en-US.ftl @@ -0,0 +1,2 @@ +chroot-about = Run COMMAND with root directory set to NEWROOT. +chroot-usage = chroot [OPTION]... NEWROOT [COMMAND [ARG]...] diff --git a/src/uu/cksum/locales/en-US.ftl b/src/uu/cksum/locales/en-US.ftl new file mode 100644 index 00000000000..75a4bc8d045 --- /dev/null +++ b/src/uu/cksum/locales/en-US.ftl @@ -0,0 +1,16 @@ +cksum-about = Print CRC and size for each file +cksum-usage = cksum [OPTIONS] [FILE]... +cksum-after-help = DIGEST determines the digest algorithm and default output format: + + - sysv: (equivalent to sum -s) + - bsd: (equivalent to sum -r) + - crc: (equivalent to cksum) + - crc32b: (only available through cksum) + - md5: (equivalent to md5sum) + - sha1: (equivalent to sha1sum) + - sha224: (equivalent to sha224sum) + - sha256: (equivalent to sha256sum) + - sha384: (equivalent to sha384sum) + - sha512: (equivalent to sha512sum) + - blake2b: (equivalent to b2sum) + - sm3: (only available through cksum) diff --git a/src/uu/comm/locales/en-US.ftl b/src/uu/comm/locales/en-US.ftl new file mode 100644 index 00000000000..56b67be37dd --- /dev/null +++ b/src/uu/comm/locales/en-US.ftl @@ -0,0 +1,8 @@ +comm-about = Compare two sorted files line by line. + + When FILE1 or FILE2 (not both) is -, read standard input. + + With no options, produce three-column output. Column one contains + lines unique to FILE1, column two contains lines unique to FILE2, + and column three contains lines common to both files. +comm-usage = comm [OPTION]... FILE1 FILE2 diff --git a/src/uu/cp/locales/en-US.ftl b/src/uu/cp/locales/en-US.ftl new file mode 100644 index 00000000000..7150d1b883b --- /dev/null +++ b/src/uu/cp/locales/en-US.ftl @@ -0,0 +1,17 @@ +cp-about = Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY. +cp-usage = cp [OPTION]... [-T] SOURCE DEST + cp [OPTION]... SOURCE... DIRECTORY + cp [OPTION]... -t DIRECTORY SOURCE... +cp-after-help = Do not copy a non-directory that has an existing destination with the same or newer modification timestamp; + instead, silently skip the file without failing. If timestamps are being preserved, the comparison is to the + source timestamp truncated to the resolutions of the destination file system and of the system calls used to + update timestamps; this avoids duplicate work if several cp -pu commands are executed with the same source + and destination. This option is ignored if the -n or --no-clobber option is also specified. Also, if + --preserve=links is also specified (like with cp -au for example), that will take precedence; consequently, + depending on the order that files are processed from the source, newer files in the destination may be replaced, + to mirror hard links in the source. which gives more control over which existing files in the destination are + replaced, and its value can be one of the following: + + - all This is the default operation when an --update option is not specified, and results in all existing files in the destination being replaced. + - none This is similar to the --no-clobber option, in that no files in the destination are replaced, but also skipping a file does not induce a failure. + - older This is the default operation when --update is specified, and results in files being replaced if they’re older than the corresponding source file. diff --git a/src/uu/csplit/locales/en-US.ftl b/src/uu/csplit/locales/en-US.ftl new file mode 100644 index 00000000000..0198b521b6d --- /dev/null +++ b/src/uu/csplit/locales/en-US.ftl @@ -0,0 +1,3 @@ +csplit-about = Split a file into sections determined by context lines +csplit-usage = csplit [OPTION]... FILE PATTERN... +csplit-after-help = Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output. diff --git a/src/uu/cut/locales/en-US.ftl b/src/uu/cut/locales/en-US.ftl new file mode 100644 index 00000000000..b7feb048cec --- /dev/null +++ b/src/uu/cut/locales/en-US.ftl @@ -0,0 +1,92 @@ +cut-about = Prints specified byte or field columns from each line of stdin or the input files +cut-usage = cut OPTION... [FILE]... +cut-after-help = Each call must specify a mode (what to use for columns), + a sequence (which columns to print), and provide a data source + + ### Specifying a mode + + Use --bytes (-b) or --characters (-c) to specify byte mode + + Use --fields (-f) to specify field mode, where each line is broken into + fields identified by a delimiter character. For example for a typical CSV + you could use this in combination with setting comma as the delimiter + + ### Specifying a sequence + + A sequence is a group of 1 or more numbers or inclusive ranges separated + by a commas. + + cut -f 2,5-7 some_file.txt + + will display the 2nd, 5th, 6th, and 7th field for each source line + + Ranges can extend to the end of the row by excluding the second number + + cut -f 3- some_file.txt + + will display the 3rd field and all fields after for each source line + + The first number of a range can be excluded, and this is effectively the + same as using 1 as the first number: it causes the range to begin at the + first column. Ranges can also display a single column + + cut -f 1,3-5 some_file.txt + + will display the 1st, 3rd, 4th, and 5th field for each source line + + The --complement option, when used, inverts the effect of the sequence + + cut --complement -f 4-6 some_file.txt + + will display the every field but the 4th, 5th, and 6th + + ### Specifying a data source + + If no sourcefile arguments are specified, stdin is used as the source of + lines to print + + If sourcefile arguments are specified, stdin is ignored and all files are + read in consecutively if a sourcefile is not successfully read, a warning + will print to stderr, and the eventual status code will be 1, but cut + will continue to read through proceeding sourcefiles + + To print columns from both STDIN and a file argument, use - (dash) as a + sourcefile argument to represent stdin. + + ### Field Mode options + + The fields in each line are identified by a delimiter (separator) + + #### Set the delimiter + + Set the delimiter which separates fields in the file using the + --delimiter (-d) option. Setting the delimiter is optional. + If not set, a default delimiter of Tab will be used. + + If the -w option is provided, fields will be separated by any number + of whitespace characters (Space and Tab). The output delimiter will + be a Tab unless explicitly specified. Only one of -d or -w option can be specified. + This is an extension adopted from FreeBSD. + + #### Optionally Filter based on delimiter + + If the --only-delimited (-s) flag is provided, only lines which + contain the delimiter will be printed + + #### Replace the delimiter + + If the --output-delimiter option is provided, the argument used for + it will replace the delimiter character in each line printed. This is + useful for transforming tabular data - e.g. to convert a CSV to a + TSV (tab-separated file) + + ### Line endings + + When the --zero-terminated (-z) option is used, cut sees \\0 (null) as the + 'line ending' character (both for the purposes of reading lines and + separating printed lines) instead of \\n (newline). This is useful for + tabular data where some of the cells may contain newlines + + echo 'ab\\0cd' | cut -z -c 1 + + will result in 'a\\0c\\0' diff --git a/src/uu/date/locales/en-US.ftl b/src/uu/date/locales/en-US.ftl new file mode 100644 index 00000000000..8889943beb1 --- /dev/null +++ b/src/uu/date/locales/en-US.ftl @@ -0,0 +1,76 @@ +date-about = + Print or set the system date and time + +date-usage = + date [OPTION]... [+FORMAT]... + date [OPTION]... [MMDDhhmm[[CC]YY][.ss]] + + FORMAT controls the output. Interpreted sequences are: + { "| Sequence | Description | Example |" } + { "| -------- | -------------------------------------------------------------------- | ---------------------- |" } + { "| %% | a literal % | % |" } + { "| %a | locale's abbreviated weekday name | Sun |" } + { "| %A | locale's full weekday name | Sunday |" } + { "| %b | locale's abbreviated month name | Jan |" } + { "| %B | locale's full month name | January |" } + { "| %c | locale's date and time | Thu Mar 3 23:05:25 2005|" } + { "| %C | century; like %Y, except omit last two digits | 20 |" } + { "| %d | day of month | 01 |" } + { "| %D | date; same as %m/%d/%y | 12/31/99 |" } + { "| %e | day of month, space padded; same as %_d | 3 |" } + { "| %F | full date; same as %Y-%m-%d | 2005-03-03 |" } + { "| %g | last two digits of year of ISO week number (see %G) | 05 |" } + { "| %G | year of ISO week number (see %V); normally useful only with %V | 2005 |" } + { "| %h | same as %b | Jan |" } + { "| %H | hour (00..23) | 23 |" } + { "| %I | hour (01..12) | 11 |" } + { "| %j | day of year (001..366) | 062 |" } + { "| %k | hour, space padded ( 0..23); same as %_H | 3 |" } + { "| %l | hour, space padded ( 1..12); same as %_I | 9 |" } + { "| %m | month (01..12) | 03 |" } + { "| %M | minute (00..59) | 30 |" } + { "| %n | a newline | \\n |" } + { "| %N | nanoseconds (000000000..999999999) | 123456789 |" } + { "| %p | locale's equivalent of either AM or PM; blank if not known | PM |" } + { "| %P | like %p, but lower case | pm |" } + { "| %q | quarter of year (1..4) | 1 |" } + { "| %r | locale's 12-hour clock time | 11:11:04 PM |" } + { "| %R | 24-hour hour and minute; same as %H:%M | 23:30 |" } + { "| %s | seconds since 1970-01-01 00:00:00 UTC | 1615432800 |" } + { "| %S | second (00..60) | 30 |" } + { "| %t | a tab | \\t |" } + { "| %T | time; same as %H:%M:%S | 23:30:30 |" } + { "| %u | day of week (1..7); 1 is Monday | 4 |" } + { "| %U | week number of year, with Sunday as first day of week (00..53) | 10 |" } + { "| %V | ISO week number, with Monday as first day of week (01..53) | 12 |" } + { "| %w | day of week (0..6); 0 is Sunday | 4 |" } + { "| %W | week number of year, with Monday as first day of week (00..53) | 11 |" } + { "| %x | locale's date representation | 03/03/2005 |" } + { "| %X | locale's time representation | 23:30:30 |" } + { "| %y | last two digits of year (00..99) | 05 |" } + { "| %Y | year | 2005 |" } + { "| %z | +hhmm numeric time zone | -0400 |" } + { "| %:z | +hh:mm numeric time zone | -04:00 |" } + { "| %::z | +hh:mm:ss numeric time zone | -04:00:00 |" } + { "| %:::z | numeric time zone with : to necessary precision | -04, +05:30 |" } + { "| %Z | alphabetic time zone abbreviation | EDT |" } + + By default, date pads numeric fields with zeroes. + The following optional flags may follow '%': + { "* `-` (hyphen) do not pad the field" } + { "* `_` (underscore) pad with spaces" } + { "* `0` (zero) pad with zeros" } + { "* `^` use upper case if possible" } + { "* `#` use opposite case if possible" } + After any flags comes an optional field width, as a decimal number; + then an optional modifier, which is either + { "* `E` to use the locale's alternate representations if available, or" } + { "* `O` to use the locale's alternate numeric symbols if available." } + Examples: + Convert seconds since the epoch (1970-01-01 UTC) to a date + + date --date='@2147483647' + + Show the time on the west coast of the US (use tzselect(1) to find TZ) + + TZ='America/Los_Angeles' date diff --git a/src/uu/dd/locales/en-US.ftl b/src/uu/dd/locales/en-US.ftl new file mode 100644 index 00000000000..56f391c79b8 --- /dev/null +++ b/src/uu/dd/locales/en-US.ftl @@ -0,0 +1,115 @@ +dd-about = Copy, and optionally convert, a file system resource +dd-usage = dd [OPERAND]... + dd OPTION +dd-after-help = ### Operands + + - bs=BYTES : read and write up to BYTES bytes at a time (default: 512); + overwrites ibs and obs. + - cbs=BYTES : the 'conversion block size' in bytes. Applies to the + conv=block, and conv=unblock operations. + - conv=CONVS : a comma-separated list of conversion options or (for legacy + reasons) file flags. + - count=N : stop reading input after N ibs-sized read operations rather + than proceeding until EOF. See iflag=count_bytes if stopping after N bytes + is preferred + - ibs=N : the size of buffer used for reads (default: 512) + - if=FILE : the file used for input. When not specified, stdin is used instead + - iflag=FLAGS : a comma-separated list of input flags which specify how the + input source is treated. FLAGS may be any of the input-flags or general-flags + specified below. + - skip=N (or iseek=N) : skip N ibs-sized records into input before beginning + copy/convert operations. See iflag=seek_bytes if seeking N bytes is preferred. + - obs=N : the size of buffer used for writes (default: 512) + - of=FILE : the file used for output. When not specified, stdout is used + instead + - oflag=FLAGS : comma separated list of output flags which specify how the + output source is treated. FLAGS may be any of the output flags or general + flags specified below + - seek=N (or oseek=N) : seeks N obs-sized records into output before + beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is + preferred + - status=LEVEL : controls whether volume and performance stats are written to + stderr. + + When unspecified, dd will print stats upon completion. An example is below. + + ```plain + 6+0 records in + 16+0 records out + 8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s, + 14.4 MB/s + + The first two lines are the 'volume' stats and the final line is the + 'performance' stats. + The volume stats indicate the number of complete and partial ibs-sized reads, + or obs-sized writes that took place during the copy. The format of the volume + stats is +. If records have been truncated (see + conv=block), the volume stats will contain the number of truncated records. + + Possible LEVEL values are: + - progress : Print periodic performance stats as the copy proceeds. + - noxfer : Print final volume stats, but not performance stats. + - none : Do not print any stats. + + Printing performance stats is also triggered by the INFO signal (where supported), + or the USR1 signal. Setting the POSIXLY_CORRECT environment variable to any value + (including an empty value) will cause the USR1 signal to be ignored. + + ### Conversion Options + + - ascii : convert from EBCDIC to ASCII. This is the inverse of the ebcdic + option. Implies conv=unblock. + - ebcdic : convert from ASCII to EBCDIC. This is the inverse of the ascii + option. Implies conv=block. + - ibm : convert from ASCII to EBCDIC, applying the conventions for [, ] + and ~ specified in POSIX. Implies conv=block. + + - ucase : convert from lower-case to upper-case. + - lcase : converts from upper-case to lower-case. + + - block : for each newline less than the size indicated by cbs=BYTES, remove + the newline and pad with spaces up to cbs. Lines longer than cbs are truncated. + - unblock : for each block of input of the size indicated by cbs=BYTES, remove + right-trailing spaces and replace with a newline character. + + - sparse : attempts to seek the output when an obs-sized block consists of + only zeros. + - swab : swaps each adjacent pair of bytes. If an odd number of bytes is + present, the final byte is omitted. + - sync : pad each ibs-sided block with zeros. If block or unblock is + specified, pad with spaces instead. + - excl : the output file must be created. Fail if the output file is already + present. + - nocreat : the output file will not be created. Fail if the output file in + not already present. + - notrunc : the output file will not be truncated. If this option is not + present, output will be truncated when opened. + - noerror : all read errors will be ignored. If this option is not present, + dd will only ignore Error::Interrupted. + - fdatasync : data will be written before finishing. + - fsync : data and metadata will be written before finishing. + + ### Input flags + + - count_bytes : a value to count=N will be interpreted as bytes. + - skip_bytes : a value to skip=N will be interpreted as bytes. + - fullblock : wait for ibs bytes from each read. zero-length reads are still + considered EOF. + + ### Output flags + + - append : open file in append mode. Consider setting conv=notrunc as well. + - seek_bytes : a value to seek=N will be interpreted as bytes. + + ### General Flags + + - direct : use direct I/O for data. + - directory : fail unless the given input (if used as an iflag) or + output (if used as an oflag) is a directory. + - dsync : use synchronized I/O for data. + - sync : use synchronized I/O for data and metadata. + - nonblock : use non-blocking I/O. + - noatime : do not update access time. + - nocache : request that OS drop cache. + - noctty : do not assign a controlling tty. + - nofollow : do not follow system links. diff --git a/src/uu/df/locales/en-US.ftl b/src/uu/df/locales/en-US.ftl new file mode 100644 index 00000000000..cbc69df76d8 --- /dev/null +++ b/src/uu/df/locales/en-US.ftl @@ -0,0 +1,10 @@ +df-about = Show information about the file system on which each FILE resides, + or all file systems by default. +df-usage = df [OPTION]... [FILE]... +df-after-help = Display values are in units of the first available SIZE from --block-size, + and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. + Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). + + SIZE is an integer and optional unit (example: 10M is 10*1024*1024). + Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers + of 1000). diff --git a/src/uu/dircolors/locales/en-US.ftl b/src/uu/dircolors/locales/en-US.ftl new file mode 100644 index 00000000000..65b0b874c3d --- /dev/null +++ b/src/uu/dircolors/locales/en-US.ftl @@ -0,0 +1,5 @@ +dircolors-about = Output commands to set the LS_COLORS environment variable. +dircolors-usage = dircolors [OPTION]... [FILE] +dircolors-after-help = If FILE is specified, read it to determine which colors to use for which + file types and extensions. Otherwise, a precompiled database is used. + For details on the format of these files, run 'dircolors --print-database' diff --git a/src/uu/dirname/locales/en-US.ftl b/src/uu/dirname/locales/en-US.ftl new file mode 100644 index 00000000000..37f33df4786 --- /dev/null +++ b/src/uu/dirname/locales/en-US.ftl @@ -0,0 +1,4 @@ +dirname-about = Strip last component from file name +dirname-usage = dirname [OPTION] NAME... +dirname-after-help = Output each NAME with its last non-slash component and trailing slashes + removed; if NAME contains no /'s, output '.' (meaning the current directory). diff --git a/src/uu/du/locales/en-US.ftl b/src/uu/du/locales/en-US.ftl new file mode 100644 index 00000000000..9f409ffa372 --- /dev/null +++ b/src/uu/du/locales/en-US.ftl @@ -0,0 +1,16 @@ +du-about = Estimate file space usage +du-usage = du [OPTION]... [FILE]... + du [OPTION]... --files0-from=F +du-after-help = Display values are in units of the first available SIZE from --block-size, + and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. + Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). + + SIZE is an integer and optional unit (example: 10M is 10*1024*1024). + Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers + of 1000). + + PATTERN allows some advanced exclusions. For example, the following syntaxes + are supported: + ? will match only one character + { "*" } will match zero or more characters + {"{"}a,b{"}"} will match a or b diff --git a/src/uu/echo/locales/en-US.ftl b/src/uu/echo/locales/en-US.ftl new file mode 100644 index 00000000000..05bf06cd26b --- /dev/null +++ b/src/uu/echo/locales/en-US.ftl @@ -0,0 +1,18 @@ +echo-about = Display a line of text +echo-usage = echo [OPTIONS]... [STRING]... +echo-after-help = Echo the STRING(s) to standard output. + + If -e is in effect, the following sequences are recognized: + + - \ backslash + - \a alert (BEL) + - \b backspace + - \c produce no further output + - \e escape + - \f form feed + - \n new line + - \r carriage return + - \t horizontal tab + - \v vertical tab + - \0NNN byte with octal value NNN (1 to 3 digits) + - \xHH byte with hexadecimal value HH (1 to 2 digits) diff --git a/src/uu/env/locales/en-US.ftl b/src/uu/env/locales/en-US.ftl new file mode 100644 index 00000000000..4204cb2e407 --- /dev/null +++ b/src/uu/env/locales/en-US.ftl @@ -0,0 +1,3 @@ +env-about = Set each NAME to VALUE in the environment and run COMMAND +env-usage = env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...] +env-after-help = A mere - implies -i. If no COMMAND, print the resulting environment. diff --git a/src/uu/expand/locales/en-US.ftl b/src/uu/expand/locales/en-US.ftl new file mode 100644 index 00000000000..ad15acf3953 --- /dev/null +++ b/src/uu/expand/locales/en-US.ftl @@ -0,0 +1,3 @@ +expand-about = Convert tabs in each FILE to spaces, writing to standard output. + With no FILE, or when FILE is -, read standard input. +expand-usage = expand [OPTION]... [FILE]... diff --git a/src/uu/expr/locales/en-US.ftl b/src/uu/expr/locales/en-US.ftl new file mode 100644 index 00000000000..f334d58ee1c --- /dev/null +++ b/src/uu/expr/locales/en-US.ftl @@ -0,0 +1,45 @@ +expr-about = Print the value of EXPRESSION to standard output +expr-usage = expr [EXPRESSION] + expr [OPTIONS] +expr-after-help = Print the value of EXPRESSION to standard output. A blank line below + separates increasing precedence groups. + + EXPRESSION may be: + + - ARG1 | ARG2: ARG1 if it is neither null nor 0, otherwise ARG2 + - ARG1 & ARG2: ARG1 if neither argument is null or 0, otherwise 0 + - ARG1 < ARG2: ARG1 is less than ARG2 + - ARG1 <= ARG2: ARG1 is less than or equal to ARG2 + - ARG1 = ARG2: ARG1 is equal to ARG2 + - ARG1 != ARG2: ARG1 is unequal to ARG2 + - ARG1 >= ARG2: ARG1 is greater than or equal to ARG2 + - ARG1 > ARG2: ARG1 is greater than ARG2 + - ARG1 + ARG2: arithmetic sum of ARG1 and ARG2 + - ARG1 - ARG2: arithmetic difference of ARG1 and ARG2 + - ARG1 * ARG2: arithmetic product of ARG1 and ARG2 + - ARG1 / ARG2: arithmetic quotient of ARG1 divided by ARG2 + - ARG1 % ARG2: arithmetic remainder of ARG1 divided by ARG2 + - STRING : REGEXP: anchored pattern match of REGEXP in STRING + - match STRING REGEXP: same as STRING : REGEXP + - substr STRING POS LENGTH: substring of STRING, POS counted from 1 + - index STRING CHARS: index in STRING where any CHARS is found, or 0 + - length STRING: length of STRING + - + TOKEN: interpret TOKEN as a string, even if it is a keyword like match + or an operator like / + - ( EXPRESSION ): value of EXPRESSION + + Beware that many operators need to be escaped or quoted for shells. + Comparisons are arithmetic if both ARGs are numbers, else lexicographical. + Pattern matches return the string matched between \( and \) or null; if + \( and \) are not used, they return the number of characters matched or 0. + + Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION + is null or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an + error occurred. + + Environment variables: + + - EXPR_DEBUG_TOKENS=1: dump expression's tokens + - EXPR_DEBUG_RPN=1: dump expression represented in reverse polish notation + - EXPR_DEBUG_SYA_STEP=1: dump each parser step + - EXPR_DEBUG_AST=1: dump expression represented abstract syntax tree diff --git a/src/uu/factor/locales/en-US.ftl b/src/uu/factor/locales/en-US.ftl new file mode 100644 index 00000000000..07e7920c5c6 --- /dev/null +++ b/src/uu/factor/locales/en-US.ftl @@ -0,0 +1,3 @@ +factor-about = Print the prime factors of the given NUMBER(s). + If none are specified, read from standard input. +factor-usage = factor [OPTION]... [NUMBER]... diff --git a/src/uu/false/locales/en-US.ftl b/src/uu/false/locales/en-US.ftl new file mode 100644 index 00000000000..8b642a69fa2 --- /dev/null +++ b/src/uu/false/locales/en-US.ftl @@ -0,0 +1,5 @@ +false-about = Returns false, an unsuccessful exit status. + + Immediately returns with the exit status 1. When invoked with one of the recognized options it + will try to write the help or version text. Any IO error during this operation is diagnosed, yet + the program will also return 1. diff --git a/src/uu/fmt/locales/en-US.ftl b/src/uu/fmt/locales/en-US.ftl new file mode 100644 index 00000000000..c572e7f5c2a --- /dev/null +++ b/src/uu/fmt/locales/en-US.ftl @@ -0,0 +1,2 @@ +fmt-about = Reformat paragraphs from input files (or stdin) to stdout. +fmt-usage = fmt [-WIDTH] [OPTION]... [FILE]... diff --git a/src/uu/fold/locales/en-US.ftl b/src/uu/fold/locales/en-US.ftl new file mode 100644 index 00000000000..5d5bb40a627 --- /dev/null +++ b/src/uu/fold/locales/en-US.ftl @@ -0,0 +1,3 @@ +fold-about = Writes each file (or standard input if no files are given) + to standard output whilst breaking long lines +fold-usage = fold [OPTION]... [FILE]... diff --git a/src/uu/groups/locales/en-US.ftl b/src/uu/groups/locales/en-US.ftl new file mode 100644 index 00000000000..ec6cc885556 --- /dev/null +++ b/src/uu/groups/locales/en-US.ftl @@ -0,0 +1,3 @@ +groups-about = Print group memberships for each USERNAME or, if no USERNAME is specified, for + the current process (which may differ if the groups data‐base has changed). +groups-usage = groups [OPTION]... [USERNAME]... diff --git a/src/uu/hashsum/locales/en-US.ftl b/src/uu/hashsum/locales/en-US.ftl new file mode 100644 index 00000000000..790171034bc --- /dev/null +++ b/src/uu/hashsum/locales/en-US.ftl @@ -0,0 +1,2 @@ +hashsum-about = Compute and check message digests. +hashsum-usage = hashsum -- [OPTIONS]... [FILE]... diff --git a/src/uu/head/locales/en-US.ftl b/src/uu/head/locales/en-US.ftl new file mode 100644 index 00000000000..4b702b6bc2a --- /dev/null +++ b/src/uu/head/locales/en-US.ftl @@ -0,0 +1,6 @@ +head-about = Print the first 10 lines of each FILE to standard output. + With more than one FILE, precede each with a header giving the file name. + With no FILE, or when FILE is -, read standard input. + + Mandatory arguments to long flags are mandatory for short flags too. +head-usage = head [FLAG]... [FILE]... diff --git a/src/uu/hostid/locales/en-US.ftl b/src/uu/hostid/locales/en-US.ftl new file mode 100644 index 00000000000..25abe94f734 --- /dev/null +++ b/src/uu/hostid/locales/en-US.ftl @@ -0,0 +1,2 @@ +hostid-about = Print the numeric identifier (in hexadecimal) for the current host +hostid-usage = hostid [options] diff --git a/src/uu/hostname/locales/en-US.ftl b/src/uu/hostname/locales/en-US.ftl new file mode 100644 index 00000000000..8ad9ab52e1f --- /dev/null +++ b/src/uu/hostname/locales/en-US.ftl @@ -0,0 +1,2 @@ +hostname-about = Display or set the system's host name. +hostname-usage = hostname [OPTION]... [HOSTNAME] diff --git a/src/uu/id/locales/en-US.ftl b/src/uu/id/locales/en-US.ftl new file mode 100644 index 00000000000..fd7b8348b1f --- /dev/null +++ b/src/uu/id/locales/en-US.ftl @@ -0,0 +1,10 @@ +id-about = Print user and group information for each specified USER, + or (when USER omitted) for the current user. +id-usage = id [OPTION]... [USER]... +id-after-help = The id utility displays the user and group names and numeric IDs, of the + calling process, to the standard output. If the real and effective IDs are + different, both are displayed, otherwise only the real ID is displayed. + + If a user (login name or user ID) is specified, the user and group IDs of + that user are displayed. In this case, the real and effective IDs are + assumed to be the same. diff --git a/src/uu/install/locales/en-US.ftl b/src/uu/install/locales/en-US.ftl new file mode 100644 index 00000000000..b5781abfbe5 --- /dev/null +++ b/src/uu/install/locales/en-US.ftl @@ -0,0 +1,3 @@ +install-about = Copy SOURCE to DEST or multiple SOURCE(s) to the existing + DIRECTORY, while setting permission modes and owner/group +install-usage = install [OPTION]... [FILE]... diff --git a/src/uu/join/locales/en-US.ftl b/src/uu/join/locales/en-US.ftl new file mode 100644 index 00000000000..e5d0053846d --- /dev/null +++ b/src/uu/join/locales/en-US.ftl @@ -0,0 +1,5 @@ +join-about = For each pair of input lines with identical join fields, write a line to + standard output. The default join field is the first, delimited by blanks. + + When FILE1 or FILE2 (not both) is -, read standard input. +join-usage = join [OPTION]... FILE1 FILE2 diff --git a/src/uu/kill/locales/en-US.ftl b/src/uu/kill/locales/en-US.ftl new file mode 100644 index 00000000000..3038adaed0c --- /dev/null +++ b/src/uu/kill/locales/en-US.ftl @@ -0,0 +1,2 @@ +kill-about = Send signal to processes or list information about signals. +kill-usage = kill [OPTIONS]... PID... diff --git a/src/uu/link/locales/en-US.ftl b/src/uu/link/locales/en-US.ftl new file mode 100644 index 00000000000..7a18c090015 --- /dev/null +++ b/src/uu/link/locales/en-US.ftl @@ -0,0 +1,2 @@ +link-about = Call the link function to create a link named FILE2 to an existing FILE1. +link-usage = link FILE1 FILE2 diff --git a/src/uu/ln/locales/en-US.ftl b/src/uu/ln/locales/en-US.ftl new file mode 100644 index 00000000000..7de0c171eb2 --- /dev/null +++ b/src/uu/ln/locales/en-US.ftl @@ -0,0 +1,13 @@ +ln-about = Make links between files. +ln-usage = ln [OPTION]... [-T] TARGET LINK_NAME + ln [OPTION]... TARGET + ln [OPTION]... TARGET... DIRECTORY + ln [OPTION]... -t DIRECTORY TARGET... +ln-after-help = In the 1st form, create a link to TARGET with the name LINK_NAME. + In the 2nd form, create a link to TARGET in the current directory. + In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. + Create hard links by default, symbolic links with --symbolic. + By default, each destination (name of new link) should not already exist. + When creating hard links, each TARGET must exist. Symbolic links + can hold arbitrary text; if later resolved, a relative link is + interpreted in relation to its parent directory. diff --git a/src/uu/logname/locales/en-US.ftl b/src/uu/logname/locales/en-US.ftl new file mode 100644 index 00000000000..038b757bc29 --- /dev/null +++ b/src/uu/logname/locales/en-US.ftl @@ -0,0 +1 @@ +logname-about = Print user's login name diff --git a/src/uu/ls/locales/en-US.ftl b/src/uu/ls/locales/en-US.ftl new file mode 100644 index 00000000000..b21aefa2257 --- /dev/null +++ b/src/uu/ls/locales/en-US.ftl @@ -0,0 +1,4 @@ +ls-about = List directory contents. + Ignore files and directories starting with a '.' by default +ls-usage = ls [OPTION]... [FILE]... +ls-after-help = The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. Also the TIME_STYLE environment variable sets the default style to use. diff --git a/src/uu/mkdir/locales/en-US.ftl b/src/uu/mkdir/locales/en-US.ftl new file mode 100644 index 00000000000..04232924409 --- /dev/null +++ b/src/uu/mkdir/locales/en-US.ftl @@ -0,0 +1,3 @@ +mkdir-about = Create the given DIRECTORY(ies) if they do not exist +mkdir-usage = mkdir [OPTION]... DIRECTORY... +mkdir-after-help = Each MODE is of the form [ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+. diff --git a/src/uu/mkfifo/locales/en-US.ftl b/src/uu/mkfifo/locales/en-US.ftl new file mode 100644 index 00000000000..aee4bba9288 --- /dev/null +++ b/src/uu/mkfifo/locales/en-US.ftl @@ -0,0 +1,2 @@ +mkfifo-about = Create a FIFO with the given name. +mkfifo-usage = mkfifo [OPTION]... NAME... diff --git a/src/uu/mknod/locales/en-US.ftl b/src/uu/mknod/locales/en-US.ftl new file mode 100644 index 00000000000..c0e391381b9 --- /dev/null +++ b/src/uu/mknod/locales/en-US.ftl @@ -0,0 +1,17 @@ +mknod-about = Create the special file NAME of the given TYPE. +mknod-usage = mknod [OPTION]... NAME TYPE [MAJOR MINOR] +mknod-after-help = Mandatory arguments to long options are mandatory for short options too. + -m, --mode=MODE set file permission bits to MODE, not a=rw - umask + + Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they + must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, + it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; + otherwise, as decimal. TYPE may be: + + - b create a block (buffered) special file + - c, u create a character (unbuffered) special file + - p create a FIFO + + NOTE: your shell may have its own version of mknod, which usually supersedes + the version described here. Please refer to your shell's documentation + for details about the options it supports. diff --git a/src/uu/mktemp/locales/en-US.ftl b/src/uu/mktemp/locales/en-US.ftl new file mode 100644 index 00000000000..0e290ad970c --- /dev/null +++ b/src/uu/mktemp/locales/en-US.ftl @@ -0,0 +1,2 @@ +mktemp-about = Create a temporary file or directory. +mktemp-usage = mktemp [OPTION]... [TEMPLATE] diff --git a/src/uu/more/locales/en-US.ftl b/src/uu/more/locales/en-US.ftl new file mode 100644 index 00000000000..9d2d24c869b --- /dev/null +++ b/src/uu/more/locales/en-US.ftl @@ -0,0 +1,2 @@ +more-about = Display the contents of a text file +more-usage = more [OPTIONS] FILE... diff --git a/src/uu/mv/locales/en-US.ftl b/src/uu/mv/locales/en-US.ftl new file mode 100644 index 00000000000..7e8b2bd28e8 --- /dev/null +++ b/src/uu/mv/locales/en-US.ftl @@ -0,0 +1,16 @@ +mv-about = Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY. +mv-usage = mv [OPTION]... [-T] SOURCE DEST + mv [OPTION]... SOURCE... DIRECTORY + mv [OPTION]... -t DIRECTORY SOURCE... +mv-after-help = When specifying more than one of -i, -f, -n, only the final one will take effect. + + Do not move a non-directory that has an existing destination with the same or newer modification timestamp; + instead, silently skip the file without failing. If the move is across file system boundaries, the comparison is + to the source timestamp truncated to the resolutions of the destination file system and of the system calls used + to update timestamps; this avoids duplicate work if several mv -u commands are executed with the same source + and destination. This option is ignored if the -n or --no-clobber option is also specified. which gives more control + over which existing files in the destination are replaced, and its value can be one of the following: + + - all This is the default operation when an --update option is not specified, and results in all existing files in the destination being replaced. + - none This is similar to the --no-clobber option, in that no files in the destination are replaced, but also skipping a file does not induce a failure. + - older This is the default operation when --update is specified, and results in files being replaced if they’re older than the corresponding source file. diff --git a/src/uu/nice/locales/en-US.ftl b/src/uu/nice/locales/en-US.ftl new file mode 100644 index 00000000000..d243ef94e47 --- /dev/null +++ b/src/uu/nice/locales/en-US.ftl @@ -0,0 +1,5 @@ +nice-about = Run COMMAND with an adjusted niceness, which affects process scheduling. + With no COMMAND, print the current niceness. Niceness values range from at + least -20 (most favorable to the process) to 19 (least favorable to the + process). +nice-usage = nice [OPTIONS] [COMMAND [ARGS]] diff --git a/src/uu/nl/locales/en-US.ftl b/src/uu/nl/locales/en-US.ftl new file mode 100644 index 00000000000..7880de562db --- /dev/null +++ b/src/uu/nl/locales/en-US.ftl @@ -0,0 +1,15 @@ +nl-about = Number lines of files +nl-usage = nl [OPTION]... [FILE]... +nl-after-help = STYLE is one of: + + - a number all lines + - t number only nonempty lines + - n number no lines + - pBRE number only lines that contain a match for the basic regular + expression, BRE + + FORMAT is one of: + + - ln left justified, no leading zeros + - rn right justified, no leading zeros + - rz right justified, leading zeros diff --git a/src/uu/nohup/locales/en-US.ftl b/src/uu/nohup/locales/en-US.ftl new file mode 100644 index 00000000000..f209a7a90b1 --- /dev/null +++ b/src/uu/nohup/locales/en-US.ftl @@ -0,0 +1,7 @@ +nohup-about = Run COMMAND ignoring hangup signals. +nohup-usage = nohup COMMAND [ARG]... + nohup OPTION +nohup-after-help = If standard input is terminal, it'll be replaced with /dev/null. + If standard output is terminal, it'll be appended to nohup.out instead, + or $HOME/nohup.out, if nohup.out open failed. + If standard error is terminal, it'll be redirected to stdout. diff --git a/src/uu/nproc/locales/en-US.ftl b/src/uu/nproc/locales/en-US.ftl new file mode 100644 index 00000000000..42522997e61 --- /dev/null +++ b/src/uu/nproc/locales/en-US.ftl @@ -0,0 +1,4 @@ +nproc-about = Print the number of cores available to the current process. + If the OMP_NUM_THREADS or OMP_THREAD_LIMIT environment variables are set, then + they will determine the minimum and maximum returned value respectively. +nproc-usage = nproc [OPTIONS]... diff --git a/src/uu/numfmt/locales/en-US.ftl b/src/uu/numfmt/locales/en-US.ftl new file mode 100644 index 00000000000..588a835a45d --- /dev/null +++ b/src/uu/numfmt/locales/en-US.ftl @@ -0,0 +1,36 @@ +numfmt-about = Convert numbers from/to human-readable strings +numfmt-usage = numfmt [OPTION]... [NUMBER]... +numfmt-after-help = UNIT options: + + - none: no auto-scaling is done; suffixes will trigger an error + - auto: accept optional single/two letter suffix: + + 1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576, + + - si: accept optional single letter suffix: + + 1K = 1000, 1M = 1000000, ... + + - iec: accept optional single letter suffix: + + 1K = 1024, 1M = 1048576, ... + + - iec-i: accept optional two-letter suffix: + + 1Ki = 1024, 1Mi = 1048576, ... + + - FIELDS supports cut(1) style field ranges: + + N N'th field, counted from 1 + N- from N'th field, to end of line + N-M from N'th to M'th field (inclusive) + -M from first to M'th field (inclusive) + - all fields + + Multiple fields/ranges can be separated with commas + + FORMAT must be suitable for printing one floating-point argument %f. + Optional quote (%'f) will enable --grouping (if supported by current locale). + Optional width value (%10f) will pad output. Optional zero (%010f) width + will zero pad the number. Optional negative values (%-10f) will left align. + Optional precision (%.1f) will override the input determined precision. diff --git a/src/uu/od/locales/en-US.ftl b/src/uu/od/locales/en-US.ftl new file mode 100644 index 00000000000..d451b30915e --- /dev/null +++ b/src/uu/od/locales/en-US.ftl @@ -0,0 +1,41 @@ +od-about = Dump files in octal and other formats +od-usage = od [OPTION]... [--] [FILENAME]... + od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] + od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]] +od-after-help = Displays data in various human-readable formats. If multiple formats are + specified, the output will contain all formats in the order they appear on the + command line. Each format will be printed on a new line. Only the line + containing the first format will be prefixed with the offset. + + If no filename is specified, or it is "-", stdin will be used. After a "--", no + more options will be recognized. This allows for filenames starting with a "-". + + If a filename is a valid number which can be used as an offset in the second + form, you can force it to be recognized as a filename if you include an option + like "-j0", which is only valid in the first form. + + RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none. + + BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if + prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the + number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2. + + OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or + decimal if a "." suffix is added. The "b" suffix will multiply with 512. + + TYPE contains one or more format specifications consisting of: + a for printable 7-bits ASCII + c for utf-8 characters or octal for undefined characters + d[SIZE] for signed decimal + f[SIZE] for floating point + o[SIZE] for octal + u[SIZE] for unsigned decimal + x[SIZE] for hexadecimal + SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16, + or C, I, S, L for 1, 2, 4, 8 bytes for integer types, + or F, D, L for 4, 8, 16 bytes for floating point. + Any type specification can have a "z" suffix, which will add a ASCII dump at + the end of the line. + + If an error occurred, a diagnostic message will be printed to stderr, and the + exit code will be non-zero. diff --git a/src/uu/paste/locales/en-US.ftl b/src/uu/paste/locales/en-US.ftl new file mode 100644 index 00000000000..15c39b0a62f --- /dev/null +++ b/src/uu/paste/locales/en-US.ftl @@ -0,0 +1,3 @@ +paste-about = Write lines consisting of the sequentially corresponding lines from each + FILE, separated by TABs, to standard output. +paste-usage = paste [OPTIONS] [FILE]... diff --git a/src/uu/pathchk/locales/en-US.ftl b/src/uu/pathchk/locales/en-US.ftl new file mode 100644 index 00000000000..c75b172622d --- /dev/null +++ b/src/uu/pathchk/locales/en-US.ftl @@ -0,0 +1,2 @@ +pathchk-about = Check whether file names are valid or portable +pathchk-usage = pathchk [OPTION]... NAME... diff --git a/src/uu/pinky/locales/en-US.ftl b/src/uu/pinky/locales/en-US.ftl new file mode 100644 index 00000000000..daa79f0b473 --- /dev/null +++ b/src/uu/pinky/locales/en-US.ftl @@ -0,0 +1,2 @@ +pinky-about = Displays brief user information on Unix-based systems +pinky-usage = pinky [OPTION]... [USER]... diff --git a/src/uu/pr/locales/en-US.ftl b/src/uu/pr/locales/en-US.ftl new file mode 100644 index 00000000000..cd11a164dbd --- /dev/null +++ b/src/uu/pr/locales/en-US.ftl @@ -0,0 +1,19 @@ +pr-about = Write content of given file or standard input to standard output with pagination filter +pr-usage = pr [OPTION]... [FILE]... +pr-after-help = +PAGE Begin output at page number page of the formatted input. + -COLUMN Produce multi-column output. See --column + + The pr utility is a printing and pagination filter for text files. + When multiple input files are specified, each is read, formatted, and written to standard output. + By default, the input is separated into 66-line pages, each with + + - A 5-line header with the page number, date, time, and the pathname of the file. + - A 5-line trailer consisting of blank lines. + + If standard output is associated with a terminal, diagnostic messages are suppressed until the pr + utility has completed processing. + + When multiple column output is specified, text columns are of equal width. + By default, text columns are separated by at least one . + Input lines that do not fit into a text column are truncated. + Lines are not truncated under single column output. diff --git a/src/uu/printenv/locales/en-US.ftl b/src/uu/printenv/locales/en-US.ftl new file mode 100644 index 00000000000..b60962e1a6e --- /dev/null +++ b/src/uu/printenv/locales/en-US.ftl @@ -0,0 +1,2 @@ +printenv-about = Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all. +printenv-usage = printenv [OPTION]... [VARIABLE]... diff --git a/src/uu/printf/locales/en-US.ftl b/src/uu/printf/locales/en-US.ftl new file mode 100644 index 00000000000..6f58a07758a --- /dev/null +++ b/src/uu/printf/locales/en-US.ftl @@ -0,0 +1,249 @@ +printf-about = Print output based off of the format string and proceeding arguments. +printf-usage = printf FORMAT [ARGUMENT]... + printf OPTION +printf-after-help = basic anonymous string templating: + + prints format string at least once, repeating as long as there are remaining arguments + output prints escaped literals in the format string as character literals + output replaces anonymous fields with the next unused argument, formatted according to the field. + + Prints the , replacing escaped character sequences with character literals + and substitution field sequences with passed arguments + + literally, with the exception of the below + escaped character sequences, and the substitution sequences described further down. + + ### ESCAPE SEQUENCES + + The following escape sequences, organized here in alphabetical order, + will print the corresponding character literal: + + - \" double quote + + - \\ backslash + + - \\a alert (BEL) + + - \\b backspace + + - \\c End-of-Input + + - \\e escape + + - \\f form feed + + - \\n new line + + - \\r carriage return + + - \\t horizontal tab + + - \\v vertical tab + + - \\NNN byte with value expressed in octal value NNN (1 to 3 digits) + values greater than 256 will be treated + + - \\xHH byte with value expressed in hexadecimal value NN (1 to 2 digits) + + - \\uHHHH Unicode (IEC 10646) character with value expressed in hexadecimal value HHHH (4 digits) + + - \\uHHHH Unicode character with value expressed in hexadecimal value HHHH (8 digits) + + - %% a single % + + ### SUBSTITUTIONS + + #### SUBSTITUTION QUICK REFERENCE + + Fields + + - %s: string + - %b: string parsed for literals second parameter is max length + + - %c: char no second parameter + + - %i or %d: 64-bit integer + - %u: 64 bit unsigned integer + - %x or %X: 64-bit unsigned integer as hex + - %o: 64-bit unsigned integer as octal + second parameter is min-width, integer + output below that width is padded with leading zeroes + + - %q: ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable + characters with the proposed POSIX $'' syntax. + + - %f or %F: decimal floating point value + - %e or %E: scientific notation floating point value + - %g or %G: shorter of specially interpreted decimal or SciNote floating point value. + second parameter is + -max places after decimal point for floating point output + -max number of significant digits for scientific notation output + + parameterizing fields + + examples: + + printf '%4.3i' 7 + + It has a first parameter of 4 and a second parameter of 3 and will result in ' 007' + + printf '%.1s' abcde + + It has no first parameter and a second parameter of 1 and will result in 'a' + + printf '%4c' q + + It has a first parameter of 4 and no second parameter and will result in ' q' + + The first parameter of a field is the minimum width to pad the output to + if the output is less than this absolute value of this width, + it will be padded with leading spaces, or, if the argument is negative, + with trailing spaces. the default is zero. + + The second parameter of a field is particular to the output field type. + defaults can be found in the full substitution help below + + special prefixes to numeric arguments + + - 0: (e.g. 010) interpret argument as octal (integer output fields only) + - 0x: (e.g. 0xABC) interpret argument as hex (numeric output fields only) + - \': (e.g. \'a) interpret argument as a character constant + + #### HOW TO USE SUBSTITUTIONS + + Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a + particular way. E.g. + + printf 'the letter %X comes before the letter %X' 10 11 + + will print + + the letter A comes before the letter B + + because the substitution field %X means + 'take an integer argument and write it as a hexadecimal number' + + Passing more arguments than are in the format string will cause the format string to be + repeated for the remaining substitutions + + printf 'it is %i F in %s \n' 22 Portland 25 Boston 27 New York + + will print + + it is 22 F in Portland + it is 25 F in Boston + it is 27 F in Boston + + If a format string is printed but there are less arguments remaining + than there are substitution fields, substitution fields without + an argument will default to empty strings, or for numeric fields + the value 0 + + #### AVAILABLE SUBSTITUTIONS + + This program, like GNU coreutils printf, + interprets a modified subset of the POSIX C printf spec, + a quick reference to substitutions is below. + + #### STRING SUBSTITUTIONS + + All string fields have a 'max width' parameter + %.3s means 'print no more than three characters of the original input' + + - %s: string + + - %b: escaped string - the string will be checked for any escaped literals from + the escaped literal list above, and translate them to literal characters. + e.g. \\n will be transformed into a newline character. + One special rule about %b mode is that octal literals are interpreted differently + In arguments passed by %b, pass octal-interpreted literals must be in the form of \\0NNN + instead of \\NNN. (Although, for legacy reasons, octal literals in the form of \\NNN will + still be interpreted and not throw a warning, you will have problems if you use this for a + literal whose code begins with zero, as it will be viewed as in \\0NNN form.) + + - %q: escaped string - the string in a format that can be reused as input by most shells. + Non-printable characters are escaped with the POSIX proposed ‘$''’ syntax, + and shell meta-characters are quoted appropriately. + This is an equivalent format to ls --quoting=shell-escape output. + + #### CHAR SUBSTITUTIONS + + The character field does not have a secondary parameter. + + - %c: a single character + + #### INTEGER SUBSTITUTIONS + + All integer fields have a 'pad with zero' parameter + %.4i means an integer which if it is less than 4 digits in length, + is padded with leading zeros until it is 4 digits in length. + + - %d or %i: 64-bit integer + + - %u: 64-bit unsigned integer + + - %x or %X: 64-bit unsigned integer printed in Hexadecimal (base 16) + %X instead of %x means to use uppercase letters for 'a' through 'f' + + - %o: 64-bit unsigned integer printed in octal (base 8) + + #### FLOATING POINT SUBSTITUTIONS + + All floating point fields have a 'max decimal places / max significant digits' parameter + %.10f means a decimal floating point with 7 decimal places past 0 + %.10e means a scientific notation number with 10 significant digits + %.10g means the same behavior for decimal and Sci. Note, respectively, and provides the shortest + of each's output. + + Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a + double first before being rendered to text. For both implementations do not expect meaningful + precision past the 18th decimal place. When using a number of decimal places that is 18 or + higher, you can expect variation in output between GNU coreutils printf and this printf at the + 18th decimal place of +/- 1 + + - %f: floating point value presented in decimal, truncated and displayed to 6 decimal places by + default. There is not past-double behavior parity with Coreutils printf, values are not + estimated or adjusted beyond input values. + + - %e or %E: floating point value presented in scientific notation + 7 significant digits by default + %E means use to use uppercase E for the mantissa. + + - %g or %G: floating point value presented in the shortest of decimal and scientific notation + behaves differently from %f and %E, please see posix printf spec for full details, + some examples of different behavior: + Sci Note has 6 significant digits by default + Trailing zeroes are removed + Instead of being truncated, digit after last is rounded + + Like other behavior in this utility, the design choices of floating point + behavior in this utility is selected to reproduce in exact + the behavior of GNU coreutils' printf from an inputs and outputs standpoint. + + ### USING PARAMETERS + + Most substitution fields can be parameterized using up to 2 numbers that can + be passed to the field, between the % sign and the field letter. + + The 1st parameter always indicates the minimum width of output, it is useful for creating + columnar output. Any output that would be less than this minimum width is padded with + leading spaces + The 2nd parameter is proceeded by a dot. + You do not have to use parameters + + ### SPECIAL FORMS OF INPUT + + For numeric input, the following additional forms of input are accepted besides decimal: + + Octal (only with integer): if the argument begins with a 0 the proceeding characters + will be interpreted as octal (base 8) for integer fields + + Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted + will be interpreted as hex (base 16) for any numeric fields + for float fields, hexadecimal input results in a precision + limit (in converting input past the decimal point) of 10^-15 + + Character Constant: if the argument begins with a single quote character, the first byte + of the next character will be interpreted as an 8-bit unsigned integer. If there are + additional bytes, they will throw an error (unless the environment variable POSIXLY_CORRECT + is set) diff --git a/src/uu/ptx/locales/en-US.ftl b/src/uu/ptx/locales/en-US.ftl new file mode 100644 index 00000000000..87bc95530d1 --- /dev/null +++ b/src/uu/ptx/locales/en-US.ftl @@ -0,0 +1,6 @@ +ptx-about = Produce a permuted index of file contents + Output a permuted index, including context, of the words in the input files. + Mandatory arguments to long options are mandatory for short options too. + With no FILE, or when FILE is -, read standard input. Default is '-F /'. +ptx-usage = ptx [OPTION]... [INPUT]... + ptx -G [OPTION]... [INPUT [OUTPUT]] diff --git a/src/uu/pwd/locales/en-US.ftl b/src/uu/pwd/locales/en-US.ftl new file mode 100644 index 00000000000..52a4a2283c0 --- /dev/null +++ b/src/uu/pwd/locales/en-US.ftl @@ -0,0 +1,2 @@ +pwd-about = Display the full filename of the current working directory. +pwd-usage = pwd [OPTION]... [FILE]... diff --git a/src/uu/readlink/locales/en-US.ftl b/src/uu/readlink/locales/en-US.ftl new file mode 100644 index 00000000000..ddb80665173 --- /dev/null +++ b/src/uu/readlink/locales/en-US.ftl @@ -0,0 +1,2 @@ +readlink-about = Print value of a symbolic link or canonical file name. +readlink-usage = readlink [OPTION]... [FILE]... diff --git a/src/uu/realpath/locales/en-US.ftl b/src/uu/realpath/locales/en-US.ftl new file mode 100644 index 00000000000..90910bf5301 --- /dev/null +++ b/src/uu/realpath/locales/en-US.ftl @@ -0,0 +1,2 @@ +realpath-about = Print the resolved path +realpath-usage = realpath [OPTION]... FILE... diff --git a/src/uu/rm/locales/en-US.ftl b/src/uu/rm/locales/en-US.ftl new file mode 100644 index 00000000000..31f0d008329 --- /dev/null +++ b/src/uu/rm/locales/en-US.ftl @@ -0,0 +1,14 @@ +rm-about = Remove (unlink) the FILE(s) +rm-usage = rm [OPTION]... FILE... +rm-after-help = By default, rm does not remove directories. Use the --recursive (-r or -R) + option to remove each listed directory, too, along with all of its contents + + To remove a file whose name starts with a '-', for example '-foo', + use one of these commands: + rm -- -foo + + rm ./-foo + + Note that if you use rm to remove a file, it might be possible to recover + some of its contents, given sufficient expertise and/or time. For greater + assurance that the contents are truly unrecoverable, consider using shred. diff --git a/src/uu/rmdir/locales/en-US.ftl b/src/uu/rmdir/locales/en-US.ftl new file mode 100644 index 00000000000..e2c1ca0ec19 --- /dev/null +++ b/src/uu/rmdir/locales/en-US.ftl @@ -0,0 +1,2 @@ +rmdir-about = Remove the DIRECTORY(ies), if they are empty. +rmdir-usage = rmdir [OPTION]... DIRECTORY... diff --git a/src/uu/runcon/locales/en-US.ftl b/src/uu/runcon/locales/en-US.ftl new file mode 100644 index 00000000000..37d977a5b90 --- /dev/null +++ b/src/uu/runcon/locales/en-US.ftl @@ -0,0 +1,10 @@ +runcon-about = Run command with specified security context under SELinux enabled systems. +runcon-usage = runcon CONTEXT COMMAND [ARG...] + runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [ARG...] +runcon-after-help = Run COMMAND with completely-specified CONTEXT, or with current or transitioned security context modified by one or more of LEVEL, ROLE, TYPE, and USER. + + If none of --compute, --type, --user, --role or --range is specified, then the first argument is used as the complete context. + + Note that only carefully-chosen contexts are likely to successfully run. + + If neither CONTEXT nor COMMAND is specified, the current security context is printed. diff --git a/src/uu/seq/locales/en-US.ftl b/src/uu/seq/locales/en-US.ftl new file mode 100644 index 00000000000..818ebc86c1e --- /dev/null +++ b/src/uu/seq/locales/en-US.ftl @@ -0,0 +1,4 @@ +seq-about = Display numbers from FIRST to LAST, in steps of INCREMENT. +seq-usage = seq [OPTION]... LAST + seq [OPTION]... FIRST LAST + seq [OPTION]... FIRST INCREMENT LAST diff --git a/src/uu/shred/locales/en-US.ftl b/src/uu/shred/locales/en-US.ftl new file mode 100644 index 00000000000..90245a08f0b --- /dev/null +++ b/src/uu/shred/locales/en-US.ftl @@ -0,0 +1,37 @@ +shred-about = Overwrite the specified FILE(s) repeatedly, in order to make it harder for even + very expensive hardware probing to recover the data. +shred-usage = shred [OPTION]... FILE... +shred-after-help = Delete FILE(s) if --remove (-u) is specified. The default is not to remove + the files because it is common to operate on device files like /dev/hda, and + those files usually should not be removed. + + CAUTION: Note that shred relies on a very important assumption: that the file + system overwrites data in place. This is the traditional way to do things, but + many modern file system designs do not satisfy this assumption. The following + are examples of file systems on which shred is not effective, or is not + guaranteed to be effective in all file system modes: + + - log-structured or journal file systems, such as those supplied with + AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.) + + - file systems that write redundant data and carry on even if some writes + fail, such as RAID-based file systems + + - file systems that make snapshots, such as Network Appliance's NFS server + + - file systems that cache in temporary locations, such as NFS + version 3 clients + + - compressed file systems + + In the case of ext3 file systems, the above disclaimer applies (and shred is + thus of limited effectiveness) only in data=journal mode, which journals file + data in addition to just metadata. In both the data=ordered (default) and + data=writeback modes, shred works as usual. Ext3 journal modes can be changed + by adding the data=something option to the mount options for a particular + file system in the /etc/fstab file, as documented in the mount man page (`man + mount`). + + In addition, file system backups and remote mirrors may contain copies of + the file that cannot be removed, and that will allow a shredded file to be + recovered later. diff --git a/src/uu/shuf/locales/en-US.ftl b/src/uu/shuf/locales/en-US.ftl new file mode 100644 index 00000000000..5348f1f7e1b --- /dev/null +++ b/src/uu/shuf/locales/en-US.ftl @@ -0,0 +1,6 @@ +shuf-about = Shuffle the input by outputting a random permutation of input lines. + Each output permutation is equally likely. + With no FILE, or when FILE is -, read standard input. +shuf-usage = shuf [OPTION]... [FILE] + shuf -e [OPTION]... [ARG]... + shuf -i LO-HI [OPTION]... diff --git a/src/uu/sleep/locales/en-US.ftl b/src/uu/sleep/locales/en-US.ftl new file mode 100644 index 00000000000..c9e7925babe --- /dev/null +++ b/src/uu/sleep/locales/en-US.ftl @@ -0,0 +1,8 @@ +sleep-about = Pause for NUMBER seconds. +sleep-usage = sleep NUMBER[SUFFIX]... + sleep OPTION +sleep-after-help = Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), + 'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations + that require NUMBER be an integer, here NUMBER may be an arbitrary floating + point number. Given two or more arguments, pause for the amount of time + specified by the sum of their values. diff --git a/src/uu/sort/locales/en-US.ftl b/src/uu/sort/locales/en-US.ftl new file mode 100644 index 00000000000..7b53e9df023 --- /dev/null +++ b/src/uu/sort/locales/en-US.ftl @@ -0,0 +1,11 @@ +sort-about = Display sorted concatenation of all FILE(s). With no FILE, or when FILE is -, read standard input. +sort-usage = sort [OPTION]... [FILE]... +sort-after-help = The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. + + Fields by default are separated by the first whitespace after a non-whitespace character. Use -t to specify a custom separator. + In the default case, whitespace is appended at the beginning of each field. Custom separators however are not included in fields. + + FIELD and CHAR both start at 1 (i.e. they are 1-indexed). If there is no end specified after a comma, the end will be the end of the line. + If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the start position and to 0 for the end position. + + Valid options are: MbdfhnRrV. They override the global options for this key. diff --git a/src/uu/split/locales/en-US.ftl b/src/uu/split/locales/en-US.ftl new file mode 100644 index 00000000000..0b870d63386 --- /dev/null +++ b/src/uu/split/locales/en-US.ftl @@ -0,0 +1,16 @@ +split-about = Create output files containing consecutive or interleaved sections of input +split-usage = split [OPTION]... [INPUT [PREFIX]] +split-after-help = Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is -, read standard input. + + The SIZE argument is an integer and optional unit (example: 10K is 10*1024). + Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). + Binary prefixes can be used, too: KiB=K, MiB=M, and so on. + + CHUNKS may be: + + - N split into N files based on size of input + - K/N output Kth of N to stdout + - l/N split into N files without splitting lines/records + - l/K/N output Kth of N to stdout without splitting lines/records + - r/N like 'l' but use round robin distribution + - r/K/N likewise but only output Kth of N to stdout diff --git a/src/uu/stat/locales/en-US.ftl b/src/uu/stat/locales/en-US.ftl new file mode 100644 index 00000000000..440e80e3474 --- /dev/null +++ b/src/uu/stat/locales/en-US.ftl @@ -0,0 +1,54 @@ +stat-about = Display file or file system status. +stat-usage = stat [OPTION]... FILE... +stat-after-help = Valid format sequences for files (without `--file-system`): + + -`%a`: access rights in octal (note '#' and '0' printf flags) + -`%A`: access rights in human readable form + -`%b`: number of blocks allocated (see %B) + -`%B`: the size in bytes of each block reported by %b + -`%C`: SELinux security context string + -`%d`: device number in decimal + -`%D`: device number in hex + -`%f`: raw mode in hex + -`%F`: file type + -`%g`: group ID of owner + -`%G`: group name of owner + -`%h`: number of hard links + -`%i`: inode number + -`%m`: mount point + -`%n`: file name + -`%N`: quoted file name with dereference (follow) if symbolic link + -`%o`: optimal I/O transfer size hint + -`%s`: total size, in bytes + -`%t`: major device type in hex, for character/block device special files + -`%T`: minor device type in hex, for character/block device special files + -`%u`: user ID of owner + -`%U`: user name of owner + -`%w`: time of file birth, human-readable; - if unknown + -`%W`: time of file birth, seconds since Epoch; 0 if unknown + -`%x`: time of last access, human-readable + -`%X`: time of last access, seconds since Epoch + -`%y`: time of last data modification, human-readable + + -`%Y`: time of last data modification, seconds since Epoch + -`%z`: time of last status change, human-readable + -`%Z`: time of last status change, seconds since Epoch + + Valid format sequences for file systems: + + -`%a`: free blocks available to non-superuser + -`%b`: total data blocks in file system + -`%c`: total file nodes in file system + -`%d`: free file nodes in file system + -`%f`: free blocks in file system + -`%i`: file system ID in hex + -`%l`: maximum length of filenames + -`%n`: file name + -`%s`: block size (for faster transfers) + -`%S`: fundamental block size (for block counts) + -`%t`: file system type in hex + -`%T`: file system type in human readable form + + NOTE: your shell may have its own version of stat, which usually supersedes + the version described here. Please refer to your shell's documentation + for details about the options it supports. diff --git a/src/uu/stdbuf/locales/en-US.ftl b/src/uu/stdbuf/locales/en-US.ftl new file mode 100644 index 00000000000..fb968f98548 --- /dev/null +++ b/src/uu/stdbuf/locales/en-US.ftl @@ -0,0 +1,16 @@ +stdbuf-about = Run COMMAND, with modified buffering operations for its standard streams. + + Mandatory arguments to long options are mandatory for short options too. +stdbuf-usage = stdbuf [OPTION]... COMMAND +stdbuf-after-help = If MODE is 'L' the corresponding stream will be line buffered. + This option is invalid with standard input. + + If MODE is '0' the corresponding stream will be unbuffered. + + Otherwise, MODE is a number which may be followed by one of the following: + + KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y. + In this case the corresponding stream will be fully buffered with the buffer size set to MODE bytes. + + NOTE: If COMMAND adjusts the buffering of its standard streams (tee does for e.g.) then that will override corresponding settings changed by stdbuf. + Also some filters (like dd and cat etc.) don't use streams for I/O, and are thus unaffected by stdbuf settings. diff --git a/src/uu/stty/locales/en-US.ftl b/src/uu/stty/locales/en-US.ftl new file mode 100644 index 00000000000..06249f06ab4 --- /dev/null +++ b/src/uu/stty/locales/en-US.ftl @@ -0,0 +1,4 @@ +stty-about = Print or change terminal characteristics. +stty-usage = stty [-F DEVICE | --file=DEVICE] [SETTING]... + stty [-F DEVICE | --file=DEVICE] [-a|--all] + stty [-F DEVICE | --file=DEVICE] [-g|--save] diff --git a/src/uu/sum/locales/en-US.ftl b/src/uu/sum/locales/en-US.ftl new file mode 100644 index 00000000000..0d3f34b65ee --- /dev/null +++ b/src/uu/sum/locales/en-US.ftl @@ -0,0 +1,4 @@ +sum-about = Checksum and count the blocks in a file. + + With no FILE, or when FILE is -, read standard input. +sum-usage = sum [OPTION]... [FILE]... diff --git a/src/uu/sync/locales/en-US.ftl b/src/uu/sync/locales/en-US.ftl new file mode 100644 index 00000000000..bb7bfe3a556 --- /dev/null +++ b/src/uu/sync/locales/en-US.ftl @@ -0,0 +1,2 @@ +sync-about = Synchronize cached writes to persistent storage +sync-usage = sync [OPTION]... FILE... diff --git a/src/uu/tac/locales/en-US.ftl b/src/uu/tac/locales/en-US.ftl new file mode 100644 index 00000000000..28a3621f278 --- /dev/null +++ b/src/uu/tac/locales/en-US.ftl @@ -0,0 +1,2 @@ +tac-about = Write each file to standard output, last line first. +tac-usage = tac [OPTION]... [FILE]... diff --git a/src/uu/tail/locales/en-US.ftl b/src/uu/tail/locales/en-US.ftl new file mode 100644 index 00000000000..8d6394838de --- /dev/null +++ b/src/uu/tail/locales/en-US.ftl @@ -0,0 +1,6 @@ +tail-about = Print the last 10 lines of each FILE to standard output. + With more than one FILE, precede each with a header giving the file name. + With no FILE, or when FILE is -, read standard input. + + Mandatory arguments to long flags are mandatory for short flags too. +tail-usage = tail [FLAG]... [FILE]... diff --git a/src/uu/tee/locales/en-US.ftl b/src/uu/tee/locales/en-US.ftl new file mode 100644 index 00000000000..2f0286958af --- /dev/null +++ b/src/uu/tee/locales/en-US.ftl @@ -0,0 +1,3 @@ +tee-about = Copy standard input to each FILE, and also to standard output. +tee-usage = tee [OPTION]... [FILE]... +tee-after-help = If a FILE is -, it refers to a file named - . diff --git a/src/uu/test/locales/en-US.ftl b/src/uu/test/locales/en-US.ftl new file mode 100644 index 00000000000..0f7a6739417 --- /dev/null +++ b/src/uu/test/locales/en-US.ftl @@ -0,0 +1,71 @@ +test-about = Check file types and compare values. +test-usage = test EXPRESSION + test + {"[ EXPRESSION ]"} + {"[ ]"} + {"[ OPTION ]"} +test-after-help = Exit with the status determined by EXPRESSION. + + An omitted EXPRESSION defaults to false. + Otherwise, EXPRESSION is true or false and sets exit status. + + It is one of: + + - ( EXPRESSION ) EXPRESSION is true + - ! EXPRESSION EXPRESSION is false + - EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true + - EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true + + String operations: + - -n STRING the length of STRING is nonzero + - STRING equivalent to -n STRING + - -z STRING the length of STRING is zero + - STRING1 = STRING2 the strings are equal + - STRING1 != STRING2 the strings are not equal + + Integer comparisons: + - INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2 + - INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2 + - INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2 + - INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2 + - INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2 + - INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2 + + File operations: + - FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers + - FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2 + - FILE1 -ot FILE2 FILE1 is older than FILE2 + + - -b FILE FILE exists and is block special + - -c FILE FILE exists and is character special + - -d FILE FILE exists and is a directory + - -e FILE FILE exists + - -f FILE FILE exists and is a regular file + - -g FILE FILE exists and is set-group-ID + - -G FILE FILE exists and is owned by the effective group ID + - -h FILE FILE exists and is a symbolic link (same as -L) + - -k FILE FILE exists and has its sticky bit set + - -L FILE FILE exists and is a symbolic link (same as -h) + - -N FILE FILE exists and has been modified since it was last read + - -O FILE FILE exists and is owned by the effective user ID + - -p FILE FILE exists and is a named pipe + - -r FILE FILE exists and read permission is granted + - -s FILE FILE exists and has a size greater than zero + - -S FILE FILE exists and is a socket + - -t FD file descriptor FD is opened on a terminal + - -u FILE FILE exists and its set-user-ID bit is set + - -w FILE FILE exists and write permission is granted + - -x FILE FILE exists and execute (or search) permission is granted + + Except for -h and -L, all FILE-related tests dereference (follow) symbolic links. + Beware that parentheses need to be escaped (e.g., by backslashes) for shells. + INTEGER may also be -l STRING, which evaluates to the length of STRING. + + NOTE: Binary -a and -o are inherently ambiguous. + Use test EXPR1 && test EXPR2 or test EXPR1 || test EXPR2 instead. + + NOTE: [ honors the --help and --version options, but test does not. + test treats each of those as it treats any other nonempty STRING. + + NOTE: your shell may have its own version of test and/or [, which usually supersedes the version described here. + Please refer to your shell's documentation for details about the options it supports. diff --git a/src/uu/timeout/locales/en-US.ftl b/src/uu/timeout/locales/en-US.ftl new file mode 100644 index 00000000000..57126de1efc --- /dev/null +++ b/src/uu/timeout/locales/en-US.ftl @@ -0,0 +1,2 @@ +timeout-about = Start COMMAND, and kill it if still running after DURATION. +timeout-usage = timeout [OPTION] DURATION COMMAND... diff --git a/src/uu/touch/locales/en-US.ftl b/src/uu/touch/locales/en-US.ftl new file mode 100644 index 00000000000..a3e6d6e3e8a --- /dev/null +++ b/src/uu/touch/locales/en-US.ftl @@ -0,0 +1,2 @@ +touch-about = Update the access and modification times of each FILE to the current time. +touch-usage = touch [OPTION]... [USER] diff --git a/src/uu/tr/locales/en-US.ftl b/src/uu/tr/locales/en-US.ftl new file mode 100644 index 00000000000..931c6265b2b --- /dev/null +++ b/src/uu/tr/locales/en-US.ftl @@ -0,0 +1,3 @@ +tr-about = Translate or delete characters +tr-usage = tr [OPTION]... SET1 [SET2] +tr-after-help = Translate, squeeze, and/or delete characters from standard input, writing to standard output. diff --git a/src/uu/true/locales/en-US.ftl b/src/uu/true/locales/en-US.ftl new file mode 100644 index 00000000000..e75e9e67088 --- /dev/null +++ b/src/uu/true/locales/en-US.ftl @@ -0,0 +1,5 @@ +true-about = Returns true, a successful exit status. + + Immediately returns with the exit status 0, except when invoked with one of the recognized + options. In those cases it will try to write the help or version text. Any IO error during this + operation causes the program to return 1 instead. diff --git a/src/uu/truncate/locales/en-US.ftl b/src/uu/truncate/locales/en-US.ftl new file mode 100644 index 00000000000..b3ae2fcc48a --- /dev/null +++ b/src/uu/truncate/locales/en-US.ftl @@ -0,0 +1,18 @@ +truncate-about = Shrink or extend the size of each file to the specified size. +truncate-usage = truncate [OPTION]... [FILE]... +truncate-after-help = SIZE is an integer with an optional prefix and optional unit. + The available units (K, M, G, T, P, E, Z, and Y) use the following format: + 'KB' => 1000 (kilobytes) + 'K' => 1024 (kibibytes) + 'MB' => 1000*1000 (megabytes) + 'M' => 1024*1024 (mebibytes) + 'GB' => 1000*1000*1000 (gigabytes) + 'G' => 1024*1024*1024 (gibibytes) + SIZE may also be prefixed by one of the following to adjust the size of each + file based on its current size: + '+' => extend by + '-' => reduce by + '<' => at most + '>' => at least + '/' => round down to multiple of + '%' => round up to multiple of diff --git a/src/uu/tsort/locales/en-US.ftl b/src/uu/tsort/locales/en-US.ftl new file mode 100644 index 00000000000..12cafe40c94 --- /dev/null +++ b/src/uu/tsort/locales/en-US.ftl @@ -0,0 +1,5 @@ +tsort-about = Topological sort the strings in FILE. + Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline), ordering them based on dependencies in a directed acyclic graph (DAG). + Useful for scheduling and determining execution order. + If FILE is not passed in, stdin is used instead. +tsort-usage = tsort [OPTIONS] FILE diff --git a/src/uu/tty/locales/en-US.ftl b/src/uu/tty/locales/en-US.ftl new file mode 100644 index 00000000000..8d480ec4a63 --- /dev/null +++ b/src/uu/tty/locales/en-US.ftl @@ -0,0 +1,2 @@ +tty-about = Print the file name of the terminal connected to standard input. +tty-usage = tty [OPTION]... diff --git a/src/uu/uname/locales/en-US.ftl b/src/uu/uname/locales/en-US.ftl new file mode 100644 index 00000000000..78faa1c35e4 --- /dev/null +++ b/src/uu/uname/locales/en-US.ftl @@ -0,0 +1,3 @@ +uname-about = Print certain system information. + With no OPTION, same as -s. +uname-usage = uname [OPTION]... diff --git a/src/uu/unexpand/locales/en-US.ftl b/src/uu/unexpand/locales/en-US.ftl new file mode 100644 index 00000000000..b66a71494c1 --- /dev/null +++ b/src/uu/unexpand/locales/en-US.ftl @@ -0,0 +1,3 @@ +unexpand-about = Convert blanks in each FILE to tabs, writing to standard output. + With no FILE, or when FILE is -, read standard input. +unexpand-usage = unexpand [OPTION]... [FILE]... diff --git a/src/uu/uniq/locales/en-US.ftl b/src/uu/uniq/locales/en-US.ftl new file mode 100644 index 00000000000..4ede870d18e --- /dev/null +++ b/src/uu/uniq/locales/en-US.ftl @@ -0,0 +1,7 @@ +uniq-about = Report or omit repeated lines. +uniq-usage = uniq [OPTION]... [INPUT [OUTPUT]] +uniq-after-help = Filter adjacent matching lines from INPUT (or standard input), + writing to OUTPUT (or standard output). + + Note: uniq does not detect repeated lines unless they are adjacent. + You may want to sort the input first, or use sort -u without uniq. diff --git a/src/uu/unlink/locales/en-US.ftl b/src/uu/unlink/locales/en-US.ftl new file mode 100644 index 00000000000..e819d905e34 --- /dev/null +++ b/src/uu/unlink/locales/en-US.ftl @@ -0,0 +1,3 @@ +unlink-about = Unlink the file at FILE. +unlink-usage = unlink FILE + unlink OPTION diff --git a/src/uu/uptime/locales/en-US.ftl b/src/uu/uptime/locales/en-US.ftl new file mode 100644 index 00000000000..2fe2b23ceb6 --- /dev/null +++ b/src/uu/uptime/locales/en-US.ftl @@ -0,0 +1,4 @@ +uptime-about = Display the current time, the length of time the system has been up, + the number of users on the system, and the average number of jobs + in the run queue over the last 1, 5 and 15 minutes. +uptime-usage = uptime [OPTION]... diff --git a/src/uu/users/locales/en-US.ftl b/src/uu/users/locales/en-US.ftl new file mode 100644 index 00000000000..a3d5786ac57 --- /dev/null +++ b/src/uu/users/locales/en-US.ftl @@ -0,0 +1,2 @@ +users-about = Print the user names of users currently logged in to the current host. +users-usage = users [FILE] diff --git a/src/uu/vdir/locales/en-US.ftl b/src/uu/vdir/locales/en-US.ftl new file mode 100644 index 00000000000..f0d63274eb8 --- /dev/null +++ b/src/uu/vdir/locales/en-US.ftl @@ -0,0 +1,5 @@ +vdir-about = List directory contents. + Ignore files and directories starting with a '.' by default. + + Mandatory arguments to long options are mandatory for short options too. +vdir-usage = vdir [OPTION]... [FILE]... diff --git a/src/uu/wc/locales/en-US.ftl b/src/uu/wc/locales/en-US.ftl new file mode 100644 index 00000000000..7eb458ec24a --- /dev/null +++ b/src/uu/wc/locales/en-US.ftl @@ -0,0 +1,3 @@ +wc-about = Display newline, word, and byte counts for each FILE, and a total line if + more than one FILE is specified. With no FILE, or when FILE is -, read standard input. +wc-usage = wc [OPTION]... [FILE]... diff --git a/src/uu/who/locales/en-US.ftl b/src/uu/who/locales/en-US.ftl new file mode 100644 index 00000000000..9b55893e6a9 --- /dev/null +++ b/src/uu/who/locales/en-US.ftl @@ -0,0 +1,2 @@ +who-about = Print information about users who are currently logged in. +who-usage = who [OPTION]... [ FILE | ARG1 ARG2 ] diff --git a/src/uu/whoami/locales/en-US.ftl b/src/uu/whoami/locales/en-US.ftl new file mode 100644 index 00000000000..aedac774c31 --- /dev/null +++ b/src/uu/whoami/locales/en-US.ftl @@ -0,0 +1 @@ +whoami-about = Print the current username. diff --git a/src/uu/yes/locales/en-US.ftl b/src/uu/yes/locales/en-US.ftl new file mode 100644 index 00000000000..d4d0806b18b --- /dev/null +++ b/src/uu/yes/locales/en-US.ftl @@ -0,0 +1,2 @@ +yes-about = Repeatedly display a line with STRING (or 'y') +yes-usage = yes [STRING]... From 94c1fad206543444431507349733782fdb366f67 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 17:57:43 +0200 Subject: [PATCH 112/139] l10n: adjust the code to use the fluent files instead of the PROG.md docs --- src/uu/basename/src/basename.rs | 4 ++-- src/uu/cat/src/cat.rs | 5 ++--- src/uu/chcon/src/chcon.rs | 4 ++-- src/uu/chgrp/src/chgrp.rs | 4 ++-- src/uu/chmod/src/chmod.rs | 9 +++++---- src/uu/chown/src/chown.rs | 4 ++-- src/uu/chroot/src/chroot.rs | 5 ++--- src/uu/cksum/src/cksum.rs | 5 ++--- src/uu/comm/src/comm.rs | 4 ++-- src/uu/cp/src/cp.rs | 7 +++---- src/uu/csplit/src/csplit.rs | 4 ++-- src/uu/cut/src/cut.rs | 5 ++--- src/uu/date/src/date.rs | 5 ++--- src/uu/dd/src/dd.rs | 4 ++-- src/uu/df/src/df.rs | 4 ++-- src/uu/dircolors/src/dircolors.rs | 5 ++--- src/uu/dirname/src/dirname.rs | 4 ++-- src/uu/du/src/du.rs | 5 ++--- src/uu/echo/src/echo.rs | 4 ++-- src/uu/env/src/env.rs | 4 ++-- src/uu/expand/src/expand.rs | 4 ++-- src/uu/expr/src/expr.rs | 9 +++++---- src/uu/factor/src/factor.rs | 4 ++-- src/uu/false/src/false.rs | 3 +-- src/uu/fmt/src/fmt.rs | 4 ++-- src/uu/fold/src/fold.rs | 5 ++--- src/uu/groups/src/groups.rs | 4 ++-- src/uu/hashsum/src/hashsum.rs | 4 ++-- src/uu/head/src/head.rs | 5 ++--- src/uu/hostid/src/hostid.rs | 4 ++-- src/uu/hostname/src/hostname.rs | 5 ++--- src/uu/id/src/id.rs | 5 ++--- src/uu/install/src/install.rs | 5 ++--- src/uu/join/src/join.rs | 4 ++-- src/uu/kill/src/kill.rs | 4 ++-- src/uu/link/src/link.rs | 4 ++-- src/uu/ln/src/ln.rs | 5 ++--- src/uu/logname/src/logname.rs | 5 ++--- src/uu/ls/src/ls.rs | 14 +++++--------- src/uu/mkdir/src/mkdir.rs | 5 ++--- src/uu/mkfifo/src/mkfifo.rs | 5 ++--- src/uu/mknod/src/mknod.rs | 4 ++-- src/uu/mktemp/src/mktemp.rs | 4 ++-- src/uu/more/src/more.rs | 4 ++-- src/uu/mv/src/mv.rs | 5 ++--- src/uu/nice/src/nice.rs | 5 ++--- src/uu/nl/src/nl.rs | 5 ++--- src/uu/nohup/src/nohup.rs | 4 ++-- src/uu/nproc/src/nproc.rs | 5 ++--- src/uu/numfmt/src/numfmt.rs | 5 ++--- src/uu/od/src/od.rs | 5 ++--- src/uu/paste/src/paste.rs | 4 ++-- src/uu/pathchk/src/pathchk.rs | 5 ++--- src/uu/pinky/src/pinky.rs | 4 ++-- src/uu/pr/src/pr.rs | 4 ++-- src/uu/printenv/src/printenv.rs | 4 ++-- src/uu/printf/src/printf.rs | 4 ++-- src/uu/ptx/src/ptx.rs | 4 ++-- src/uu/pwd/src/pwd.rs | 4 ++-- src/uu/readlink/src/readlink.rs | 4 ++-- src/uu/realpath/src/realpath.rs | 4 +--- src/uu/rm/src/rm.rs | 7 ++----- src/uu/rmdir/src/rmdir.rs | 4 ++-- src/uu/runcon/src/runcon.rs | 7 +++---- src/uu/seq/src/seq.rs | 4 ++-- src/uu/shred/src/shred.rs | 4 ++-- src/uu/shuf/src/shuf.rs | 6 ++---- src/uu/sleep/src/sleep.rs | 5 ++--- src/uu/sort/src/sort.rs | 4 ++-- src/uu/split/src/split.rs | 5 ++--- src/uu/stat/src/stat.rs | 11 +++++------ src/uu/stdbuf/src/stdbuf.rs | 4 +--- src/uu/stty/src/stty.rs | 2 +- src/uu/sum/src/sum.rs | 4 ++-- src/uu/sync/src/sync.rs | 4 ++-- src/uu/tac/src/tac.rs | 5 ++--- src/uu/tail/src/args.rs | 4 ++-- src/uu/tee/src/tee.rs | 4 ++-- src/uu/test/src/test.rs | 4 ++-- src/uu/timeout/src/timeout.rs | 5 ++--- src/uu/touch/src/touch.rs | 5 ++--- src/uu/tr/src/tr.rs | 4 ++-- src/uu/true/src/true.rs | 3 +-- src/uu/truncate/src/truncate.rs | 5 ++--- src/uu/tsort/src/tsort.rs | 4 ++-- src/uu/tty/src/tty.rs | 4 ++-- src/uu/uname/src/uname.rs | 5 ++--- src/uu/unexpand/src/unexpand.rs | 4 ++-- src/uu/uniq/src/uniq.rs | 4 ++-- src/uu/unlink/src/unlink.rs | 4 ++-- src/uu/uptime/src/uptime.rs | 4 ++-- src/uu/users/src/users.rs | 4 ++-- src/uu/wc/src/wc.rs | 5 ++--- src/uu/who/src/who.rs | 4 ++-- src/uu/whoami/src/whoami.rs | 4 +--- src/uu/yes/src/yes.rs | 4 ++-- 96 files changed, 200 insertions(+), 249 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 62046a15d60..c39c329df87 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -9,10 +9,10 @@ use clap::{Arg, ArgAction, Command}; use std::path::{PathBuf, is_separator}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; +use uucore::format_usage; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static MULTIPLE: &str = "multiple"; diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index d60262e1d7c..cd89c3bc385 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -24,14 +24,13 @@ use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; use uucore::fs::FileInformation; -use uucore::{fast_inc::fast_inc_one, format_usage, help_about, help_usage}; +use uucore::locale::get_message; +use uucore::{fast_inc::fast_inc_one, format_usage}; /// Linux splice support #[cfg(any(target_os = "linux", target_os = "android"))] mod splice; -use uucore::locale::{self, get_message}; - // Allocate 32 digits for the line number. // An estimate is that we can print about 1e8 lines/seconds, so 32 digits // would be enough for billions of universe lifetimes. diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index eaf820d2d6f..be1bc2d5be0 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -8,7 +8,7 @@ use clap::builder::ValueParser; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::{display::Quotable, format_usage, help_about, help_usage, show_error, show_warning}; +use uucore::{display::Quotable, format_usage, show_error, show_warning}; use clap::{Arg, ArgAction, Command}; use selinux::{OpaqueSecurityContext, SecurityContext}; @@ -24,7 +24,7 @@ mod fts; use errors::*; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static HELP: &str = "help"; diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index fa5daedb930..0fb0489034e 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -8,15 +8,15 @@ use uucore::display::Quotable; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; use uucore::perms::{GidUidOwnerFilter, IfFrom, chown_base, options}; -use uucore::{format_usage, help_about, help_usage}; use clap::{Arg, ArgAction, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; fn parse_gid_from_str(group: &str) -> Result { if let Some(gid_str) = group.strip_prefix(':') { diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index b899a237c38..7367d4b76ff 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -17,10 +17,9 @@ use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; use uucore::perms::{TraverseSymlinks, configure_symlink_and_recursion}; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; +use uucore::{format_usage, show, show_error}; -use uucore::locale::{self, get_message}; -const LONG_USAGE: &str = help_section!("after help", "chmod.md"); +use uucore::locale::get_message; mod options { pub const HELP: &str = "help"; @@ -93,7 +92,9 @@ fn extract_negative_modes(mut args: impl uucore::Args) -> (Option, Vec UResult<()> { let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name - let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(get_message("chmod-after-help")) + .try_get_matches_from(args)?; let changes = matches.get_flag(options::CHANGES); let quiet = matches.get_flag(options::QUIET); diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index af6cbc843f5..bf820c11ea3 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -7,8 +7,8 @@ use uucore::display::Quotable; pub use uucore::entries::{self, Group, Locate, Passwd}; +use uucore::format_usage; use uucore::perms::{GidUidOwnerFilter, IfFrom, chown_base, options}; -use uucore::{format_usage, help_about, help_usage}; use uucore::error::{FromIo, UResult, USimpleError}; @@ -17,7 +17,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use std::fs; use std::os::unix::fs::MetadataExt; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult { let filter = if let Some(spec) = matches.get_one::(options::FROM) { diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 48c5de17b2a..0b04e79ae47 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -17,10 +17,9 @@ use uucore::entries::{Locate, Passwd, grp2gid, usr2uid}; use uucore::error::{UClapError, UResult, UUsageError, set_exit_code}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; -use uucore::locale::{self, get_message}; -static USAGE: &str = help_usage!("chroot.md"); +use uucore::locale::get_message; mod options { pub const NEWROOT: &str = "newroot"; diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 0532579975c..0ded6c634de 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -17,17 +17,16 @@ use uucore::checksum::{ ChecksumVerbose, SUPPORTED_ALGORITHMS, calculate_blake2b_length, detect_algo, digest_reader, perform_checksum_validation, }; +use uucore::locale::get_message; use uucore::{ encoding, error::{FromIo, UResult, USimpleError}, - format_usage, help_about, help_section, help_usage, + format_usage, line_ending::LineEnding, os_str_as_bytes, show, sum::Digest, }; -use uucore::locale::{self, get_message}; - #[derive(Debug, PartialEq)] enum OutputFormat { Hexadecimal, diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index c67b8665a11..d595b463d2d 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -9,13 +9,13 @@ use std::cmp::Ordering; use std::fs::{File, metadata}; use std::io::{self, BufRead, BufReader, Read, Stdin, stdin}; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; use uucore::fs::paths_refer_to_same_file; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_usage}; use clap::{Arg, ArgAction, ArgMatches, Command}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const COLUMN_1: &str = "1"; diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 37c2367cd66..d2966c5c4c2 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -36,11 +36,12 @@ use uucore::{backup_control, update_control}; // requires these enum. pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{ - format_usage, help_about, help_section, help_usage, - parser::shortcut_value_parser::ShortcutValueParser, prompt_yes, show_error, show_warning, + format_usage, parser::shortcut_value_parser::ShortcutValueParser, prompt_yes, show_error, + show_warning, }; use crate::copydir::copy_directory; +use uucore::locale::get_message; mod copydir; mod platform; @@ -451,8 +452,6 @@ fn show_debug(copy_debug: &CopyDebug) { ); } -use uucore::locale::{self, get_message}; - static EXIT_ERR: i32 = 1; // Argument constants diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index ed025da625b..5b2478ccd58 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -16,7 +16,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use regex::Regex; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::format_usage; mod csplit_error; mod patterns; @@ -25,7 +25,7 @@ mod split_name; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const SUFFIX_FORMAT: &str = "suffix-format"; diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 4d9241b6856..19d5d7c0a3e 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -18,14 +18,13 @@ use uucore::os_str_as_bytes; use self::searcher::Searcher; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; +use uucore::locale::get_message; use uucore::ranges::Range; -use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; +use uucore::{format_usage, show_error, show_if_err}; mod matcher; mod searcher; -use uucore::locale::{self, get_message}; - struct Options<'a> { out_delimiter: Option<&'a [u8]>, line_ending: LineEnding, diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index a2e5be2d0ed..9f8b4c095c4 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -17,10 +17,11 @@ use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; #[cfg(windows)] use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime}; +use uucore::locale::get_message; use uucore::parser::shortcut_value_parser::ShortcutValueParser; // Options @@ -30,8 +31,6 @@ const MINUTES: &str = "minutes"; const SECONDS: &str = "seconds"; const NS: &str = "ns"; -use uucore::locale::{self, get_message}; - const OPT_DATE: &str = "date"; const OPT_FORMAT: &str = "format"; const OPT_FILE: &str = "file"; diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 3c1d1e42800..c0e37263fa2 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -60,9 +60,9 @@ use uucore::error::{FromIo, UResult}; use uucore::error::{USimpleError, set_exit_code}; #[cfg(target_os = "linux")] use uucore::show_if_err; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; +use uucore::{format_usage, show_error}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const BUF_INIT_BYTE: u8 = 0xDD; /// Final settings after parsing diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index c5d1a5aa46b..c346cf16396 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -15,7 +15,7 @@ use uucore::display::Quotable; use uucore::error::{UError, UResult, USimpleError, get_exit_code}; use uucore::fsext::{MountInfo, read_fs_list}; use uucore::parser::parse_size::ParseSizeError; -use uucore::{format_usage, help_about, help_section, help_usage, show}; +use uucore::{format_usage, show}; use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource}; @@ -29,7 +29,7 @@ use crate::filesystem::Filesystem; use crate::filesystem::FsError; use crate::table::Table; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; static OPT_HELP: &str = "help"; static OPT_ALL: &str = "all"; diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index ea5d5082528..ae9b7617763 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -16,7 +16,8 @@ use clap::{Arg, ArgAction, Command}; use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::{format_usage, help_about, help_section, help_usage, parser::parse_glob}; +use uucore::locale::get_message; +use uucore::{format_usage, parser::parse_glob}; mod options { pub const BOURNE_SHELL: &str = "bourne-shell"; @@ -26,8 +27,6 @@ mod options { pub const FILE: &str = "FILE"; } -use uucore::locale::{self, get_message}; - #[derive(PartialEq, Eq, Debug)] pub enum OutputFmt { Shell, diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 5eb6ca4b35e..02a6ea9384b 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -7,10 +7,10 @@ use clap::{Arg, ArgAction, Command}; use std::path::Path; use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; +use uucore::format_usage; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_section, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const ZERO: &str = "zero"; diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index c25d66f5deb..08a7c7b91d6 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -27,10 +27,11 @@ use thiserror::Error; use uucore::display::{Quotable, print_verbatim}; use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; use uucore::line_ending::LineEnding; +use uucore::locale::get_message; use uucore::parser::parse_glob; use uucore::parser::parse_size::{ParseSizeError, parse_size_u64}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_warning}; +use uucore::{format_usage, show, show_error, show_warning}; #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; #[cfg(windows)] @@ -70,8 +71,6 @@ mod options { pub const FILE: &str = "FILE"; } -use uucore::locale::{self, get_message}; - struct TraversalOptions { all: bool, separate_dirs: bool, diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index b6e699ce20c..d1306dd3e9d 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -10,9 +10,9 @@ use std::ffi::{OsStr, OsString}; use std::io::{self, StdoutLock, Write}; use uucore::error::{UResult, USimpleError}; use uucore::format::{FormatChar, OctalParsing, parse_escape_only}; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const STRING: &str = "STRING"; diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 0555dbe60ac..dbf5f1d56a8 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -36,7 +36,7 @@ use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError}; use uucore::line_ending::LineEnding; #[cfg(unix)] use uucore::signals::signal_by_name_or_value; -use uucore::{format_usage, help_about, help_section, help_usage, show_warning}; +use uucore::{format_usage, show_warning}; use thiserror::Error; @@ -74,7 +74,7 @@ impl From for EnvError { } } -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const IGNORE_ENVIRONMENT: &str = "ignore-environment"; diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index a7cb0928c18..2e8d1b296c0 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -16,9 +16,9 @@ use thiserror::Error; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, set_exit_code}; -use uucore::{format_usage, help_about, help_usage, show_error}; +use uucore::{format_usage, show_error}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static TABS: &str = "tabs"; diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 6a4f9487bb0..e43b5a36264 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -6,10 +6,11 @@ use clap::{Arg, ArgAction, Command}; use syntax_tree::{AstNode, is_truthy}; use thiserror::Error; +use uucore::locale::get_message; use uucore::{ display::Quotable, error::{UError, UResult}, - format_usage, help_about, help_section, help_usage, + format_usage, }; mod syntax_tree; @@ -67,9 +68,9 @@ impl UError for ExprError { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(help_about!("expr.md")) - .override_usage(format_usage(help_usage!("expr.md"))) - .after_help(help_section!("after help", "expr.md")) + .about(get_message("expr-about")) + .override_usage(format_usage(&get_message("expr-usage"))) + .after_help(get_message("expr-after-help")) .infer_long_args(true) .disable_help_flag(true) .disable_version_flag(true) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index f00d05eca8d..4820190f4f1 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -14,9 +14,9 @@ use num_bigint::BigUint; use num_traits::FromPrimitive; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; -use uucore::{format_usage, help_about, help_usage, show_error, show_warning}; +use uucore::{format_usage, show_error, show_warning}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub static EXPONENTS: &str = "exponents"; diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index a1e15ba421d..b8bf3a8253a 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -5,9 +5,8 @@ use clap::{Arg, ArgAction, Command}; use std::{ffi::OsString, io::Write}; use uucore::error::{UResult, set_exit_code}; -use uucore::help_about; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index f8d5cdfa89e..bb79c2c591d 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -10,15 +10,15 @@ use std::fs::File; use std::io::{BufReader, BufWriter, Read, Stdout, Write, stdin, stdout}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; use linebreak::break_lines; use parasplit::ParagraphStream; +use uucore::locale::get_message; mod linebreak; mod parasplit; -use uucore::locale::{self, get_message}; const MAX_WIDTH: usize = 2500; const DEFAULT_GOAL: usize = 70; const DEFAULT_WIDTH: usize = 75; diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 1735c56424c..465f964334a 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -11,12 +11,11 @@ use std::io::{BufRead, BufReader, Read, stdin}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; +use uucore::locale::get_message; const TAB_WIDTH: usize = 8; -use uucore::locale::{self, get_message}; - mod options { pub const BYTES: &str = "bytes"; pub const SPACES: &str = "spaces"; diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index dc2e7a6310d..54f5afbe3f1 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -10,15 +10,15 @@ use uucore::{ display::Quotable, entries::{Locate, Passwd, get_groups_gnu, gid2grp}, error::{UError, UResult}, - format_usage, help_about, help_usage, show, + format_usage, show, }; use clap::{Arg, ArgAction, Command}; +use uucore::locale::get_message; mod options { pub const USERS: &str = "USERNAME"; } -use uucore::locale::{self, get_message}; #[derive(Debug, Error)] enum GroupsError { diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 2a81a0b5668..621ec5a0b8f 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -26,11 +26,11 @@ use uucore::checksum::digest_reader; use uucore::checksum::escape_filename; use uucore::checksum::perform_checksum_validation; use uucore::error::{FromIo, UResult}; +use uucore::format_usage; +use uucore::locale::get_message; use uucore::sum::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; -use uucore::{format_usage, help_about, help_usage}; const NAME: &str = "hashsum"; -use uucore::locale::{self, get_message}; struct Options { algoname: &'static str, diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 35fe4611209..f6711d96094 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -16,12 +16,10 @@ use thiserror::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; const BUF_SIZE: usize = 65536; -use uucore::locale::{self, get_message}; - mod options { pub const BYTES_NAME: &str = "BYTES"; pub const LINES_NAME: &str = "LINES"; @@ -37,6 +35,7 @@ mod take; use take::copy_all_but_n_bytes; use take::copy_all_but_n_lines; use take::take_lines; +use uucore::locale::get_message; #[derive(Error, Debug)] enum HeadError { diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index cee59b7b0b9..aa29bd8fa84 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -7,9 +7,9 @@ use clap::Command; use libc::{c_long, gethostid}; -use uucore::{error::UResult, format_usage, help_about, help_usage}; +use uucore::{error::UResult, format_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 514e3db1fdc..05252883e60 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -15,14 +15,13 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] use dns_lookup::lookup_host; +use uucore::locale::get_message; use uucore::{ error::{FromIo, UResult}, - format_usage, help_about, help_usage, + format_usage, }; -use uucore::locale::{self, get_message}; - static OPT_DOMAIN: &str = "domain"; static OPT_IP_ADDRESS: &str = "ip-address"; static OPT_FQDN: &str = "fqdn"; diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 413ba06b37e..a0831474817 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -42,8 +42,9 @@ use uucore::error::{USimpleError, set_exit_code}; pub use uucore::libc; use uucore::libc::{getlogin, uid_t}; use uucore::line_ending::LineEnding; +use uucore::locale::get_message; use uucore::process::{getegid, geteuid, getgid, getuid}; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; +use uucore::{format_usage, show_error}; macro_rules! cstr2cow { ($v:expr) => { @@ -59,8 +60,6 @@ macro_rules! cstr2cow { }; } -use uucore::locale::{self, get_message}; - #[cfg(not(feature = "selinux"))] static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)"; #[cfg(feature = "selinux")] diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 3bfd542c1aa..4d93a3fc4ac 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -27,12 +27,13 @@ use uucore::perms::{Verbosity, VerbosityLevel, wrap_chown}; use uucore::process::{getegid, geteuid}; #[cfg(feature = "selinux")] use uucore::selinux::{contexts_differ, set_selinux_security_context}; -use uucore::{format_usage, help_about, help_usage, show, show_error, show_if_err}; +use uucore::{format_usage, show, show_error, show_if_err}; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(unix)] use std::os::unix::prelude::OsStrExt; +use uucore::locale::get_message; const DEFAULT_MODE: u32 = 0o755; const DEFAULT_STRIP_PROGRAM: &str = "strip"; @@ -140,8 +141,6 @@ impl Behavior { } } -use uucore::locale::{self, get_message}; - static OPT_COMPARE: &str = "compare"; static OPT_DIRECTORY: &str = "directory"; static OPT_IGNORED: &str = "ignored"; diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 838cce325a7..e676dadaa77 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -18,10 +18,10 @@ use std::os::unix::ffi::OsStrExt; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; +use uucore::format_usage; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[derive(Debug, Error)] enum JoinError { diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 8d78edd9875..6c67aede95b 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -12,9 +12,9 @@ use std::io::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; // When the -l option is selected, the program displays the type of signal related to a certain // value or string. In case of a value, the program should control the lower 8 bits, but there is diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 1fcb0186f52..5ff1760ba78 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -9,9 +9,9 @@ use std::fs::hard_link; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static FILES: &str = "FILES"; diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index ec3c2638ed7..960085200ec 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -9,7 +9,7 @@ use clap::{Arg, ArgAction, Command}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; use uucore::fs::{make_path_relative_to, paths_refer_to_same_file}; -use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error}; +use uucore::{format_usage, prompt_yes, show_error}; use std::borrow::Cow; use std::collections::HashSet; @@ -24,6 +24,7 @@ use std::os::windows::fs::{symlink_dir, symlink_file}; use std::path::{Path, PathBuf}; use uucore::backup_control::{self, BackupMode}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; +use uucore::locale::get_message; pub struct Settings { overwrite: OverwriteMode, @@ -70,8 +71,6 @@ impl UError for LnError { } } -use uucore::locale::{self, get_message}; - mod options { pub const FORCE: &str = "force"; //pub const DIRECTORY: &str = "directory"; diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index a6cdde7df4e..1ed1efb5ff3 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -7,7 +7,8 @@ use clap::Command; use std::ffi::CStr; -use uucore::{error::UResult, format_usage, help_about, help_usage, show_error}; +use uucore::locale::get_message; +use uucore::{error::UResult, show_error}; unsafe extern "C" { // POSIX requires using getlogin (or equivalent code) @@ -25,8 +26,6 @@ fn get_userlogin() -> Option { } } -use uucore::locale::{self, get_message}; - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let _ = uu_app().try_get_matches_from(args)?; diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 33975b217cc..f3884f3023f 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -37,8 +37,11 @@ use jiff::{Timestamp, Zoned}; use lscolors::LsColors; use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB}; use thiserror::Error; +#[cfg(unix)] +use uucore::entries; use uucore::error::USimpleError; use uucore::format::human::{SizeFormat, human_readable}; +use uucore::fs::FileInformation; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] use uucore::fsxattr::has_acl; #[cfg(unix)] @@ -57,6 +60,7 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; ))] use uucore::libc::{dev_t, major, minor}; use uucore::line_ending::LineEnding; +use uucore::locale::get_message; use uucore::quoting_style::{self, QuotingStyle, escape_name}; use uucore::{ display::Quotable, @@ -68,9 +72,7 @@ use uucore::{ parser::shortcut_value_parser::ShortcutValueParser, version_cmp::version_cmp, }; -use uucore::{ - help_about, help_section, help_usage, parser::parse_glob, show, show_error, show_warning, -}; +use uucore::{parser::parse_glob, show, show_error, show_warning}; mod dired; use dired::{DiredOutput, is_dired_arg_present}; @@ -82,8 +84,6 @@ static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not e #[cfg(feature = "selinux")] static CONTEXT_HELP_TEXT: &str = "print any security context of each file"; -use uucore::locale::{self, get_message}; - pub mod options { pub mod format { pub static ONE_LINE: &str = "1"; @@ -3014,10 +3014,6 @@ fn get_inode(metadata: &Metadata) -> String { // Currently getpwuid is `linux` target only. If it's broken state.out into // a posix-compliant attribute this can be updated... -#[cfg(unix)] -use uucore::entries; -use uucore::fs::FileInformation; - #[cfg(unix)] fn display_uname<'a>(metadata: &Metadata, config: &Config, state: &'a mut ListState) -> &'a String { let uid = metadata.uid(); diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 6c181e55f2a..7fceb36ce3f 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -13,15 +13,14 @@ use std::path::{Path, PathBuf}; #[cfg(not(windows))] use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; +use uucore::locale::get_message; #[cfg(not(windows))] use uucore::mode; use uucore::{display::Quotable, fs::dir_strip_dot_for_creation}; -use uucore::{format_usage, help_about, help_section, help_usage, show_if_err}; +use uucore::{format_usage, show_if_err}; static DEFAULT_PERM: u32 = 0o777; -use uucore::locale::{self, get_message}; - mod options { pub const MODE: &str = "mode"; pub const PARENTS: &str = "parents"; diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 7d3df867d52..ea5880438aa 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -10,10 +10,9 @@ use std::fs; use std::os::unix::fs::PermissionsExt; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; -static USAGE: &str = help_usage!("mkfifo.md"); -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub static MODE: &str = "mode"; diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 801b2256a1b..94aa043ca80 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -12,9 +12,9 @@ use std::ffi::CString; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError, set_exit_code}; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 21f61660fb7..ec649508425 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -8,7 +8,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser}; use uucore::display::{Quotable, println_verbatim}; use uucore::error::{FromIo, UError, UResult, UUsageError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; use std::env; use std::ffi::OsStr; @@ -25,7 +25,7 @@ use rand::Rng; use tempfile::Builder; use thiserror::Error; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX"; diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index c07846d19d8..fc268944895 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -23,10 +23,10 @@ use crossterm::{ }; use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::format_usage; use uucore::{display::Quotable, show}; -use uucore::{format_usage, help_about, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const BELL: char = '\x07'; // Printing this character will ring the bell // The prompt to be displayed at the top of the screen when viewing multiple files, diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 195c79e11f4..0b7e56d5308 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -40,7 +40,7 @@ use uucore::update_control; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which // requires these enums pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; -use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; +use uucore::{format_usage, prompt_yes, show}; use fs_extra::dir::{ CopyOptions as DirCopyOptions, TransitProcess, TransitProcessResult, get_size as dir_get_size, @@ -48,6 +48,7 @@ use fs_extra::dir::{ }; use crate::error::MvError; +use uucore::locale::get_message; /// Options contains all the possible behaviors and flags for mv. /// @@ -123,8 +124,6 @@ pub enum OverwriteMode { Force, } -use uucore::locale::{self, get_message}; - static OPT_FORCE: &str = "force"; static OPT_INTERACTIVE: &str = "interactive"; static OPT_NO_CLOBBER: &str = "no-clobber"; diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 927d9ad4b37..bb86d7778c5 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -11,9 +11,10 @@ use std::io::{Error, Write}; use std::ptr; use clap::{Arg, ArgAction, Command}; +use uucore::locale::get_message; use uucore::{ error::{UClapError, UResult, USimpleError, UUsageError, set_exit_code}, - format_usage, help_about, help_usage, show_error, + format_usage, show_error, }; pub mod options { @@ -21,8 +22,6 @@ pub mod options { pub static COMMAND: &str = "COMMAND"; } -use uucore::locale::{self, get_message}; - fn is_prefix_of(maybe_prefix: &str, target: &str, min_match: usize) -> bool { if maybe_prefix.len() < min_match || maybe_prefix.len() > target.len() { return false; diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 370d9e780f7..0b6397dfe3d 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -8,12 +8,11 @@ use std::fs::File; use std::io::{BufRead, BufReader, Read, stdin}; use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; +use uucore::locale::get_message; +use uucore::{format_usage, show_error}; mod helper; -use uucore::locale::{self, get_message}; - // Settings store options used by nl to produce its output. pub struct Settings { // The variables corresponding to the options -h, -b, and -f. diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 4fa8ed37eda..a855b01f17d 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -17,9 +17,9 @@ use std::path::{Path, PathBuf}; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{UClapError, UError, UResult, set_exit_code}; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; +use uucore::{format_usage, show_error}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; static NOHUP_OUT: &str = "nohup.out"; // exit codes that match the GNU implementation static EXIT_CANCELED: i32 = 125; diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 008cd00505c..beed928349f 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -9,7 +9,8 @@ use clap::{Arg, ArgAction, Command}; use std::{env, thread}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; +use uucore::locale::get_message; #[cfg(any(target_os = "linux", target_os = "android"))] pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; @@ -23,8 +24,6 @@ pub const _SC_NPROCESSORS_CONF: libc::c_int = 1001; static OPT_ALL: &str = "all"; static OPT_IGNORE: &str = "ignore"; -use uucore::locale::{self, get_message}; - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index c82ff9453dc..de977d03587 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -15,17 +15,16 @@ use std::str::FromStr; use units::{IEC_BASES, SI_BASES}; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::locale::get_message; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::ranges::Range; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; +use uucore::{format_usage, show, show_error}; pub mod errors; pub mod format; pub mod options; mod units; -use uucore::locale::{self, get_message}; - fn handle_args<'a>(args: impl Iterator, options: &NumfmtOptions) -> UResult<()> { for l in args { format_and_handle_validation(l, options)?; diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 1a728c937f5..a84adece255 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -44,14 +44,13 @@ use clap::ArgAction; use clap::{Arg, ArgMatches, Command, parser::ValueSource}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; +use uucore::locale::get_message; use uucore::parser::parse_size::ParseSizeError; use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_warning}; +use uucore::{format_usage, show_error, show_warning}; const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes -use uucore::locale::{self, get_message}; - pub(crate) mod options { pub const HELP: &str = "help"; pub const ADDRESS_RADIX: &str = "address-radix"; diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 64c27384e07..9bb94294fc0 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -11,10 +11,10 @@ use std::iter::Cycle; use std::rc::Rc; use std::slice::Iter; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const DELIMITER: &str = "delimiters"; diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index d27df4c5110..cdd6418c4ad 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -10,7 +10,8 @@ use std::fs; use std::io::{ErrorKind, Write}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError, set_exit_code}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; +use uucore::locale::get_message; // operating mode enum Mode { @@ -20,8 +21,6 @@ enum Mode { Both, // a combination of `Basic` and `Extra` } -use uucore::locale::{self, get_message}; - mod options { pub const POSIX: &str = "posix"; pub const POSIX_SPECIAL: &str = "posix-special"; diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 5b1ea9ddf7e..2f687661154 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -6,7 +6,7 @@ // spell-checker:ignore (ToDO) BUFSIZE gecos fullname, mesg iobuf use clap::{Arg, ArgAction, Command}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; mod platform; @@ -20,7 +20,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const LONG_FORMAT: &str = "long_format"; diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 0741f17c5b3..4ca97e784a1 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -18,9 +18,9 @@ use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const TAB: char = '\t'; const LINES_PER_PAGE: usize = 66; const LINES_PER_PAGE_FOR_FORM_FEED: usize = 63; diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 85925bf8ff6..15e39a678e9 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -5,9 +5,9 @@ use clap::{Arg, ArgAction, Command}; use std::env; -use uucore::{error::UResult, format_usage, help_about, help_usage}; +use uucore::{error::UResult, format_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; static OPT_NULL: &str = "null"; diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index bc36305cc98..a28fc6aaea2 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -7,11 +7,11 @@ use std::io::stdout; use std::ops::ControlFlow; use uucore::error::{UResult, UUsageError}; use uucore::format::{FormatArgument, FormatArguments, FormatItem, parse_spec_and_escape}; -use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show_warning}; +use uucore::locale::get_message; +use uucore::{format_usage, os_str_as_bytes, show_warning}; const VERSION: &str = "version"; const HELP: &str = "help"; -use uucore::locale::{self, get_message}; mod options { pub const FORMAT: &str = "FORMAT"; diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 82e7cec5201..856183a5499 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -17,9 +17,9 @@ use regex::Regex; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, UUsageError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[derive(Debug)] enum OutFormat { diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index b7d9ea69d78..f1f398d9b0c 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -8,12 +8,12 @@ use clap::{Arg, Command}; use std::env; use std::io; use std::path::PathBuf; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const OPT_LOGICAL: &str = "logical"; const OPT_PHYSICAL: &str = "physical"; diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index d05efa61051..f41e076a353 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -13,9 +13,9 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::line_ending::LineEnding; -use uucore::{format_usage, help_about, help_usage, show_error}; +use uucore::{format_usage, show_error}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const OPT_CANONICALIZE: &str = "canonicalize"; const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 3a947d9a070..4868ba68ade 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -11,18 +11,16 @@ use std::{ path::{Path, PathBuf}, }; use uucore::fs::make_path_relative_to; +use uucore::locale::get_message; use uucore::{ display::{Quotable, print_verbatim}, error::{FromIo, UClapError, UResult}, format_usage, fs::{MissingHandling, ResolveMode, canonicalize}, - help_about, help_usage, line_ending::LineEnding, show_if_err, }; -use uucore::locale::{self, get_message}; - static OPT_QUIET: &str = "quiet"; static OPT_STRIP: &str = "strip"; static OPT_ZERO: &str = "zero"; diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 367ae04fb10..9e07bc077a4 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -18,9 +18,8 @@ use std::path::MAIN_SEPARATOR; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::{ - format_usage, help_about, help_section, help_usage, os_str_as_bytes, prompt_yes, show_error, -}; +use uucore::locale::get_message; +use uucore::{format_usage, os_str_as_bytes, prompt_yes, show_error}; #[derive(Eq, PartialEq, Clone, Copy)] /// Enum, determining when the `rm` will prompt the user about the file deletion @@ -90,8 +89,6 @@ impl Default for Options { } } -use uucore::locale::{self, get_message}; - static OPT_DIR: &str = "dir"; static OPT_INTERACTIVE: &str = "interactive"; static OPT_FORCE: &str = "force"; diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 8be21e6182b..228b085ec21 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -14,9 +14,9 @@ use std::path::Path; use uucore::display::Quotable; use uucore::error::{UResult, set_exit_code, strip_errno}; -use uucore::{format_usage, help_about, help_usage, show_error, util_name}; +use uucore::{format_usage, show_error, util_name}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; static OPT_PARENTS: &str = "parents"; static OPT_VERBOSE: &str = "verbose"; diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index 01f692ff312..ee67a92201a 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -10,7 +10,7 @@ use uucore::error::{UClapError, UError, UResult}; use clap::{Arg, ArgAction, Command}; use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext}; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::format_usage; use std::borrow::Cow; use std::ffi::{CStr, CString, OsStr, OsString}; @@ -23,8 +23,7 @@ mod errors; use errors::error_exit_status; use errors::{Error, Result, RunconError}; -use uucore::locale::{self, get_message}; -const DESCRIPTION: &str = help_section!("after help", "runcon.md"); +use uucore::locale::get_message; pub mod options { pub const COMPUTE: &str = "compute"; @@ -90,7 +89,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) .about(get_message("runcon-about")) - .after_help(DESCRIPTION) + .after_help(get_message("runcon-after-help")) .override_usage(format_usage(&get_message("runcon-usage"))) .infer_long_args(true) .arg( diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index bd7d9b3f257..2dd5f17b81d 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -15,7 +15,7 @@ use uucore::error::{FromIo, UResult}; use uucore::extendedbigdecimal::ExtendedBigDecimal; use uucore::format::num_format::FloatVariant; use uucore::format::{Format, num_format}; -use uucore::{fast_inc::fast_inc, format_usage, help_about, help_usage}; +use uucore::{fast_inc::fast_inc, format_usage}; mod error; @@ -28,7 +28,7 @@ mod numberparse; use crate::error::SeqError; use crate::number::PreciseNumber; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const OPT_SEPARATOR: &str = "separator"; const OPT_TERMINATOR: &str = "terminator"; diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index d42d652fcfb..0cfafd8627c 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -18,9 +18,9 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parser::parse_size::parse_size_u64; use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; +use uucore::{format_usage, show_error, show_if_err}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub const FORCE: &str = "force"; diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 73ee2b37164..db44636150d 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -19,7 +19,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use uucore::display::{OsWrite, Quotable}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; +use uucore::locale::get_message; mod rand_read_adapter; @@ -29,9 +30,6 @@ enum Mode { InputRange(RangeInclusive), } -static USAGE: &str = help_usage!("shuf.md"); -use uucore::locale::{self, get_message}; - struct Options { head_count: usize, output: Option, diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index ff5b9ae2df7..9b307b1e8a6 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -8,15 +8,14 @@ use std::time::Duration; use uucore::{ error::{UResult, USimpleError, UUsageError}, - format_usage, help_about, help_section, help_usage, + format_usage, parser::parse_time, show_error, }; use clap::{Arg, ArgAction, Command}; -use uucore::locale::{self, get_message}; -static AFTER_HELP: &str = help_section!("after help", "sleep.md"); +use uucore::locale::get_message; mod options { pub const NUMBER: &str = "NUMBER"; diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 06ae5770d6a..c97d07a6aa1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -48,11 +48,11 @@ use uucore::line_ending::LineEnding; use uucore::parser::parse_size::{ParseSizeError, Parser}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::version_cmp::version_cmp; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; +use uucore::{format_usage, show_error}; use crate::tmp_dir::TmpDirWrapper; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub mod modes { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 777cc2479a1..e026aaa2e00 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -24,8 +24,9 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; use uucore::parser::parse_size::parse_size_u64; +use uucore::format_usage; +use uucore::locale::get_message; use uucore::uio_error; -use uucore::{format_usage, help_about, help_section, help_usage}; static OPT_BYTES: &str = "bytes"; static OPT_LINE_BYTES: &str = "line-bytes"; @@ -46,8 +47,6 @@ static OPT_IO_BLKSIZE: &str = "-io-blksize"; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; -use uucore::locale::{self, get_message}; - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (args, obs_lines) = handle_obsolete(args); diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index ca82955b685..2d8871d4044 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -13,9 +13,7 @@ use uucore::fsext::{ BirthTime, FsMeta, StatFs, pretty_filetype, pretty_fstype, read_fs_list, statfs, }; use uucore::libc::mode_t; -use uucore::{ - entries, format_usage, help_about, help_section, help_usage, show_error, show_warning, -}; +use uucore::{entries, format_usage, show_error, show_warning}; use chrono::{DateTime, Local}; use clap::{Arg, ArgAction, ArgMatches, Command}; @@ -28,8 +26,7 @@ use std::os::unix::prelude::OsStrExt; use std::path::Path; use std::{env, fs}; -use uucore::locale::{self, get_message}; -const LONG_USAGE: &str = help_section!("long usage", "stat.md"); +use uucore::locale::get_message; mod options { pub const DEREFERENCE: &str = "dereference"; @@ -1134,7 +1131,9 @@ impl Stater { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(get_message("stat-after-help")) + .try_get_matches_from(args)?; let stater = Stater::new(&matches)?; let exit_status = stater.exec(); diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 644a5f2ce29..9c2698a97f8 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -16,10 +16,8 @@ use tempfile::tempdir; use uucore::error::{FromIo, UClapError, UResult, USimpleError, UUsageError}; use uucore::format_usage; use uucore::parser::parse_size::parse_size_u64; -use uucore::{format_usage, help_about, help_section, help_usage}; -use uucore::locale::{self, get_message}; -const LONG_HELP: &str = help_section!("after help", "stdbuf.md"); +use uucore::locale::get_message; mod options { pub const INPUT: &str = "input"; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index e60e48cb22f..5b5a9948cd5 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -21,8 +21,8 @@ use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; use uucore::locale::get_message; -use uucore::{format_usage, help_about, help_usage}; #[cfg(not(any( target_os = "freebsd", diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 57bdbccc6cc..0f5e6c664c6 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -11,9 +11,9 @@ use std::io::{ErrorKind, Read, Write, stdin, stdout}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; fn bsd_sum(mut reader: impl Read) -> std::io::Result<(usize, u16)> { let mut buf = [0; 4096]; diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 0f45a74a1cb..47db35c324b 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -17,9 +17,9 @@ use uucore::display::Quotable; #[cfg(any(target_os = "linux", target_os = "android"))] use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static FILE_SYSTEM: &str = "file-system"; diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index bcad03b137e..0cb2009a988 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -17,12 +17,11 @@ use std::{ use uucore::display::Quotable; use uucore::error::UError; use uucore::error::UResult; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; use crate::error::TacError; -static USAGE: &str = help_usage!("tac.md"); -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub static BEFORE: &str = "before"; diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 329f5c58d03..374beffd06a 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -16,9 +16,9 @@ use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parser::parse_size::{ParseSizeError, parse_size_u64}; use uucore::parser::parse_time; use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::{format_usage, help_about, help_usage, show_warning}; +use uucore::{format_usage, show_warning}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub mod verbosity { diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index e366e147496..e5269caf242 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -12,14 +12,14 @@ use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::UResult; use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; +use uucore::{format_usage, show_error}; // spell-checker:ignore nopipe #[cfg(unix)] use uucore::signals::{enable_pipe_errors, ignore_interrupts}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const APPEND: &str = "append"; diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 16c179bcf52..674e70dd337 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -17,11 +17,11 @@ use std::fs; use std::os::unix::fs::MetadataExt; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; #[cfg(not(windows))] use uucore::process::{getegid, geteuid}; -use uucore::{format_usage, help_about, help_section}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; // The help_usage method replaces util name (the first word) with {}. // And, The format_usage method replaces {} with execution_phrase ( e.g. test or [ ). diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index f7ab937253b..35232d3e433 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -17,16 +17,15 @@ use uucore::error::{UClapError, UResult, USimpleError, UUsageError}; use uucore::parser::parse_time; use uucore::process::ChildExt; +use uucore::locale::get_message; #[cfg(unix)] use uucore::signals::enable_pipe_errors; use uucore::{ - format_usage, help_about, help_usage, show_error, + format_usage, show_error, signals::{signal_by_name_or_value, signal_name_by_value}, }; -use uucore::locale::{self, get_message}; - pub mod options { pub static FOREGROUND: &str = "foreground"; pub static KILL_AFTER: &str = "kill-after"; diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 22f546496ee..c12c50530de 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -23,9 +23,10 @@ use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; use crate::error::TouchError; +use uucore::locale::get_message; /// Options contains all the possible behaviors and flags for touch. /// @@ -83,8 +84,6 @@ pub enum Source { Now, } -use uucore::locale::{self, get_message}; - pub mod options { // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. pub static SOURCES: &str = "sources"; diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 3e3cc61cb52..5259990bf69 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -18,9 +18,9 @@ use std::io::{BufWriter, Write, stdin, stdout}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; -use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show}; +use uucore::{format_usage, os_str_as_bytes, show}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const COMPLEMENT: &str = "complement"; diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 89a302a459d..4036024a446 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -5,9 +5,8 @@ use clap::{Arg, ArgAction, Command}; use std::{ffi::OsString, io::Write}; use uucore::error::{UResult, set_exit_code}; -use uucore::help_about; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index ccf9c642ed4..461ed8057f8 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -12,8 +12,9 @@ use std::os::unix::fs::FileTypeExt; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; +use uucore::format_usage; +use uucore::locale::get_message; use uucore::parser::parse_size::{ParseSizeError, parse_size_u64}; -use uucore::{format_usage, help_about, help_section, help_usage}; #[derive(Debug, Eq, PartialEq)] enum TruncateMode { @@ -71,8 +72,6 @@ impl TruncateMode { } } -use uucore::locale::{self, get_message}; - pub mod options { pub static IO_BLOCKS: &str = "io-blocks"; pub static NO_CREATE: &str = "no-create"; diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index ce8ee050ff8..3a3a3c51c5a 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -9,9 +9,9 @@ use std::path::Path; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{UError, UResult}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const FILE: &str = "file"; diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 1673ba389b7..7cad08ba8f2 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -10,9 +10,9 @@ use clap::{Arg, ArgAction, Command}; use std::io::{IsTerminal, Write}; use uucore::error::{UResult, set_exit_code}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; mod options { pub const SILENT: &str = "silent"; diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 7c43049e733..cc3a4f471eb 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -7,13 +7,12 @@ use clap::{Arg, ArgAction, Command}; use platform_info::*; +use uucore::locale::get_message; use uucore::{ error::{UResult, USimpleError}, - format_usage, help_about, help_usage, + format_usage, }; -use uucore::locale::{self, get_message}; - pub mod options { pub static ALL: &str = "all"; pub static KERNEL_NAME: &str = "kernel-name"; diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 12ea29d8403..1b075dd2f2c 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -15,9 +15,9 @@ use thiserror::Error; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage, show}; +use uucore::{format_usage, show}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; const DEFAULT_TABSTOP: usize = 8; diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 9147b3392f3..00ca6751beb 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -13,11 +13,11 @@ use std::io::{BufRead, BufReader, BufWriter, Write, stdin, stdout}; use std::num::IntErrorKind; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::format_usage; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::posix::{OBSOLETE, posix_version}; -use uucore::{format_usage, help_about, help_section, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static ALL_REPEATED: &str = "all-repeated"; diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 311a8482bdf..418e59b6e8b 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -12,9 +12,9 @@ use clap::{Arg, Command}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; static OPT_PATH: &str = "FILE"; #[uucore::main] diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 25743af7849..081f18cab68 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -16,7 +16,7 @@ use uucore::uptime::*; use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] @@ -31,7 +31,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; pub mod options { pub static SINCE: &str = "since"; diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index ff0f4190e84..0af2f390b46 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -11,7 +11,7 @@ use std::path::Path; use clap::builder::ValueParser; use clap::{Arg, Command}; use uucore::error::UResult; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; #[cfg(target_os = "openbsd")] use utmp_classic::{UtmpEntry, parse_from_path}; @@ -26,7 +26,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[cfg(target_os = "openbsd")] const OPENBSD_UTMP_FILE: &str = "/var/run/utmp"; diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index f10c74d8254..f3414bb43e9 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -24,10 +24,11 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser}; use thiserror::Error; use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; +use uucore::locale::get_message; use uucore::{ error::{FromIo, UError, UResult}, - format_usage, help_about, help_usage, + format_usage, parser::shortcut_value_parser::ShortcutValueParser, quoting_style::{self, QuotingStyle}, show, @@ -113,8 +114,6 @@ impl<'a> Settings<'a> { } } -use uucore::locale::{self, get_message}; - mod options { pub static BYTES: &str = "bytes"; pub static CHAR: &str = "chars"; diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 9fb6d1c195f..00e90ebce3d 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -6,7 +6,7 @@ // spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr use clap::{Arg, ArgAction, Command}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::format_usage; mod platform; @@ -37,7 +37,7 @@ const ABOUT: &str = concat!( ); #[cfg(not(target_env = "musl"))] -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[cfg(target_os = "linux")] static RUNLEVEL_HELP: &str = "print current runlevel"; diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 4e1cc795c88..701d3a107ea 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -9,12 +9,10 @@ use clap::Command; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::locale::get_message; mod platform; -use uucore::locale::{self, get_message}; - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().try_get_matches_from(args)?; diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 33e922c7699..add3f70276d 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -10,11 +10,11 @@ use std::error::Error; use std::ffi::OsString; use std::io::{self, Write}; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; #[cfg(unix)] use uucore::signals::enable_pipe_errors; -use uucore::{format_usage, help_about, help_usage}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; // it's possible that using a smaller or larger buffer might provide better performance on some // systems, but honestly this is good enough From 1a80fc59a376cd98e4a00fc0904335059d306b55 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 18:02:45 +0200 Subject: [PATCH 113/139] l10n: init the locale engine for each program (next: put it behing a feature) --- src/bin/coreutils.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index b29e7ea2337..0332d29a083 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -14,6 +14,7 @@ use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process; use uucore::display::Quotable; +use uucore::locale; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -111,6 +112,9 @@ fn main() { match utils.get(util) { Some(&(uumain, _)) => { + if let Err(e) = locale::setup_localization(uucore::util_name()) { + println!("Could not init the localization system {}", e); + } process::exit(uumain(vec![util_os].into_iter().chain(args))); } None => { @@ -123,6 +127,7 @@ fn main() { match utils.get(util) { Some(&(uumain, _)) => { + let code = uumain( vec![util_os, OsString::from("--help")] .into_iter() From 5ab807a561180779644686870457c6732b9f18ea Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 18:03:48 +0200 Subject: [PATCH 114/139] l10n: disable the arch locale init --- src/uu/arch/src/arch.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 82e9bb79e5c..aad79b430d1 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -7,11 +7,10 @@ use platform_info::*; use clap::Command; use uucore::error::{UResult, USimpleError}; -use uucore::locale::{self, get_message}; +use uucore::locale::get_message; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - locale::setup_localization(uucore::util_name())?; uu_app().try_get_matches_from(args)?; let uts = From 16afa021ce64407f567cf998856c31e8d74a28c8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 18:10:58 +0200 Subject: [PATCH 115/139] l10n: add a comment to detail the need of having the capability to disable translations --- src/bin/coreutils.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 0332d29a083..384ac9876fc 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -112,8 +112,25 @@ fn main() { match utils.get(util) { Some(&(uumain, _)) => { - if let Err(e) = locale::setup_localization(uucore::util_name()) { - println!("Could not init the localization system {}", e); + // TODO: plug the deactivation of the translation + // and load the English strings directly at compilation time in the + // binary to avoid the load of the flt + // Could be something like: + // #[cfg(not(feature = "only_english"))] + match locale::setup_localization(get_canonical_util_name(util)) { + Ok(()) => {} + Err(uucore::locale::LocalizationError::ParseResource { + error: err_msg, + snippet, + }) => { + // Now you have both `err_msg: ParserError` and `snippet: String` + eprintln!("Localization parse error at “{}”: {:?}", snippet, err_msg); + process::exit(1); + } + Err(other) => { + eprintln!("Could not init the localization system: {}", other); + process::exit(1); + } } process::exit(uumain(vec![util_os].into_iter().chain(args))); } From 7102e1a4b5ef02bfaacad5d48cdbf945283690fe Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 31 May 2025 18:17:57 +0200 Subject: [PATCH 116/139] l10n: remove the old md files --- src/uu/base32/base32.md | 14 -- src/uu/base64/base64.md | 14 -- src/uu/basename/basename.md | 9 -- src/uu/basenc/basenc.md | 12 -- src/uu/cat/cat.md | 8 - src/uu/chcon/chcon.md | 11 -- src/uu/chgrp/chgrp.md | 10 -- src/uu/chmod/chmod.md | 16 -- src/uu/chown/chown.md | 9 -- src/uu/chroot/chroot.md | 8 - src/uu/cksum/cksum.md | 24 --- src/uu/comm/comm.md | 13 -- src/uu/cp/cp.md | 25 ---- src/uu/csplit/csplit.md | 11 -- src/uu/cut/cut.md | 112 -------------- src/uu/date/date-usage.md | 81 ---------- src/uu/date/date.md | 10 -- src/uu/dd/dd.md | 126 ---------------- src/uu/df/df.md | 18 --- src/uu/dircolors/dircolors.md | 13 -- src/uu/dirname/dirname.md | 12 -- src/uu/du/du.md | 24 --- src/uu/echo/echo.md | 26 ---- src/uu/env/env.md | 13 -- src/uu/expand/expand.md | 8 - src/uu/expr/expr.md | 53 ------- src/uu/factor/factor.md | 8 - src/uu/false/false.md | 11 -- src/uu/fmt/fmt.md | 7 - src/uu/fold/fold.md | 8 - src/uu/groups/groups.md | 8 - src/uu/hashsum/hashsum.md | 7 - src/uu/head/head.md | 11 -- src/uu/hostid/hostid.md | 7 - src/uu/hostname/hostname.md | 7 - src/uu/id/id.md | 18 --- src/uu/install/install.md | 8 - src/uu/join/join.md | 10 -- src/uu/kill/kill.md | 7 - src/uu/link/link.md | 7 - src/uu/ln/ln.md | 21 --- src/uu/logname/logname.md | 7 - src/uu/ls/ls.md | 12 -- src/uu/mkdir/mkdir.md | 13 -- src/uu/mkfifo/mkfifo.md | 7 - src/uu/mknod/mknod.md | 25 ---- src/uu/mktemp/mktemp.md | 7 - src/uu/more/more.md | 8 - src/uu/mv/mv.md | 23 --- src/uu/nice/nice.md | 10 -- src/uu/nl/nl.md | 23 --- src/uu/nohup/nohup.md | 15 -- src/uu/nproc/nproc.md | 9 -- src/uu/numfmt/numfmt.md | 46 ------ src/uu/od/od.md | 49 ------ src/uu/paste/paste.md | 8 - src/uu/pathchk/pathchk.md | 7 - src/uu/pinky/pinky.md | 7 - src/uu/pr/pr.md | 27 ---- src/uu/printenv/printenv.md | 7 - src/uu/printf/printf.md | 273 ---------------------------------- src/uu/ptx/ptx.md | 11 -- src/uu/pwd/pwd.md | 7 - src/uu/readlink/readlink.md | 7 - src/uu/realpath/realpath.md | 7 - src/uu/rm/rm.md | 22 --- src/uu/rmdir/rmdir.md | 7 - src/uu/runcon/runcon.md | 18 --- src/uu/seq/seq.md | 9 -- src/uu/shred/shred.md | 47 ------ src/uu/shuf/shuf.md | 11 -- src/uu/sleep/sleep.md | 16 -- src/uu/sort/sort.md | 21 --- src/uu/split/split.md | 26 ---- src/uu/stat/stat.md | 61 -------- src/uu/stdbuf/stdbuf.md | 24 --- src/uu/stty/stty.md | 9 -- src/uu/sum/sum.md | 9 -- src/uu/sync/sync.md | 7 - src/uu/tac/tac.md | 7 - src/uu/tail/tail.md | 11 -- src/uu/tee/tee.md | 11 -- src/uu/test/test.md | 79 ---------- src/uu/timeout/timeout.md | 7 - src/uu/touch/touch.md | 7 - src/uu/tr/tr.md | 11 -- src/uu/true/true.md | 11 -- src/uu/truncate/truncate.md | 26 ---- src/uu/tsort/tsort.md | 10 -- src/uu/tty/tty.md | 7 - src/uu/uname/uname.md | 8 - src/uu/unexpand/unexpand.md | 8 - src/uu/uniq/uniq.md | 15 -- src/uu/unlink/unlink.md | 8 - src/uu/uptime/uptime.md | 9 -- src/uu/users/users.md | 7 - src/uu/vdir/vdir.md | 10 -- src/uu/wc/wc.md | 8 - src/uu/who/who.md | 8 - src/uu/whoami/whoami.md | 7 - src/uu/yes/yes.md | 7 - 101 files changed, 2009 deletions(-) delete mode 100644 src/uu/base32/base32.md delete mode 100644 src/uu/base64/base64.md delete mode 100644 src/uu/basename/basename.md delete mode 100644 src/uu/basenc/basenc.md delete mode 100644 src/uu/cat/cat.md delete mode 100644 src/uu/chcon/chcon.md delete mode 100644 src/uu/chgrp/chgrp.md delete mode 100644 src/uu/chmod/chmod.md delete mode 100644 src/uu/chown/chown.md delete mode 100644 src/uu/chroot/chroot.md delete mode 100644 src/uu/cksum/cksum.md delete mode 100644 src/uu/comm/comm.md delete mode 100644 src/uu/cp/cp.md delete mode 100644 src/uu/csplit/csplit.md delete mode 100644 src/uu/cut/cut.md delete mode 100644 src/uu/date/date-usage.md delete mode 100644 src/uu/date/date.md delete mode 100644 src/uu/dd/dd.md delete mode 100644 src/uu/df/df.md delete mode 100644 src/uu/dircolors/dircolors.md delete mode 100644 src/uu/dirname/dirname.md delete mode 100644 src/uu/du/du.md delete mode 100644 src/uu/echo/echo.md delete mode 100644 src/uu/env/env.md delete mode 100644 src/uu/expand/expand.md delete mode 100644 src/uu/expr/expr.md delete mode 100644 src/uu/factor/factor.md delete mode 100644 src/uu/false/false.md delete mode 100644 src/uu/fmt/fmt.md delete mode 100644 src/uu/fold/fold.md delete mode 100644 src/uu/groups/groups.md delete mode 100644 src/uu/hashsum/hashsum.md delete mode 100644 src/uu/head/head.md delete mode 100644 src/uu/hostid/hostid.md delete mode 100644 src/uu/hostname/hostname.md delete mode 100644 src/uu/id/id.md delete mode 100644 src/uu/install/install.md delete mode 100644 src/uu/join/join.md delete mode 100644 src/uu/kill/kill.md delete mode 100644 src/uu/link/link.md delete mode 100644 src/uu/ln/ln.md delete mode 100644 src/uu/logname/logname.md delete mode 100644 src/uu/ls/ls.md delete mode 100644 src/uu/mkdir/mkdir.md delete mode 100644 src/uu/mkfifo/mkfifo.md delete mode 100644 src/uu/mknod/mknod.md delete mode 100644 src/uu/mktemp/mktemp.md delete mode 100644 src/uu/more/more.md delete mode 100644 src/uu/mv/mv.md delete mode 100644 src/uu/nice/nice.md delete mode 100644 src/uu/nl/nl.md delete mode 100644 src/uu/nohup/nohup.md delete mode 100644 src/uu/nproc/nproc.md delete mode 100644 src/uu/numfmt/numfmt.md delete mode 100644 src/uu/od/od.md delete mode 100644 src/uu/paste/paste.md delete mode 100644 src/uu/pathchk/pathchk.md delete mode 100644 src/uu/pinky/pinky.md delete mode 100644 src/uu/pr/pr.md delete mode 100644 src/uu/printenv/printenv.md delete mode 100644 src/uu/printf/printf.md delete mode 100644 src/uu/ptx/ptx.md delete mode 100644 src/uu/pwd/pwd.md delete mode 100644 src/uu/readlink/readlink.md delete mode 100644 src/uu/realpath/realpath.md delete mode 100644 src/uu/rm/rm.md delete mode 100644 src/uu/rmdir/rmdir.md delete mode 100644 src/uu/runcon/runcon.md delete mode 100644 src/uu/seq/seq.md delete mode 100644 src/uu/shred/shred.md delete mode 100644 src/uu/shuf/shuf.md delete mode 100644 src/uu/sleep/sleep.md delete mode 100644 src/uu/sort/sort.md delete mode 100644 src/uu/split/split.md delete mode 100644 src/uu/stat/stat.md delete mode 100644 src/uu/stdbuf/stdbuf.md delete mode 100644 src/uu/stty/stty.md delete mode 100644 src/uu/sum/sum.md delete mode 100644 src/uu/sync/sync.md delete mode 100644 src/uu/tac/tac.md delete mode 100644 src/uu/tail/tail.md delete mode 100644 src/uu/tee/tee.md delete mode 100644 src/uu/test/test.md delete mode 100644 src/uu/timeout/timeout.md delete mode 100644 src/uu/touch/touch.md delete mode 100644 src/uu/tr/tr.md delete mode 100644 src/uu/true/true.md delete mode 100644 src/uu/truncate/truncate.md delete mode 100644 src/uu/tsort/tsort.md delete mode 100644 src/uu/tty/tty.md delete mode 100644 src/uu/uname/uname.md delete mode 100644 src/uu/unexpand/unexpand.md delete mode 100644 src/uu/uniq/uniq.md delete mode 100644 src/uu/unlink/unlink.md delete mode 100644 src/uu/uptime/uptime.md delete mode 100644 src/uu/users/users.md delete mode 100644 src/uu/vdir/vdir.md delete mode 100644 src/uu/wc/wc.md delete mode 100644 src/uu/who/who.md delete mode 100644 src/uu/whoami/whoami.md delete mode 100644 src/uu/yes/yes.md diff --git a/src/uu/base32/base32.md b/src/uu/base32/base32.md deleted file mode 100644 index 1805d433bd3..00000000000 --- a/src/uu/base32/base32.md +++ /dev/null @@ -1,14 +0,0 @@ -# base32 - -``` -base32 [OPTION]... [FILE] -``` - -encode/decode data and print to standard output -With no FILE, or when FILE is -, read standard input. - -The data are encoded as described for the base32 alphabet in RFC 4648. -When decoding, the input may contain newlines in addition -to the bytes of the formal base32 alphabet. Use --ignore-garbage -to attempt to recover from any other non-alphabet bytes in the -encoded stream. diff --git a/src/uu/base64/base64.md b/src/uu/base64/base64.md deleted file mode 100644 index ed3aa4f7638..00000000000 --- a/src/uu/base64/base64.md +++ /dev/null @@ -1,14 +0,0 @@ -# base64 - -``` -base64 [OPTION]... [FILE] -``` - -encode/decode data and print to standard output -With no FILE, or when FILE is -, read standard input. - -The data are encoded as described for the base64 alphabet in RFC 3548. -When decoding, the input may contain newlines in addition -to the bytes of the formal base64 alphabet. Use --ignore-garbage -to attempt to recover from any other non-alphabet bytes in the -encoded stream. diff --git a/src/uu/basename/basename.md b/src/uu/basename/basename.md deleted file mode 100644 index ee87fa76d4e..00000000000 --- a/src/uu/basename/basename.md +++ /dev/null @@ -1,9 +0,0 @@ -# basename - -``` -basename [-z] NAME [SUFFIX] -basename OPTION... NAME... -``` - -Print NAME with any leading directory components removed -If specified, also remove a trailing SUFFIX diff --git a/src/uu/basenc/basenc.md b/src/uu/basenc/basenc.md deleted file mode 100644 index 001babe9e6b..00000000000 --- a/src/uu/basenc/basenc.md +++ /dev/null @@ -1,12 +0,0 @@ -# basenc - -``` -basenc [OPTION]... [FILE] -``` - -Encode/decode data and print to standard output -With no FILE, or when FILE is -, read standard input. - -When decoding, the input may contain newlines in addition to the bytes of -the formal alphabet. Use --ignore-garbage to attempt to recover -from any other non-alphabet bytes in the encoded stream. diff --git a/src/uu/cat/cat.md b/src/uu/cat/cat.md deleted file mode 100644 index efcd317eb41..00000000000 --- a/src/uu/cat/cat.md +++ /dev/null @@ -1,8 +0,0 @@ -# cat - -``` -cat [OPTION]... [FILE]... -``` - -Concatenate FILE(s), or standard input, to standard output -With no FILE, or when FILE is -, read standard input. diff --git a/src/uu/chcon/chcon.md b/src/uu/chcon/chcon.md deleted file mode 100644 index 64c64f47be7..00000000000 --- a/src/uu/chcon/chcon.md +++ /dev/null @@ -1,11 +0,0 @@ - -# chcon - -``` -chcon [OPTION]... CONTEXT FILE... -chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... -chcon [OPTION]... --reference=RFILE FILE... -``` - -Change the SELinux security context of each FILE to CONTEXT. -With --reference, change the security context of each FILE to that of RFILE. diff --git a/src/uu/chgrp/chgrp.md b/src/uu/chgrp/chgrp.md deleted file mode 100644 index 79bc068d2d3..00000000000 --- a/src/uu/chgrp/chgrp.md +++ /dev/null @@ -1,10 +0,0 @@ - - -# chgrp - -``` -chgrp [OPTION]... GROUP FILE... -chgrp [OPTION]... --reference=RFILE FILE... -``` - -Change the group of each FILE to GROUP. diff --git a/src/uu/chmod/chmod.md b/src/uu/chmod/chmod.md deleted file mode 100644 index 10ddb48a2ed..00000000000 --- a/src/uu/chmod/chmod.md +++ /dev/null @@ -1,16 +0,0 @@ - - -# chmod - -``` -chmod [OPTION]... MODE[,MODE]... FILE... -chmod [OPTION]... OCTAL-MODE FILE... -chmod [OPTION]... --reference=RFILE FILE... -``` - -Change the mode of each FILE to MODE. -With --reference, change the mode of each FILE to that of RFILE. - -## After Help - -Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+`. diff --git a/src/uu/chown/chown.md b/src/uu/chown/chown.md deleted file mode 100644 index 83101c74c73..00000000000 --- a/src/uu/chown/chown.md +++ /dev/null @@ -1,9 +0,0 @@ - -# chown - -``` -chown [OPTION]... [OWNER][:[GROUP]] FILE... -chown [OPTION]... --reference=RFILE FILE... -``` - -Change file owner and group diff --git a/src/uu/chroot/chroot.md b/src/uu/chroot/chroot.md deleted file mode 100644 index 3967d08f963..00000000000 --- a/src/uu/chroot/chroot.md +++ /dev/null @@ -1,8 +0,0 @@ - -# chroot - -``` -chroot [OPTION]... NEWROOT [COMMAND [ARG]...] -``` - -Run COMMAND with root directory set to NEWROOT. diff --git a/src/uu/cksum/cksum.md b/src/uu/cksum/cksum.md deleted file mode 100644 index 5ca83b40150..00000000000 --- a/src/uu/cksum/cksum.md +++ /dev/null @@ -1,24 +0,0 @@ -# cksum - -``` -cksum [OPTIONS] [FILE]... -``` - -Print CRC and size for each file - -## After Help - -DIGEST determines the digest algorithm and default output format: - -- `sysv`: (equivalent to sum -s) -- `bsd`: (equivalent to sum -r) -- `crc`: (equivalent to cksum) -- `crc32b`: (only available through cksum) -- `md5`: (equivalent to md5sum) -- `sha1`: (equivalent to sha1sum) -- `sha224`: (equivalent to sha224sum) -- `sha256`: (equivalent to sha256sum) -- `sha384`: (equivalent to sha384sum) -- `sha512`: (equivalent to sha512sum) -- `blake2b`: (equivalent to b2sum) -- `sm3`: (only available through cksum) diff --git a/src/uu/comm/comm.md b/src/uu/comm/comm.md deleted file mode 100644 index 91f4467d443..00000000000 --- a/src/uu/comm/comm.md +++ /dev/null @@ -1,13 +0,0 @@ -# comm - -``` -comm [OPTION]... FILE1 FILE2 -``` - -Compare two sorted files line by line. - -When FILE1 or FILE2 (not both) is -, read standard input. - -With no options, produce three-column output. Column one contains -lines unique to FILE1, column two contains lines unique to FILE2, -and column three contains lines common to both files. diff --git a/src/uu/cp/cp.md b/src/uu/cp/cp.md deleted file mode 100644 index 7485340f2ac..00000000000 --- a/src/uu/cp/cp.md +++ /dev/null @@ -1,25 +0,0 @@ -# cp - -``` -cp [OPTION]... [-T] SOURCE DEST -cp [OPTION]... SOURCE... DIRECTORY -cp [OPTION]... -t DIRECTORY SOURCE... -``` - -Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY. - -## After Help - -Do not copy a non-directory that has an existing destination with the same or newer modification timestamp; -instead, silently skip the file without failing. If timestamps are being preserved, the comparison is to the -source timestamp truncated to the resolutions of the destination file system and of the system calls used to -update timestamps; this avoids duplicate work if several `cp -pu` commands are executed with the same source -and destination. This option is ignored if the `-n` or `--no-clobber` option is also specified. Also, if -`--preserve=links` is also specified (like with `cp -au` for example), that will take precedence; consequently, -depending on the order that files are processed from the source, newer files in the destination may be replaced, -to mirror hard links in the source. which gives more control over which existing files in the destination are -replaced, and its value can be one of the following: - -* `all` This is the default operation when an `--update` option is not specified, and results in all existing files in the destination being replaced. -* `none` This is similar to the `--no-clobber` option, in that no files in the destination are replaced, but also skipping a file does not induce a failure. -* `older` This is the default operation when `--update` is specified, and results in files being replaced if they’re older than the corresponding source file. diff --git a/src/uu/csplit/csplit.md b/src/uu/csplit/csplit.md deleted file mode 100644 index 1d428fc8ee6..00000000000 --- a/src/uu/csplit/csplit.md +++ /dev/null @@ -1,11 +0,0 @@ -# csplit - -``` -csplit [OPTION]... FILE PATTERN... -``` - -Split a file into sections determined by context lines - -## After Help - -Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output. diff --git a/src/uu/cut/cut.md b/src/uu/cut/cut.md deleted file mode 100644 index 5c21d23dcf9..00000000000 --- a/src/uu/cut/cut.md +++ /dev/null @@ -1,112 +0,0 @@ -# cut - - - -``` -cut OPTION... [FILE]... -``` - -Prints specified byte or field columns from each line of stdin or the input files - -## After Help - -Each call must specify a mode (what to use for columns), -a sequence (which columns to print), and provide a data source - -### Specifying a mode - -Use `--bytes` (`-b`) or `--characters` (`-c`) to specify byte mode - -Use `--fields` (`-f`) to specify field mode, where each line is broken into -fields identified by a delimiter character. For example for a typical CSV -you could use this in combination with setting comma as the delimiter - -### Specifying a sequence - -A sequence is a group of 1 or more numbers or inclusive ranges separated -by a commas. - -``` -cut -f 2,5-7 some_file.txt -``` - -will display the 2nd, 5th, 6th, and 7th field for each source line - -Ranges can extend to the end of the row by excluding the second number - -``` -cut -f 3- some_file.txt -``` - -will display the 3rd field and all fields after for each source line - -The first number of a range can be excluded, and this is effectively the -same as using 1 as the first number: it causes the range to begin at the -first column. Ranges can also display a single column - -``` -cut -f 1,3-5 some_file.txt -``` - -will display the 1st, 3rd, 4th, and 5th field for each source line - -The `--complement` option, when used, inverts the effect of the sequence - -``` -cut --complement -f 4-6 some_file.txt -``` - -will display the every field but the 4th, 5th, and 6th - -### Specifying a data source - -If no `sourcefile` arguments are specified, stdin is used as the source of -lines to print - -If `sourcefile` arguments are specified, stdin is ignored and all files are -read in consecutively if a `sourcefile` is not successfully read, a warning -will print to stderr, and the eventual status code will be 1, but cut -will continue to read through proceeding `sourcefiles` - -To print columns from both STDIN and a file argument, use `-` (dash) as a -`sourcefile` argument to represent stdin. - -### Field Mode options - -The fields in each line are identified by a delimiter (separator) - -#### Set the delimiter - -Set the delimiter which separates fields in the file using the -`--delimiter` (`-d`) option. Setting the delimiter is optional. -If not set, a default delimiter of Tab will be used. - -If the `-w` option is provided, fields will be separated by any number -of whitespace characters (Space and Tab). The output delimiter will -be a Tab unless explicitly specified. Only one of `-d` or `-w` option can be specified. -This is an extension adopted from FreeBSD. - -#### Optionally Filter based on delimiter - -If the `--only-delimited` (`-s`) flag is provided, only lines which -contain the delimiter will be printed - -#### Replace the delimiter - -If the `--output-delimiter` option is provided, the argument used for -it will replace the delimiter character in each line printed. This is -useful for transforming tabular data - e.g. to convert a CSV to a -TSV (tab-separated file) - -### Line endings - -When the `--zero-terminated` (`-z`) option is used, cut sees \\0 (null) as the -'line ending' character (both for the purposes of reading lines and -separating printed lines) instead of \\n (newline). This is useful for -tabular data where some of the cells may contain newlines - -``` -echo 'ab\\0cd' | cut -z -c 1 -``` - -will result in 'a\\0c\\0' diff --git a/src/uu/date/date-usage.md b/src/uu/date/date-usage.md deleted file mode 100644 index 109bfd3988b..00000000000 --- a/src/uu/date/date-usage.md +++ /dev/null @@ -1,81 +0,0 @@ -# `date` usage - - - -FORMAT controls the output. Interpreted sequences are: - -| Sequence | Description | Example | -| -------- | -------------------------------------------------------------------- | ---------------------- | -| %% | a literal % | % | -| %a | locale's abbreviated weekday name | Sun | -| %A | locale's full weekday name | Sunday | -| %b | locale's abbreviated month name | Jan | -| %B | locale's full month name | January | -| %c | locale's date and time | Thu Mar 3 23:05:25 2005| -| %C | century; like %Y, except omit last two digits | 20 | -| %d | day of month | 01 | -| %D | date; same as %m/%d/%y | 12/31/99 | -| %e | day of month, space padded; same as %_d | 3 | -| %F | full date; same as %Y-%m-%d | 2005-03-03 | -| %g | last two digits of year of ISO week number (see %G) | 05 | -| %G | year of ISO week number (see %V); normally useful only with %V | 2005 | -| %h | same as %b | Jan | -| %H | hour (00..23) | 23 | -| %I | hour (01..12) | 11 | -| %j | day of year (001..366) | 062 | -| %k | hour, space padded ( 0..23); same as %_H | 3 | -| %l | hour, space padded ( 1..12); same as %_I | 9 | -| %m | month (01..12) | 03 | -| %M | minute (00..59) | 30 | -| %n | a newline | \n | -| %N | nanoseconds (000000000..999999999) | 123456789 | -| %p | locale's equivalent of either AM or PM; blank if not known | PM | -| %P | like %p, but lower case | pm | -| %q | quarter of year (1..4) | 1 | -| %r | locale's 12-hour clock time | 11:11:04 PM | -| %R | 24-hour hour and minute; same as %H:%M | 23:30 | -| %s | seconds since 1970-01-01 00:00:00 UTC | 1615432800 | -| %S | second (00..60) | 30 | -| %t | a tab | \t | -| %T | time; same as %H:%M:%S | 23:30:30 | -| %u | day of week (1..7); 1 is Monday | 4 | -| %U | week number of year, with Sunday as first day of week (00..53) | 10 | -| %V | ISO week number, with Monday as first day of week (01..53) | 12 | -| %w | day of week (0..6); 0 is Sunday | 4 | -| %W | week number of year, with Monday as first day of week (00..53) | 11 | -| %x | locale's date representation | 03/03/2005 | -| %X | locale's time representation | 23:30:30 | -| %y | last two digits of year (00..99) | 05 | -| %Y | year | 2005 | -| %z | +hhmm numeric time zone | -0400 | -| %:z | +hh:mm numeric time zone | -04:00 | -| %::z | +hh:mm:ss numeric time zone | -04:00:00 | -| %:::z | numeric time zone with : to necessary precision | -04, +05:30 | -| %Z | alphabetic time zone abbreviation | EDT | - -By default, date pads numeric fields with zeroes. -The following optional flags may follow '%': - -* `-` (hyphen) do not pad the field -* `_` (underscore) pad with spaces -* `0` (zero) pad with zeros -* `^` use upper case if possible -* `#` use opposite case if possible - -After any flags comes an optional field width, as a decimal number; -then an optional modifier, which is either -E to use the locale's alternate representations if available, or -O to use the locale's alternate numeric symbols if available. - -Examples: -Convert seconds since the epoch (1970-01-01 UTC) to a date - -``` -date --date='@2147483647' -``` - -Show the time on the west coast of the US (use tzselect(1) to find TZ) - -``` -TZ='America/Los_Angeles' date -``` diff --git a/src/uu/date/date.md b/src/uu/date/date.md deleted file mode 100644 index 97f1340169b..00000000000 --- a/src/uu/date/date.md +++ /dev/null @@ -1,10 +0,0 @@ - - -# date - -``` -date [OPTION]... [+FORMAT]... -date [OPTION]... [MMDDhhmm[[CC]YY][.ss]] -``` - -Print or set the system date and time diff --git a/src/uu/dd/dd.md b/src/uu/dd/dd.md deleted file mode 100644 index 504910884c2..00000000000 --- a/src/uu/dd/dd.md +++ /dev/null @@ -1,126 +0,0 @@ -# dd - - - -``` -dd [OPERAND]... -dd OPTION -``` - -Copy, and optionally convert, a file system resource - -## After Help - -### Operands - -- `bs=BYTES` : read and write up to BYTES bytes at a time (default: 512); - overwrites `ibs` and `obs`. -- `cbs=BYTES` : the 'conversion block size' in bytes. Applies to the - `conv=block`, and `conv=unblock` operations. -- `conv=CONVS` : a comma-separated list of conversion options or (for legacy - reasons) file flags. -- `count=N` : stop reading input after N ibs-sized read operations rather - than proceeding until EOF. See `iflag=count_bytes` if stopping after N bytes - is preferred -- `ibs=N` : the size of buffer used for reads (default: 512) -- `if=FILE` : the file used for input. When not specified, stdin is used instead -- `iflag=FLAGS` : a comma-separated list of input flags which specify how the - input source is treated. FLAGS may be any of the input-flags or general-flags - specified below. -- `skip=N` (or `iseek=N`) : skip N ibs-sized records into input before beginning - copy/convert operations. See iflag=seek_bytes if seeking N bytes is preferred. -- `obs=N` : the size of buffer used for writes (default: 512) -- `of=FILE` : the file used for output. When not specified, stdout is used - instead -- `oflag=FLAGS` : comma separated list of output flags which specify how the - output source is treated. FLAGS may be any of the output flags or general - flags specified below -- `seek=N` (or `oseek=N`) : seeks N obs-sized records into output before - beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is - preferred -- `status=LEVEL` : controls whether volume and performance stats are written to - stderr. - - When unspecified, dd will print stats upon completion. An example is below. - - ```plain - 6+0 records in - 16+0 records out - 8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s, - 14.4 MB/s - ``` - - The first two lines are the 'volume' stats and the final line is the - 'performance' stats. - The volume stats indicate the number of complete and partial ibs-sized reads, - or obs-sized writes that took place during the copy. The format of the volume - stats is `+`. If records have been truncated (see - `conv=block`), the volume stats will contain the number of truncated records. - - Possible LEVEL values are: - - `progress` : Print periodic performance stats as the copy proceeds. - - `noxfer` : Print final volume stats, but not performance stats. - - `none` : Do not print any stats. - - Printing performance stats is also triggered by the INFO signal (where supported), - or the USR1 signal. Setting the POSIXLY_CORRECT environment variable to any value - (including an empty value) will cause the USR1 signal to be ignored. - -### Conversion Options - -- `ascii` : convert from EBCDIC to ASCII. This is the inverse of the `ebcdic` - option. Implies `conv=unblock`. -- `ebcdic` : convert from ASCII to EBCDIC. This is the inverse of the `ascii` - option. Implies `conv=block`. -- `ibm` : convert from ASCII to EBCDIC, applying the conventions for `[`, `]` - and `~` specified in POSIX. Implies `conv=block`. - -- `ucase` : convert from lower-case to upper-case. -- `lcase` : converts from upper-case to lower-case. - -- `block` : for each newline less than the size indicated by cbs=BYTES, remove - the newline and pad with spaces up to cbs. Lines longer than cbs are truncated. -- `unblock` : for each block of input of the size indicated by cbs=BYTES, remove - right-trailing spaces and replace with a newline character. - -- `sparse` : attempts to seek the output when an obs-sized block consists of - only zeros. -- `swab` : swaps each adjacent pair of bytes. If an odd number of bytes is - present, the final byte is omitted. -- `sync` : pad each ibs-sided block with zeros. If `block` or `unblock` is - specified, pad with spaces instead. -- `excl` : the output file must be created. Fail if the output file is already - present. -- `nocreat` : the output file will not be created. Fail if the output file in - not already present. -- `notrunc` : the output file will not be truncated. If this option is not - present, output will be truncated when opened. -- `noerror` : all read errors will be ignored. If this option is not present, - dd will only ignore Error::Interrupted. -- `fdatasync` : data will be written before finishing. -- `fsync` : data and metadata will be written before finishing. - -### Input flags - -- `count_bytes` : a value to `count=N` will be interpreted as bytes. -- `skip_bytes` : a value to `skip=N` will be interpreted as bytes. -- `fullblock` : wait for ibs bytes from each read. zero-length reads are still - considered EOF. - -### Output flags - -- `append` : open file in append mode. Consider setting conv=notrunc as well. -- `seek_bytes` : a value to seek=N will be interpreted as bytes. - -### General Flags - -- `direct` : use direct I/O for data. -- `directory` : fail unless the given input (if used as an iflag) or - output (if used as an oflag) is a directory. -- `dsync` : use synchronized I/O for data. -- `sync` : use synchronized I/O for data and metadata. -- `nonblock` : use non-blocking I/O. -- `noatime` : do not update access time. -- `nocache` : request that OS drop cache. -- `noctty` : do not assign a controlling tty. -- `nofollow` : do not follow system links. diff --git a/src/uu/df/df.md b/src/uu/df/df.md deleted file mode 100644 index 1a192f8fd01..00000000000 --- a/src/uu/df/df.md +++ /dev/null @@ -1,18 +0,0 @@ -# df - -``` -df [OPTION]... [FILE]... -``` - -Show information about the file system on which each FILE resides, -or all file systems by default. - -## After Help - -Display values are in units of the first available SIZE from --block-size, -and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. -Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). - -SIZE is an integer and optional unit (example: 10M is 10*1024*1024). -Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers -of 1000). diff --git a/src/uu/dircolors/dircolors.md b/src/uu/dircolors/dircolors.md deleted file mode 100644 index 959b8fd19bc..00000000000 --- a/src/uu/dircolors/dircolors.md +++ /dev/null @@ -1,13 +0,0 @@ -# dircolors - -``` -dircolors [OPTION]... [FILE] -``` - -Output commands to set the LS_COLORS environment variable. - -## After Help - -If FILE is specified, read it to determine which colors to use for which -file types and extensions. Otherwise, a precompiled database is used. -For details on the format of these files, run 'dircolors --print-database' diff --git a/src/uu/dirname/dirname.md b/src/uu/dirname/dirname.md deleted file mode 100644 index f08c1c4c9ad..00000000000 --- a/src/uu/dirname/dirname.md +++ /dev/null @@ -1,12 +0,0 @@ -# dirname - -``` -dirname [OPTION] NAME... -``` - -Strip last component from file name - -## After Help - -Output each NAME with its last non-slash component and trailing slashes -removed; if NAME contains no /'s, output '.' (meaning the current directory). diff --git a/src/uu/du/du.md b/src/uu/du/du.md deleted file mode 100644 index ae3319ad6b6..00000000000 --- a/src/uu/du/du.md +++ /dev/null @@ -1,24 +0,0 @@ -# du - -``` -du [OPTION]... [FILE]... -du [OPTION]... --files0-from=F -``` - -Estimate file space usage - -## After Help - -Display values are in units of the first available SIZE from --block-size, -and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. -Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). - -SIZE is an integer and optional unit (example: 10M is 10*1024*1024). -Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers -of 1000). - -PATTERN allows some advanced exclusions. For example, the following syntaxes -are supported: -`?` will match only one character -`*` will match zero or more characters -`{a,b}` will match a or b diff --git a/src/uu/echo/echo.md b/src/uu/echo/echo.md deleted file mode 100644 index f6f6f37ffcd..00000000000 --- a/src/uu/echo/echo.md +++ /dev/null @@ -1,26 +0,0 @@ -# echo - -``` -echo [OPTIONS]... [STRING]... -``` - -Display a line of text - -## After Help - -Echo the STRING(s) to standard output. - -If -e is in effect, the following sequences are recognized: - -- `\` backslash -- `\a` alert (BEL) -- `\b` backspace -- `\c` produce no further output -- `\e` escape -- `\f` form feed -- `\n` new line -- `\r` carriage return -- `\t` horizontal tab -- `\v` vertical tab -- `\0NNN` byte with octal value NNN (1 to 3 digits) -- `\xHH` byte with hexadecimal value HH (1 to 2 digits) diff --git a/src/uu/env/env.md b/src/uu/env/env.md deleted file mode 100644 index fc3341a61f7..00000000000 --- a/src/uu/env/env.md +++ /dev/null @@ -1,13 +0,0 @@ -# env - -``` -env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...] -``` - - -Set each NAME to VALUE in the environment and run COMMAND - - -## After Help - -A mere - implies -i. If no COMMAND, print the resulting environment. diff --git a/src/uu/expand/expand.md b/src/uu/expand/expand.md deleted file mode 100644 index df7294c30d2..00000000000 --- a/src/uu/expand/expand.md +++ /dev/null @@ -1,8 +0,0 @@ -# expand - -``` -expand [OPTION]... [FILE]... -``` - -Convert tabs in each `FILE` to spaces, writing to standard output. -With no `FILE`, or when `FILE` is `-`, read standard input. diff --git a/src/uu/expr/expr.md b/src/uu/expr/expr.md deleted file mode 100644 index ab082465149..00000000000 --- a/src/uu/expr/expr.md +++ /dev/null @@ -1,53 +0,0 @@ -# expr - -``` -expr [EXPRESSION] -expr [OPTIONS] -``` - -Print the value of `EXPRESSION` to standard output - -## After help - -Print the value of `EXPRESSION` to standard output. A blank line below -separates increasing precedence groups. - -`EXPRESSION` may be: - -- `ARG1 | ARG2`: `ARG1` if it is neither null nor 0, otherwise `ARG2` -- `ARG1 & ARG2`: `ARG1` if neither argument is null or 0, otherwise 0 -- `ARG1 < ARG2`: `ARG1` is less than `ARG2` -- `ARG1 <= ARG2`: `ARG1` is less than or equal to `ARG2` -- `ARG1 = ARG2`: `ARG1` is equal to `ARG2` -- `ARG1 != ARG2`: `ARG1` is unequal to `ARG2` -- `ARG1 >= ARG2`: `ARG1` is greater than or equal to `ARG2` -- `ARG1 > ARG2`: `ARG1` is greater than `ARG2` -- `ARG1 + ARG2`: arithmetic sum of `ARG1` and `ARG2` -- `ARG1 - ARG2`: arithmetic difference of `ARG1` and `ARG2` -- `ARG1 * ARG2`: arithmetic product of `ARG1` and `ARG2` -- `ARG1 / ARG2`: arithmetic quotient of `ARG1` divided by `ARG2` -- `ARG1 % ARG2`: arithmetic remainder of `ARG1` divided by `ARG2` -- `STRING : REGEXP`: anchored pattern match of `REGEXP` in `STRING` -- `match STRING REGEXP`: same as `STRING : REGEXP` -- `substr STRING POS LENGTH`: substring of `STRING`, `POS` counted from 1 -- `index STRING CHARS`: index in `STRING` where any `CHARS` is found, or 0 -- `length STRING`: length of `STRING` -- `+ TOKEN`: interpret `TOKEN` as a string, even if it is a keyword like `match` - or an operator like `/` -- `( EXPRESSION )`: value of `EXPRESSION` - -Beware that many operators need to be escaped or quoted for shells. -Comparisons are arithmetic if both ARGs are numbers, else lexicographical. -Pattern matches return the string matched between \( and \) or null; if -\( and \) are not used, they return the number of characters matched or 0. - -Exit status is `0` if `EXPRESSION` is neither null nor `0`, `1` if `EXPRESSION` -is null or `0`, `2` if `EXPRESSION` is syntactically invalid, and `3` if an -error occurred. - -Environment variables: - -- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens -- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation -- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step -- `EXPR_DEBUG_AST=1`: dump expression represented abstract syntax tree diff --git a/src/uu/factor/factor.md b/src/uu/factor/factor.md deleted file mode 100644 index ccce4f2529c..00000000000 --- a/src/uu/factor/factor.md +++ /dev/null @@ -1,8 +0,0 @@ -# factor - -``` -factor [OPTION]... [NUMBER]... -``` - -Print the prime factors of the given NUMBER(s). -If none are specified, read from standard input. diff --git a/src/uu/false/false.md b/src/uu/false/false.md deleted file mode 100644 index 3f0594767f2..00000000000 --- a/src/uu/false/false.md +++ /dev/null @@ -1,11 +0,0 @@ -# false - -``` -false -``` - -Returns false, an unsuccessful exit status. - -Immediately returns with the exit status `1`. When invoked with one of the recognized options it -will try to write the help or version text. Any IO error during this operation is diagnosed, yet -the program will also return `1`. diff --git a/src/uu/fmt/fmt.md b/src/uu/fmt/fmt.md deleted file mode 100644 index 6ed7d3048a5..00000000000 --- a/src/uu/fmt/fmt.md +++ /dev/null @@ -1,7 +0,0 @@ -# fmt - -``` -fmt [-WIDTH] [OPTION]... [FILE]... -``` - -Reformat paragraphs from input files (or stdin) to stdout. diff --git a/src/uu/fold/fold.md b/src/uu/fold/fold.md deleted file mode 100644 index 13062c8587d..00000000000 --- a/src/uu/fold/fold.md +++ /dev/null @@ -1,8 +0,0 @@ -# fold - -``` -fold [OPTION]... [FILE]... -``` - -Writes each file (or standard input if no files are given) -to standard output whilst breaking long lines diff --git a/src/uu/groups/groups.md b/src/uu/groups/groups.md deleted file mode 100644 index 1d11d5e46bb..00000000000 --- a/src/uu/groups/groups.md +++ /dev/null @@ -1,8 +0,0 @@ -# groups - -``` -groups [OPTION]... [USERNAME]... -``` - -Print group memberships for each `USERNAME` or, if no `USERNAME` is specified, for -the current process (which may differ if the groups data‐base has changed). diff --git a/src/uu/hashsum/hashsum.md b/src/uu/hashsum/hashsum.md deleted file mode 100644 index 35ec840a39c..00000000000 --- a/src/uu/hashsum/hashsum.md +++ /dev/null @@ -1,7 +0,0 @@ -# hashsum - -``` -hashsum -- [OPTIONS]... [FILE]... -``` - -Compute and check message digests. diff --git a/src/uu/head/head.md b/src/uu/head/head.md deleted file mode 100644 index 476f3affc30..00000000000 --- a/src/uu/head/head.md +++ /dev/null @@ -1,11 +0,0 @@ -# head - -``` -head [FLAG]... [FILE]... -``` - -Print the first 10 lines of each `FILE` to standard output. -With more than one `FILE`, precede each with a header giving the file name. -With no `FILE`, or when `FILE` is `-`, read standard input. - -Mandatory arguments to long flags are mandatory for short flags too. diff --git a/src/uu/hostid/hostid.md b/src/uu/hostid/hostid.md deleted file mode 100644 index 940cf5e5d36..00000000000 --- a/src/uu/hostid/hostid.md +++ /dev/null @@ -1,7 +0,0 @@ -# hostid - -``` -hostid [options] -``` - -Print the numeric identifier (in hexadecimal) for the current host diff --git a/src/uu/hostname/hostname.md b/src/uu/hostname/hostname.md deleted file mode 100644 index f9e852c4b9e..00000000000 --- a/src/uu/hostname/hostname.md +++ /dev/null @@ -1,7 +0,0 @@ -# hostname - -``` -hostname [OPTION]... [HOSTNAME] -``` - -Display or set the system's host name. diff --git a/src/uu/id/id.md b/src/uu/id/id.md deleted file mode 100644 index bb7330314ab..00000000000 --- a/src/uu/id/id.md +++ /dev/null @@ -1,18 +0,0 @@ -# id - -``` -id [OPTION]... [USER]... -``` - -Print user and group information for each specified `USER`, -or (when `USER` omitted) for the current user. - -## After help - -The id utility displays the user and group names and numeric IDs, of the -calling process, to the standard output. If the real and effective IDs are -different, both are displayed, otherwise only the real ID is displayed. - -If a user (login name or user ID) is specified, the user and group IDs of -that user are displayed. In this case, the real and effective IDs are -assumed to be the same. diff --git a/src/uu/install/install.md b/src/uu/install/install.md deleted file mode 100644 index ac6a70f4219..00000000000 --- a/src/uu/install/install.md +++ /dev/null @@ -1,8 +0,0 @@ -# install - -``` -install [OPTION]... [FILE]... -``` - -Copy SOURCE to DEST or multiple SOURCE(s) to the existing -DIRECTORY, while setting permission modes and owner/group diff --git a/src/uu/join/join.md b/src/uu/join/join.md deleted file mode 100644 index 04275342a6e..00000000000 --- a/src/uu/join/join.md +++ /dev/null @@ -1,10 +0,0 @@ -# join - -``` -join [OPTION]... FILE1 FILE2 -``` - -For each pair of input lines with identical join fields, write a line to -standard output. The default join field is the first, delimited by blanks. - -When `FILE1` or `FILE2` (not both) is `-`, read standard input. diff --git a/src/uu/kill/kill.md b/src/uu/kill/kill.md deleted file mode 100644 index 1e3e2b3d094..00000000000 --- a/src/uu/kill/kill.md +++ /dev/null @@ -1,7 +0,0 @@ -# kill - -``` -kill [OPTIONS]... PID... -``` - -Send signal to processes or list information about signals. diff --git a/src/uu/link/link.md b/src/uu/link/link.md deleted file mode 100644 index ea6a531b987..00000000000 --- a/src/uu/link/link.md +++ /dev/null @@ -1,7 +0,0 @@ -# link - -``` -link FILE1 FILE2 -``` - -Call the link function to create a link named FILE2 to an existing FILE1. diff --git a/src/uu/ln/ln.md b/src/uu/ln/ln.md deleted file mode 100644 index 6bd6ee01619..00000000000 --- a/src/uu/ln/ln.md +++ /dev/null @@ -1,21 +0,0 @@ -# ln - -``` -ln [OPTION]... [-T] TARGET LINK_NAME -ln [OPTION]... TARGET -ln [OPTION]... TARGET... DIRECTORY -ln [OPTION]... -t DIRECTORY TARGET... -``` - -Make links between files. - -## After Help - -In the 1st form, create a link to TARGET with the name LINK_NAME. -In the 2nd form, create a link to TARGET in the current directory. -In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. -Create hard links by default, symbolic links with --symbolic. -By default, each destination (name of new link) should not already exist. -When creating hard links, each TARGET must exist. Symbolic links -can hold arbitrary text; if later resolved, a relative link is -interpreted in relation to its parent directory. diff --git a/src/uu/logname/logname.md b/src/uu/logname/logname.md deleted file mode 100644 index b997c206be8..00000000000 --- a/src/uu/logname/logname.md +++ /dev/null @@ -1,7 +0,0 @@ -# logname - -``` -logname -``` - -Print user's login name diff --git a/src/uu/ls/ls.md b/src/uu/ls/ls.md deleted file mode 100644 index 01eec7e5052..00000000000 --- a/src/uu/ls/ls.md +++ /dev/null @@ -1,12 +0,0 @@ -# ls - -``` -ls [OPTION]... [FILE]... -``` - -List directory contents. -Ignore files and directories starting with a '.' by default - -## After help - -The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. Also the TIME_STYLE environment variable sets the default style to use. diff --git a/src/uu/mkdir/mkdir.md b/src/uu/mkdir/mkdir.md deleted file mode 100644 index f5dbb25440b..00000000000 --- a/src/uu/mkdir/mkdir.md +++ /dev/null @@ -1,13 +0,0 @@ -# mkdir - - - -``` -mkdir [OPTION]... DIRECTORY... -``` - -Create the given DIRECTORY(ies) if they do not exist - -## After Help - -Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+`. diff --git a/src/uu/mkfifo/mkfifo.md b/src/uu/mkfifo/mkfifo.md deleted file mode 100644 index b6343782922..00000000000 --- a/src/uu/mkfifo/mkfifo.md +++ /dev/null @@ -1,7 +0,0 @@ -# mkfifo - -``` -mkfifo [OPTION]... NAME... -``` - -Create a FIFO with the given name. diff --git a/src/uu/mknod/mknod.md b/src/uu/mknod/mknod.md deleted file mode 100644 index 78da970252e..00000000000 --- a/src/uu/mknod/mknod.md +++ /dev/null @@ -1,25 +0,0 @@ -# mknod - -``` -mknod [OPTION]... NAME TYPE [MAJOR MINOR] -``` - -Create the special file NAME of the given TYPE. - -## After Help - -Mandatory arguments to long options are mandatory for short options too. -`-m`, `--mode=MODE` set file permission bits to `MODE`, not `a=rw - umask` - -Both `MAJOR` and `MINOR` must be specified when `TYPE` is `b`, `c`, or `u`, and they -must be omitted when `TYPE` is `p`. If `MAJOR` or `MINOR` begins with `0x` or `0X`, -it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; -otherwise, as decimal. `TYPE` may be: - -* `b` create a block (buffered) special file -* `c`, `u` create a character (unbuffered) special file -* `p` create a FIFO - -NOTE: your shell may have its own version of mknod, which usually supersedes -the version described here. Please refer to your shell's documentation -for details about the options it supports. diff --git a/src/uu/mktemp/mktemp.md b/src/uu/mktemp/mktemp.md deleted file mode 100644 index 75b56d66751..00000000000 --- a/src/uu/mktemp/mktemp.md +++ /dev/null @@ -1,7 +0,0 @@ -# mktemp - -``` -mktemp [OPTION]... [TEMPLATE] -``` - -Create a temporary file or directory. diff --git a/src/uu/more/more.md b/src/uu/more/more.md deleted file mode 100644 index e63ca1a87fb..00000000000 --- a/src/uu/more/more.md +++ /dev/null @@ -1,8 +0,0 @@ -# more - -``` -more [OPTIONS] FILE... -``` - -Display the contents of a text file - diff --git a/src/uu/mv/mv.md b/src/uu/mv/mv.md deleted file mode 100644 index 6fcee46973f..00000000000 --- a/src/uu/mv/mv.md +++ /dev/null @@ -1,23 +0,0 @@ -# mv - -``` -mv [OPTION]... [-T] SOURCE DEST -mv [OPTION]... SOURCE... DIRECTORY -mv [OPTION]... -t DIRECTORY SOURCE... -``` -Move `SOURCE` to `DEST`, or multiple `SOURCE`(s) to `DIRECTORY`. - -## After Help - -When specifying more than one of -i, -f, -n, only the final one will take effect. - -Do not move a non-directory that has an existing destination with the same or newer modification timestamp; -instead, silently skip the file without failing. If the move is across file system boundaries, the comparison is -to the source timestamp truncated to the resolutions of the destination file system and of the system calls used -to update timestamps; this avoids duplicate work if several `mv -u` commands are executed with the same source -and destination. This option is ignored if the `-n` or `--no-clobber` option is also specified. which gives more control -over which existing files in the destination are replaced, and its value can be one of the following: - -* `all` This is the default operation when an `--update` option is not specified, and results in all existing files in the destination being replaced. -* `none` This is similar to the `--no-clobber` option, in that no files in the destination are replaced, but also skipping a file does not induce a failure. -* `older` This is the default operation when `--update` is specified, and results in files being replaced if they’re older than the corresponding source file. diff --git a/src/uu/nice/nice.md b/src/uu/nice/nice.md deleted file mode 100644 index 872c239cab1..00000000000 --- a/src/uu/nice/nice.md +++ /dev/null @@ -1,10 +0,0 @@ -# nice - -``` -nice [OPTIONS] [COMMAND [ARGS]] -``` - -Run `COMMAND` with an adjusted niceness, which affects process scheduling. -With no `COMMAND`, print the current niceness. Niceness values range from at -least -20 (most favorable to the process) to 19 (least favorable to the -process). diff --git a/src/uu/nl/nl.md b/src/uu/nl/nl.md deleted file mode 100644 index bf3952cb2aa..00000000000 --- a/src/uu/nl/nl.md +++ /dev/null @@ -1,23 +0,0 @@ -# nl - -``` -nl [OPTION]... [FILE]... -``` - -Number lines of files - -## After Help - -`STYLE` is one of: - -* `a` number all lines -* `t` number only nonempty lines -* `n` number no lines -* `pBRE` number only lines that contain a match for the basic regular - expression, `BRE` - -`FORMAT` is one of: - -* `ln` left justified, no leading zeros -* `rn` right justified, no leading zeros -* `rz` right justified, leading zeros diff --git a/src/uu/nohup/nohup.md b/src/uu/nohup/nohup.md deleted file mode 100644 index 65ffaec873d..00000000000 --- a/src/uu/nohup/nohup.md +++ /dev/null @@ -1,15 +0,0 @@ -# nohup - -``` -nohup COMMAND [ARG]... -nohup OPTION -``` - -Run COMMAND ignoring hangup signals. - -## After Help - -If standard input is terminal, it'll be replaced with /dev/null. -If standard output is terminal, it'll be appended to nohup.out instead, -or $HOME/nohup.out, if nohup.out open failed. -If standard error is terminal, it'll be redirected to stdout. diff --git a/src/uu/nproc/nproc.md b/src/uu/nproc/nproc.md deleted file mode 100644 index fee958a92fc..00000000000 --- a/src/uu/nproc/nproc.md +++ /dev/null @@ -1,9 +0,0 @@ -# nproc - -``` -nproc [OPTIONS]... -``` - -Print the number of cores available to the current process. -If the `OMP_NUM_THREADS` or `OMP_THREAD_LIMIT` environment variables are set, then -they will determine the minimum and maximum returned value respectively. diff --git a/src/uu/numfmt/numfmt.md b/src/uu/numfmt/numfmt.md deleted file mode 100644 index 7c75736c115..00000000000 --- a/src/uu/numfmt/numfmt.md +++ /dev/null @@ -1,46 +0,0 @@ -# numfmt - - - -``` -numfmt [OPTION]... [NUMBER]... -``` - -Convert numbers from/to human-readable strings - -## After Help - -`UNIT` options: - -- `none`: no auto-scaling is done; suffixes will trigger an error -- `auto`: accept optional single/two letter suffix: - - 1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576, - -- `si`: accept optional single letter suffix: - - 1K = 1000, 1M = 1000000, ... - -- `iec`: accept optional single letter suffix: - - 1K = 1024, 1M = 1048576, ... - -- `iec-i`: accept optional two-letter suffix: - - 1Ki = 1024, 1Mi = 1048576, ... - -- `FIELDS` supports `cut(1)` style field ranges: - - N N'th field, counted from 1 - N- from N'th field, to end of line - N-M from N'th to M'th field (inclusive) - -M from first to M'th field (inclusive) - - all fields - -Multiple fields/ranges can be separated with commas - -`FORMAT` must be suitable for printing one floating-point argument `%f`. -Optional quote (`%'f`) will enable --grouping (if supported by current locale). -Optional width value (`%10f`) will pad output. Optional zero (`%010f`) width -will zero pad the number. Optional negative values (`%-10f`) will left align. -Optional precision (`%.1f`) will override the input determined precision. diff --git a/src/uu/od/od.md b/src/uu/od/od.md deleted file mode 100644 index 5cd9ac3b5e7..00000000000 --- a/src/uu/od/od.md +++ /dev/null @@ -1,49 +0,0 @@ -# od - -``` -od [OPTION]... [--] [FILENAME]... -od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] -od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]] -``` - -Dump files in octal and other formats - -## After Help - -Displays data in various human-readable formats. If multiple formats are -specified, the output will contain all formats in the order they appear on the -command line. Each format will be printed on a new line. Only the line -containing the first format will be prefixed with the offset. - -If no filename is specified, or it is "-", stdin will be used. After a "--", no -more options will be recognized. This allows for filenames starting with a "-". - -If a filename is a valid number which can be used as an offset in the second -form, you can force it to be recognized as a filename if you include an option -like "-j0", which is only valid in the first form. - -RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none. - -BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if -prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the -number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2. - -OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or -decimal if a "." suffix is added. The "b" suffix will multiply with 512. - -TYPE contains one or more format specifications consisting of: - a for printable 7-bits ASCII - c for utf-8 characters or octal for undefined characters - d[SIZE] for signed decimal - f[SIZE] for floating point - o[SIZE] for octal - u[SIZE] for unsigned decimal - x[SIZE] for hexadecimal -SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16, - or C, I, S, L for 1, 2, 4, 8 bytes for integer types, - or F, D, L for 4, 8, 16 bytes for floating point. -Any type specification can have a "z" suffix, which will add a ASCII dump at - the end of the line. - -If an error occurred, a diagnostic message will be printed to stderr, and the -exit code will be non-zero. diff --git a/src/uu/paste/paste.md b/src/uu/paste/paste.md deleted file mode 100644 index 74160588894..00000000000 --- a/src/uu/paste/paste.md +++ /dev/null @@ -1,8 +0,0 @@ -# paste - -``` -paste [OPTIONS] [FILE]... -``` - -Write lines consisting of the sequentially corresponding lines from each -`FILE`, separated by `TAB`s, to standard output. diff --git a/src/uu/pathchk/pathchk.md b/src/uu/pathchk/pathchk.md deleted file mode 100644 index 2e5eca95953..00000000000 --- a/src/uu/pathchk/pathchk.md +++ /dev/null @@ -1,7 +0,0 @@ -# pathchk - -``` -pathchk [OPTION]... NAME... -``` - -Check whether file names are valid or portable diff --git a/src/uu/pinky/pinky.md b/src/uu/pinky/pinky.md deleted file mode 100644 index 965ae4cd090..00000000000 --- a/src/uu/pinky/pinky.md +++ /dev/null @@ -1,7 +0,0 @@ -# pinky - -``` -pinky [OPTION]... [USER]... -``` - -Displays brief user information on Unix-based systems diff --git a/src/uu/pr/pr.md b/src/uu/pr/pr.md deleted file mode 100644 index cd2f552f3de..00000000000 --- a/src/uu/pr/pr.md +++ /dev/null @@ -1,27 +0,0 @@ -# pr - -``` -pr [OPTION]... [FILE]... -``` - -Write content of given file or standard input to standard output with pagination filter - -## After help - -`+PAGE` Begin output at page number page of the formatted input. -`-COLUMN` Produce multi-column output. See `--column` - -The pr utility is a printing and pagination filter for text files. -When multiple input files are specified, each is read, formatted, and written to standard output. -By default, the input is separated into 66-line pages, each with - -* A 5-line header with the page number, date, time, and the pathname of the file. -* A 5-line trailer consisting of blank lines. - -If standard output is associated with a terminal, diagnostic messages are suppressed until the pr -utility has completed processing. - -When multiple column output is specified, text columns are of equal width. -By default, text columns are separated by at least one ``. -Input lines that do not fit into a text column are truncated. -Lines are not truncated under single column output. diff --git a/src/uu/printenv/printenv.md b/src/uu/printenv/printenv.md deleted file mode 100644 index f67d46a23dd..00000000000 --- a/src/uu/printenv/printenv.md +++ /dev/null @@ -1,7 +0,0 @@ -# printenv - -``` -printenv [OPTION]... [VARIABLE]... -``` - -Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all. diff --git a/src/uu/printf/printf.md b/src/uu/printf/printf.md deleted file mode 100644 index 791cc8be1f2..00000000000 --- a/src/uu/printf/printf.md +++ /dev/null @@ -1,273 +0,0 @@ - - -# printf - -``` -printf FORMAT [ARGUMENT]... -printf OPTION -``` - -Print output based off of the format string and proceeding arguments. - -## After Help - -basic anonymous string templating: - -prints format string at least once, repeating as long as there are remaining arguments -output prints escaped literals in the format string as character literals -output replaces anonymous fields with the next unused argument, formatted according to the field. - -Prints the `,` replacing escaped character sequences with character literals -and substitution field sequences with passed arguments - -literally, with the exception of the below -escaped character sequences, and the substitution sequences described further down. - -### ESCAPE SEQUENCES - -The following escape sequences, organized here in alphabetical order, -will print the corresponding character literal: - -* `\"` double quote - -* `\\\\` backslash - -* `\\a` alert (BEL) - -* `\\b` backspace - -* `\\c` End-of-Input - -* `\\e` escape - -* `\\f` form feed - -* `\\n` new line - -* `\\r` carriage return - -* `\\t` horizontal tab - -* `\\v` vertical tab - -* `\\NNN` byte with value expressed in octal value NNN (1 to 3 digits) - values greater than 256 will be treated - -* `\\xHH` byte with value expressed in hexadecimal value NN (1 to 2 digits) - -* `\\uHHHH` Unicode (IEC 10646) character with value expressed in hexadecimal value HHHH (4 digits) - -* `\\uHHHH` Unicode character with value expressed in hexadecimal value HHHH (8 digits) - -* `%%` a single % - -### SUBSTITUTIONS - -#### SUBSTITUTION QUICK REFERENCE - -Fields - -* `%s`: string -* `%b`: string parsed for literals second parameter is max length - -* `%c`: char no second parameter - -* `%i` or `%d`: 64-bit integer -* `%u`: 64 bit unsigned integer -* `%x` or `%X`: 64-bit unsigned integer as hex -* `%o`: 64-bit unsigned integer as octal - second parameter is min-width, integer - output below that width is padded with leading zeroes - -* `%q`: ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable - characters with the proposed POSIX $'' syntax. - -* `%f` or `%F`: decimal floating point value -* `%e` or `%E`: scientific notation floating point value -* `%g` or `%G`: shorter of specially interpreted decimal or SciNote floating point value. - second parameter is - `-max` places after decimal point for floating point output - `-max` number of significant digits for scientific notation output - -parameterizing fields - -examples: - -``` -printf '%4.3i' 7 -``` - -It has a first parameter of 4 and a second parameter of 3 and will result in ' 007' - -``` -printf '%.1s' abcde -``` - -It has no first parameter and a second parameter of 1 and will result in 'a' - -``` -printf '%4c' q -``` - -It has a first parameter of 4 and no second parameter and will result in ' q' - -The first parameter of a field is the minimum width to pad the output to -if the output is less than this absolute value of this width, -it will be padded with leading spaces, or, if the argument is negative, -with trailing spaces. the default is zero. - -The second parameter of a field is particular to the output field type. -defaults can be found in the full substitution help below - -special prefixes to numeric arguments - -* `0`: (e.g. 010) interpret argument as octal (integer output fields only) -* `0x`: (e.g. 0xABC) interpret argument as hex (numeric output fields only) -* `\'`: (e.g. \'a) interpret argument as a character constant - -#### HOW TO USE SUBSTITUTIONS - -Substitutions are used to pass additional argument(s) into the FORMAT string, to be formatted a -particular way. E.g. - -``` -printf 'the letter %X comes before the letter %X' 10 11 -``` - -will print - -``` -the letter A comes before the letter B -``` - -because the substitution field `%X` means -'take an integer argument and write it as a hexadecimal number' - -Passing more arguments than are in the format string will cause the format string to be -repeated for the remaining substitutions - -``` -printf 'it is %i F in %s \n' 22 Portland 25 Boston 27 New York -``` - -will print - -``` -it is 22 F in Portland -it is 25 F in Boston -it is 27 F in Boston -``` - -If a format string is printed but there are less arguments remaining -than there are substitution fields, substitution fields without -an argument will default to empty strings, or for numeric fields -the value 0 - -#### AVAILABLE SUBSTITUTIONS - -This program, like GNU coreutils printf, -interprets a modified subset of the POSIX C printf spec, -a quick reference to substitutions is below. - -#### STRING SUBSTITUTIONS - -All string fields have a 'max width' parameter -`%.3s` means 'print no more than three characters of the original input' - -* `%s`: string - -* `%b`: escaped string - the string will be checked for any escaped literals from - the escaped literal list above, and translate them to literal characters. - e.g. `\\n` will be transformed into a newline character. - One special rule about `%b` mode is that octal literals are interpreted differently - In arguments passed by `%b`, pass octal-interpreted literals must be in the form of `\\0NNN` - instead of `\\NNN`. (Although, for legacy reasons, octal literals in the form of `\\NNN` will - still be interpreted and not throw a warning, you will have problems if you use this for a - literal whose code begins with zero, as it will be viewed as in `\\0NNN` form.) - -* `%q`: escaped string - the string in a format that can be reused as input by most shells. - Non-printable characters are escaped with the POSIX proposed ‘$''’ syntax, - and shell meta-characters are quoted appropriately. - This is an equivalent format to ls --quoting=shell-escape output. - -#### CHAR SUBSTITUTIONS - -The character field does not have a secondary parameter. - -* `%c`: a single character - -#### INTEGER SUBSTITUTIONS - -All integer fields have a 'pad with zero' parameter -`%.4i` means an integer which if it is less than 4 digits in length, -is padded with leading zeros until it is 4 digits in length. - -* `%d` or `%i`: 64-bit integer - -* `%u`: 64-bit unsigned integer - -* `%x` or `%X`: 64-bit unsigned integer printed in Hexadecimal (base 16) - `%X` instead of `%x` means to use uppercase letters for 'a' through 'f' - -* `%o`: 64-bit unsigned integer printed in octal (base 8) - -#### FLOATING POINT SUBSTITUTIONS - -All floating point fields have a 'max decimal places / max significant digits' parameter -`%.10f` means a decimal floating point with 7 decimal places past 0 -`%.10e` means a scientific notation number with 10 significant digits -`%.10g` means the same behavior for decimal and Sci. Note, respectively, and provides the shortest -of each's output. - -Like with GNU coreutils, the value after the decimal point is these outputs is parsed as a -double first before being rendered to text. For both implementations do not expect meaningful -precision past the 18th decimal place. When using a number of decimal places that is 18 or -higher, you can expect variation in output between GNU coreutils printf and this printf at the -18th decimal place of +/- 1 - -* `%f`: floating point value presented in decimal, truncated and displayed to 6 decimal places by - default. There is not past-double behavior parity with Coreutils printf, values are not - estimated or adjusted beyond input values. - -* `%e` or `%E`: floating point value presented in scientific notation - 7 significant digits by default - `%E` means use to use uppercase E for the mantissa. - -* `%g` or `%G`: floating point value presented in the shortest of decimal and scientific notation - behaves differently from `%f` and `%E`, please see posix printf spec for full details, - some examples of different behavior: - Sci Note has 6 significant digits by default - Trailing zeroes are removed - Instead of being truncated, digit after last is rounded - -Like other behavior in this utility, the design choices of floating point -behavior in this utility is selected to reproduce in exact -the behavior of GNU coreutils' printf from an inputs and outputs standpoint. - -### USING PARAMETERS - -Most substitution fields can be parameterized using up to 2 numbers that can -be passed to the field, between the % sign and the field letter. - -The 1st parameter always indicates the minimum width of output, it is useful for creating -columnar output. Any output that would be less than this minimum width is padded with -leading spaces -The 2nd parameter is proceeded by a dot. -You do not have to use parameters - -### SPECIAL FORMS OF INPUT - -For numeric input, the following additional forms of input are accepted besides decimal: - -Octal (only with integer): if the argument begins with a 0 the proceeding characters -will be interpreted as octal (base 8) for integer fields - -Hexadecimal: if the argument begins with 0x the proceeding characters will be interpreted -will be interpreted as hex (base 16) for any numeric fields -for float fields, hexadecimal input results in a precision -limit (in converting input past the decimal point) of 10^-15 - -Character Constant: if the argument begins with a single quote character, the first byte -of the next character will be interpreted as an 8-bit unsigned integer. If there are -additional bytes, they will throw an error (unless the environment variable POSIXLY_CORRECT -is set) diff --git a/src/uu/ptx/ptx.md b/src/uu/ptx/ptx.md deleted file mode 100644 index b8717776cb6..00000000000 --- a/src/uu/ptx/ptx.md +++ /dev/null @@ -1,11 +0,0 @@ -# ptx - -``` -ptx [OPTION]... [INPUT]... -ptx -G [OPTION]... [INPUT [OUTPUT]] -``` - -Produce a permuted index of file contents -Output a permuted index, including context, of the words in the input files. -Mandatory arguments to long options are mandatory for short options too. -With no FILE, or when FILE is -, read standard input. Default is '-F /'. \ No newline at end of file diff --git a/src/uu/pwd/pwd.md b/src/uu/pwd/pwd.md deleted file mode 100644 index 89ccee2db8e..00000000000 --- a/src/uu/pwd/pwd.md +++ /dev/null @@ -1,7 +0,0 @@ -# pwd - -``` -pwd [OPTION]... [FILE]... -``` - -Display the full filename of the current working directory. diff --git a/src/uu/readlink/readlink.md b/src/uu/readlink/readlink.md deleted file mode 100644 index 7215acbec09..00000000000 --- a/src/uu/readlink/readlink.md +++ /dev/null @@ -1,7 +0,0 @@ -# readlink - -``` -readlink [OPTION]... [FILE]... -``` - -Print value of a symbolic link or canonical file name. diff --git a/src/uu/realpath/realpath.md b/src/uu/realpath/realpath.md deleted file mode 100644 index 25e4a37260d..00000000000 --- a/src/uu/realpath/realpath.md +++ /dev/null @@ -1,7 +0,0 @@ -# realpath - -``` -realpath [OPTION]... FILE... -``` - -Print the resolved path diff --git a/src/uu/rm/rm.md b/src/uu/rm/rm.md deleted file mode 100644 index 7acc46363b8..00000000000 --- a/src/uu/rm/rm.md +++ /dev/null @@ -1,22 +0,0 @@ -# rm - -``` -rm [OPTION]... FILE... -``` - -Remove (unlink) the FILE(s) - -## After Help - -By default, rm does not remove directories. Use the --recursive (-r or -R) -option to remove each listed directory, too, along with all of its contents - -To remove a file whose name starts with a '-', for example '-foo', -use one of these commands: -rm -- -foo - -rm ./-foo - -Note that if you use rm to remove a file, it might be possible to recover -some of its contents, given sufficient expertise and/or time. For greater -assurance that the contents are truly unrecoverable, consider using shred. diff --git a/src/uu/rmdir/rmdir.md b/src/uu/rmdir/rmdir.md deleted file mode 100644 index 16f7bf82bea..00000000000 --- a/src/uu/rmdir/rmdir.md +++ /dev/null @@ -1,7 +0,0 @@ -# rmdir - -``` -rmdir [OPTION]... DIRECTORY... -``` - -Remove the DIRECTORY(ies), if they are empty. diff --git a/src/uu/runcon/runcon.md b/src/uu/runcon/runcon.md deleted file mode 100644 index 53884b703be..00000000000 --- a/src/uu/runcon/runcon.md +++ /dev/null @@ -1,18 +0,0 @@ -# runcon - -``` -runcon CONTEXT COMMAND [ARG...] -runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [ARG...] -``` - -Run command with specified security context under SELinux enabled systems. - -## After Help - -Run COMMAND with completely-specified CONTEXT, or with current or transitioned security context modified by one or more of LEVEL, ROLE, TYPE, and USER. - -If none of --compute, --type, --user, --role or --range is specified, then the first argument is used as the complete context. - -Note that only carefully-chosen contexts are likely to successfully run. - -If neither CONTEXT nor COMMAND is specified, the current security context is printed. diff --git a/src/uu/seq/seq.md b/src/uu/seq/seq.md deleted file mode 100644 index d747e4a0263..00000000000 --- a/src/uu/seq/seq.md +++ /dev/null @@ -1,9 +0,0 @@ -# seq - -``` -seq [OPTION]... LAST -seq [OPTION]... FIRST LAST -seq [OPTION]... FIRST INCREMENT LAST -``` - -Display numbers from FIRST to LAST, in steps of INCREMENT. diff --git a/src/uu/shred/shred.md b/src/uu/shred/shred.md deleted file mode 100644 index 6d6398cce10..00000000000 --- a/src/uu/shred/shred.md +++ /dev/null @@ -1,47 +0,0 @@ -# shred - - - -``` -shred [OPTION]... FILE... -``` - -Overwrite the specified FILE(s) repeatedly, in order to make it harder for even -very expensive hardware probing to recover the data. - -## After help - -Delete `FILE(s)` if `--remove` (`-u`) is specified. The default is not to remove -the files because it is common to operate on device files like `/dev/hda`, and -those files usually should not be removed. - -CAUTION: Note that shred relies on a very important assumption: that the file -system overwrites data in place. This is the traditional way to do things, but -many modern file system designs do not satisfy this assumption. The following -are examples of file systems on which shred is not effective, or is not -guaranteed to be effective in all file system modes: - - * log-structured or journal file systems, such as those supplied with - AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.) - - * file systems that write redundant data and carry on even if some writes - fail, such as RAID-based file systems - - * file systems that make snapshots, such as Network Appliance's NFS server - - * file systems that cache in temporary locations, such as NFS - version 3 clients - - * compressed file systems - -In the case of ext3 file systems, the above disclaimer applies (and shred is -thus of limited effectiveness) only in `data=journal` mode, which journals file -data in addition to just metadata. In both the `data=ordered` (default) and -`data=writeback` modes, shred works as usual. Ext3 journal modes can be changed -by adding the `data=something` option to the mount options for a particular -file system in the `/etc/fstab` file, as documented in the mount man page (`man -mount`). - -In addition, file system backups and remote mirrors may contain copies of -the file that cannot be removed, and that will allow a shredded file to be -recovered later. diff --git a/src/uu/shuf/shuf.md b/src/uu/shuf/shuf.md deleted file mode 100644 index 7bc1e0a6d60..00000000000 --- a/src/uu/shuf/shuf.md +++ /dev/null @@ -1,11 +0,0 @@ -# shuf - -``` -shuf [OPTION]... [FILE] -shuf -e [OPTION]... [ARG]... -shuf -i LO-HI [OPTION]... -``` - -Shuffle the input by outputting a random permutation of input lines. -Each output permutation is equally likely. -With no FILE, or when FILE is -, read standard input. diff --git a/src/uu/sleep/sleep.md b/src/uu/sleep/sleep.md deleted file mode 100644 index b5c07fb493e..00000000000 --- a/src/uu/sleep/sleep.md +++ /dev/null @@ -1,16 +0,0 @@ -# sleep - -``` -sleep NUMBER[SUFFIX]... -sleep OPTION -``` - -Pause for NUMBER seconds. - -## After Help - -Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), -'m' for minutes, 'h' for hours or 'd' for days. Unlike most implementations -that require NUMBER be an integer, here NUMBER may be an arbitrary floating -point number. Given two or more arguments, pause for the amount of time -specified by the sum of their values. diff --git a/src/uu/sort/sort.md b/src/uu/sort/sort.md deleted file mode 100644 index 1d1aa5d5ffe..00000000000 --- a/src/uu/sort/sort.md +++ /dev/null @@ -1,21 +0,0 @@ - - -# sort - -``` -sort [OPTION]... [FILE]... -``` - -Display sorted concatenation of all FILE(s). With no FILE, or when FILE is -, read standard input. - -## After help - -The key format is `FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]`. - -Fields by default are separated by the first whitespace after a non-whitespace character. Use `-t` to specify a custom separator. -In the default case, whitespace is appended at the beginning of each field. Custom separators however are not included in fields. - -`FIELD` and `CHAR` both start at 1 (i.e. they are 1-indexed). If there is no end specified after a comma, the end will be the end of the line. -If `CHAR` is set 0, it means the end of the field. `CHAR` defaults to 1 for the start position and to 0 for the end position. - -Valid options are: `MbdfhnRrV`. They override the global options for this key. diff --git a/src/uu/split/split.md b/src/uu/split/split.md deleted file mode 100644 index 836e3a0c69a..00000000000 --- a/src/uu/split/split.md +++ /dev/null @@ -1,26 +0,0 @@ - - -# split - -``` -split [OPTION]... [INPUT [PREFIX]] -``` - -Create output files containing consecutive or interleaved sections of input - -## After Help - -Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is -, read standard input. - -The SIZE argument is an integer and optional unit (example: 10K is 10*1024). -Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). -Binary prefixes can be used, too: KiB=K, MiB=M, and so on. - -CHUNKS may be: - -- N split into N files based on size of input -- K/N output Kth of N to stdout -- l/N split into N files without splitting lines/records -- l/K/N output Kth of N to stdout without splitting lines/records -- r/N like 'l' but use round robin distribution -- r/K/N likewise but only output Kth of N to stdout diff --git a/src/uu/stat/stat.md b/src/uu/stat/stat.md deleted file mode 100644 index ac63edd18d2..00000000000 --- a/src/uu/stat/stat.md +++ /dev/null @@ -1,61 +0,0 @@ -# stat - -``` -stat [OPTION]... FILE... -``` - -Display file or file system status. - -## Long Usage - -Valid format sequences for files (without `--file-system`): - -- `%a`: access rights in octal (note '#' and '0' printf flags) -- `%A`: access rights in human readable form -- `%b`: number of blocks allocated (see %B) -- `%B`: the size in bytes of each block reported by %b -- `%C`: SELinux security context string -- `%d`: device number in decimal -- `%D`: device number in hex -- `%f`: raw mode in hex -- `%F`: file type -- `%g`: group ID of owner -- `%G`: group name of owner -- `%h`: number of hard links -- `%i`: inode number -- `%m`: mount point -- `%n`: file name -- `%N`: quoted file name with dereference (follow) if symbolic link -- `%o`: optimal I/O transfer size hint -- `%s`: total size, in bytes -- `%t`: major device type in hex, for character/block device special files -- `%T`: minor device type in hex, for character/block device special files -- `%u`: user ID of owner -- `%U`: user name of owner -- `%w`: time of file birth, human-readable; - if unknown -- `%W`: time of file birth, seconds since Epoch; 0 if unknown -- `%x`: time of last access, human-readable -- `%X`: time of last access, seconds since Epoch -- `%y`: time of last data modification, human-readable -- `%Y`: time of last data modification, seconds since Epoch -- `%z`: time of last status change, human-readable -- `%Z`: time of last status change, seconds since Epoch - -Valid format sequences for file systems: - -- `%a`: free blocks available to non-superuser -- `%b`: total data blocks in file system -- `%c`: total file nodes in file system -- `%d`: free file nodes in file system -- `%f`: free blocks in file system -- `%i`: file system ID in hex -- `%l`: maximum length of filenames -- `%n`: file name -- `%s`: block size (for faster transfers) -- `%S`: fundamental block size (for block counts) -- `%t`: file system type in hex -- `%T`: file system type in human readable form - -NOTE: your shell may have its own version of stat, which usually supersedes -the version described here. Please refer to your shell's documentation -for details about the options it supports. diff --git a/src/uu/stdbuf/stdbuf.md b/src/uu/stdbuf/stdbuf.md deleted file mode 100644 index e0062e6271a..00000000000 --- a/src/uu/stdbuf/stdbuf.md +++ /dev/null @@ -1,24 +0,0 @@ -# stdbuf - -``` -stdbuf [OPTION]... COMMAND -``` - -Run `COMMAND`, with modified buffering operations for its standard streams. - -Mandatory arguments to long options are mandatory for short options too. - -## After Help - -If `MODE` is 'L' the corresponding stream will be line buffered. -This option is invalid with standard input. - -If `MODE` is '0' the corresponding stream will be unbuffered. - -Otherwise, `MODE` is a number which may be followed by one of the following: - -KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y. -In this case the corresponding stream will be fully buffered with the buffer size set to `MODE` bytes. - -NOTE: If `COMMAND` adjusts the buffering of its standard streams (`tee` does for e.g.) then that will override corresponding settings changed by `stdbuf`. -Also some filters (like `dd` and `cat` etc.) don't use streams for I/O, and are thus unaffected by `stdbuf` settings. diff --git a/src/uu/stty/stty.md b/src/uu/stty/stty.md deleted file mode 100644 index 6aa1decf5e7..00000000000 --- a/src/uu/stty/stty.md +++ /dev/null @@ -1,9 +0,0 @@ -# stty - -``` -stty [-F DEVICE | --file=DEVICE] [SETTING]... -stty [-F DEVICE | --file=DEVICE] [-a|--all] -stty [-F DEVICE | --file=DEVICE] [-g|--save] -``` - -Print or change terminal characteristics. diff --git a/src/uu/sum/sum.md b/src/uu/sum/sum.md deleted file mode 100644 index 93dbdbf62d0..00000000000 --- a/src/uu/sum/sum.md +++ /dev/null @@ -1,9 +0,0 @@ -# sum - -``` -sum [OPTION]... [FILE]... -``` - -Checksum and count the blocks in a file. - -With no FILE, or when FILE is -, read standard input. diff --git a/src/uu/sync/sync.md b/src/uu/sync/sync.md deleted file mode 100644 index 2fdd363399f..00000000000 --- a/src/uu/sync/sync.md +++ /dev/null @@ -1,7 +0,0 @@ -# sync - -``` -sync [OPTION]... FILE... -``` - -Synchronize cached writes to persistent storage diff --git a/src/uu/tac/tac.md b/src/uu/tac/tac.md deleted file mode 100644 index 6787b3f49eb..00000000000 --- a/src/uu/tac/tac.md +++ /dev/null @@ -1,7 +0,0 @@ -# tac - -``` -tac [OPTION]... [FILE]... -``` - -Write each file to standard output, last line first. diff --git a/src/uu/tail/tail.md b/src/uu/tail/tail.md deleted file mode 100644 index fefe7f6ee68..00000000000 --- a/src/uu/tail/tail.md +++ /dev/null @@ -1,11 +0,0 @@ -# tail - -``` -tail [FLAG]... [FILE]... -``` - -Print the last 10 lines of each FILE to standard output. -With more than one FILE, precede each with a header giving the file name. -With no FILE, or when FILE is -, read standard input. - -Mandatory arguments to long flags are mandatory for short flags too. diff --git a/src/uu/tee/tee.md b/src/uu/tee/tee.md deleted file mode 100644 index 8bf097cecd5..00000000000 --- a/src/uu/tee/tee.md +++ /dev/null @@ -1,11 +0,0 @@ -# tee - -``` -tee [OPTION]... [FILE]... -``` - -Copy standard input to each FILE, and also to standard output. - -## After Help - -If a FILE is -, it refers to a file named - . diff --git a/src/uu/test/test.md b/src/uu/test/test.md deleted file mode 100644 index e67eb1824ab..00000000000 --- a/src/uu/test/test.md +++ /dev/null @@ -1,79 +0,0 @@ -# test - -``` -test EXPRESSION -test -[ EXPRESSION ] -[ ] -[ OPTION -``` - -Check file types and compare values. - -## After Help - -Exit with the status determined by `EXPRESSION`. - -An omitted `EXPRESSION` defaults to false. -Otherwise, `EXPRESSION` is true or false and sets exit status. - -It is one of: - -* ( EXPRESSION ) `EXPRESSION` is true -* ! EXPRESSION `EXPRESSION` is false -* EXPRESSION1 -a EXPRESSION2 both `EXPRESSION1` and `EXPRESSION2` are true -* EXPRESSION1 -o EXPRESSION2 either `EXPRESSION1` or `EXPRESSION2` is true - -String operations: -* -n STRING the length of `STRING` is nonzero -* STRING equivalent to -n `STRING` -* -z STRING the length of `STRING` is zero -* STRING1 = STRING2 the strings are equal -* STRING1 != STRING2 the strings are not equal - -Integer comparisons: -* INTEGER1 -eq INTEGER2 `INTEGER1` is equal to `INTEGER2` -* INTEGER1 -ge INTEGER2 `INTEGER1` is greater than or equal to `INTEGER2` -* INTEGER1 -gt INTEGER2 `INTEGER1` is greater than `INTEGER2` -* INTEGER1 -le INTEGER2 `INTEGER1` is less than or equal to `INTEGER2` -* INTEGER1 -lt INTEGER2 `INTEGER1` is less than `INTEGER2` -* INTEGER1 -ne INTEGER2 `INTEGER1` is not equal to `INTEGER2` - -File operations: -* FILE1 -ef FILE2 `FILE1` and `FILE2` have the same device and inode numbers -* FILE1 -nt FILE2 `FILE1` is newer (modification date) than `FILE2` -* FILE1 -ot FILE2 `FILE1` is older than `FILE2` - -* -b FILE `FILE` exists and is block special -* -c FILE `FILE` exists and is character special -* -d FILE `FILE` exists and is a directory -* -e FILE `FILE` exists -* -f FILE `FILE` exists and is a regular file -* -g FILE `FILE` exists and is set-group-ID -* -G FILE `FILE` exists and is owned by the effective group ID -* -h FILE `FILE` exists and is a symbolic link (same as -L) -* -k FILE `FILE` exists and has its sticky bit set -* -L FILE `FILE` exists and is a symbolic link (same as -h) -* -N FILE `FILE` exists and has been modified since it was last read -* -O FILE `FILE` exists and is owned by the effective user ID -* -p FILE `FILE` exists and is a named pipe -* -r FILE `FILE` exists and read permission is granted -* -s FILE `FILE` exists and has a size greater than zero -* -S FILE `FILE` exists and is a socket -* -t FD `file` descriptor `FD` is opened on a terminal -* -u FILE `FILE` exists and its set-user-ID bit is set -* -w FILE `FILE` exists and write permission is granted -* -x FILE `FILE` exists and execute (or search) permission is granted - -Except for `-h` and `-L`, all FILE-related tests dereference (follow) symbolic links. -Beware that parentheses need to be escaped (e.g., by backslashes) for shells. -`INTEGER` may also be -l `STRING`, which evaluates to the length of `STRING`. - -NOTE: Binary `-a` and `-o` are inherently ambiguous. -Use `test EXPR1 && test EXPR2` or `test EXPR1 || test EXPR2` instead. - -NOTE: `[` honors the `--help` and `--version` options, but test does not. -test treats each of those as it treats any other nonempty `STRING`. - -NOTE: your shell may have its own version of `test` and/or `[`, which usually supersedes the version described here. -Please refer to your shell's documentation for details about the options it supports. diff --git a/src/uu/timeout/timeout.md b/src/uu/timeout/timeout.md deleted file mode 100644 index f992ab32772..00000000000 --- a/src/uu/timeout/timeout.md +++ /dev/null @@ -1,7 +0,0 @@ -# timeout - -``` -timeout [OPTION] DURATION COMMAND... -``` - -Start `COMMAND`, and kill it if still running after `DURATION`. diff --git a/src/uu/touch/touch.md b/src/uu/touch/touch.md deleted file mode 100644 index 8ce35b6868a..00000000000 --- a/src/uu/touch/touch.md +++ /dev/null @@ -1,7 +0,0 @@ -# touch - -``` -touch [OPTION]... [USER] -``` - -Update the access and modification times of each `FILE` to the current time. diff --git a/src/uu/tr/tr.md b/src/uu/tr/tr.md deleted file mode 100644 index 93349eeaa84..00000000000 --- a/src/uu/tr/tr.md +++ /dev/null @@ -1,11 +0,0 @@ -# tr - -``` -tr [OPTION]... SET1 [SET2] -``` - -Translate or delete characters - -## After help - -Translate, squeeze, and/or delete characters from standard input, writing to standard output. diff --git a/src/uu/true/true.md b/src/uu/true/true.md deleted file mode 100644 index b21c9616e14..00000000000 --- a/src/uu/true/true.md +++ /dev/null @@ -1,11 +0,0 @@ -# true - -``` -true -``` - -Returns true, a successful exit status. - -Immediately returns with the exit status `0`, except when invoked with one of the recognized -options. In those cases it will try to write the help or version text. Any IO error during this -operation causes the program to return `1` instead. diff --git a/src/uu/truncate/truncate.md b/src/uu/truncate/truncate.md deleted file mode 100644 index d60a6b7b200..00000000000 --- a/src/uu/truncate/truncate.md +++ /dev/null @@ -1,26 +0,0 @@ -# truncate - -``` -truncate [OPTION]... [FILE]... -``` - -Shrink or extend the size of each file to the specified size. - -## After help - -SIZE is an integer with an optional prefix and optional unit. -The available units (K, M, G, T, P, E, Z, and Y) use the following format: - 'KB' => 1000 (kilobytes) - 'K' => 1024 (kibibytes) - 'MB' => 1000*1000 (megabytes) - 'M' => 1024*1024 (mebibytes) - 'GB' => 1000*1000*1000 (gigabytes) - 'G' => 1024*1024*1024 (gibibytes) -SIZE may also be prefixed by one of the following to adjust the size of each -file based on its current size: - '+' => extend by - '-' => reduce by - '<' => at most - '>' => at least - '/' => round down to multiple of - '%' => round up to multiple of diff --git a/src/uu/tsort/tsort.md b/src/uu/tsort/tsort.md deleted file mode 100644 index 5effbf1e705..00000000000 --- a/src/uu/tsort/tsort.md +++ /dev/null @@ -1,10 +0,0 @@ -# tsort - -``` -tsort [OPTIONS] FILE -``` - -Topological sort the strings in FILE. -Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline), ordering them based on dependencies in a directed acyclic graph (DAG). -Useful for scheduling and determining execution order. -If FILE is not passed in, stdin is used instead. diff --git a/src/uu/tty/tty.md b/src/uu/tty/tty.md deleted file mode 100644 index 230399a2069..00000000000 --- a/src/uu/tty/tty.md +++ /dev/null @@ -1,7 +0,0 @@ -# tty - -``` -tty [OPTION]... -``` - -Print the file name of the terminal connected to standard input. diff --git a/src/uu/uname/uname.md b/src/uu/uname/uname.md deleted file mode 100644 index b90c54cfcc3..00000000000 --- a/src/uu/uname/uname.md +++ /dev/null @@ -1,8 +0,0 @@ -# uname - -``` -uname [OPTION]... -``` - -Print certain system information. -With no OPTION, same as -s. diff --git a/src/uu/unexpand/unexpand.md b/src/uu/unexpand/unexpand.md deleted file mode 100644 index 6fc69b93a3a..00000000000 --- a/src/uu/unexpand/unexpand.md +++ /dev/null @@ -1,8 +0,0 @@ -# unexpand - -``` -unexpand [OPTION]... [FILE]... -``` - -Convert blanks in each `FILE` to tabs, writing to standard output. -With no `FILE`, or when `FILE` is `-`, read standard input. diff --git a/src/uu/uniq/uniq.md b/src/uu/uniq/uniq.md deleted file mode 100644 index 20de52395ba..00000000000 --- a/src/uu/uniq/uniq.md +++ /dev/null @@ -1,15 +0,0 @@ -# uniq - -``` -uniq [OPTION]... [INPUT [OUTPUT]] -``` - -Report or omit repeated lines. - -## After help - -Filter adjacent matching lines from `INPUT` (or standard input), -writing to `OUTPUT` (or standard output). - -Note: `uniq` does not detect repeated lines unless they are adjacent. -You may want to sort the input first, or use `sort -u` without `uniq`. diff --git a/src/uu/unlink/unlink.md b/src/uu/unlink/unlink.md deleted file mode 100644 index 14b023b8b66..00000000000 --- a/src/uu/unlink/unlink.md +++ /dev/null @@ -1,8 +0,0 @@ -# unlink - -``` -unlink FILE -unlink OPTION -``` - -Unlink the file at `FILE`. diff --git a/src/uu/uptime/uptime.md b/src/uu/uptime/uptime.md deleted file mode 100644 index fd9c8fd2f59..00000000000 --- a/src/uu/uptime/uptime.md +++ /dev/null @@ -1,9 +0,0 @@ -# uptime - -``` -uptime [OPTION]... -``` - -Display the current time, the length of time the system has been up, -the number of users on the system, and the average number of jobs -in the run queue over the last 1, 5 and 15 minutes. diff --git a/src/uu/users/users.md b/src/uu/users/users.md deleted file mode 100644 index 7b126a0f0be..00000000000 --- a/src/uu/users/users.md +++ /dev/null @@ -1,7 +0,0 @@ -# users - -``` -users [FILE] -``` - -Print the user names of users currently logged in to the current host. diff --git a/src/uu/vdir/vdir.md b/src/uu/vdir/vdir.md deleted file mode 100644 index c7a0cae3643..00000000000 --- a/src/uu/vdir/vdir.md +++ /dev/null @@ -1,10 +0,0 @@ -# vdir - -``` -vdir [OPTION]... [FILE]... -``` - -List directory contents. -Ignore files and directories starting with a '.' by default. - -Mandatory arguments to long options are mandatory for short options too. diff --git a/src/uu/wc/wc.md b/src/uu/wc/wc.md deleted file mode 100644 index c7aaf8545f2..00000000000 --- a/src/uu/wc/wc.md +++ /dev/null @@ -1,8 +0,0 @@ -# wc - -``` -wc [OPTION]... [FILE]... -``` - -Display newline, word, and byte counts for each FILE, and a total line if -more than one FILE is specified. With no FILE, or when FILE is -, read standard input. diff --git a/src/uu/who/who.md b/src/uu/who/who.md deleted file mode 100644 index 6adc2659733..00000000000 --- a/src/uu/who/who.md +++ /dev/null @@ -1,8 +0,0 @@ -# who - -``` -who [OPTION]... [ FILE | ARG1 ARG2 ] -``` - -Print information about users who are currently logged in. - diff --git a/src/uu/whoami/whoami.md b/src/uu/whoami/whoami.md deleted file mode 100644 index 6106d24ee7e..00000000000 --- a/src/uu/whoami/whoami.md +++ /dev/null @@ -1,7 +0,0 @@ -# whoami - -``` -whoami -``` - -Print the current username. diff --git a/src/uu/yes/yes.md b/src/uu/yes/yes.md deleted file mode 100644 index ad2da7dc9af..00000000000 --- a/src/uu/yes/yes.md +++ /dev/null @@ -1,7 +0,0 @@ -# yes - -``` -yes [STRING]... -``` - -Repeatedly display a line with STRING (or 'y') From a36d5455abdaf29d7a2ef1fa5542fb092a065225 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 22:28:48 +0200 Subject: [PATCH 117/139] l10n: improve the display when the ftl is invalid --- Cargo.lock | 1 + Cargo.toml | 1 + src/uucore/Cargo.toml | 1 + src/uucore/src/lib/mods/locale.rs | 57 ++++++++++++++++++++++++------- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b5d5ead095..810cc2a66d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3601,6 +3601,7 @@ dependencies = [ "dunce", "fluent", "fluent-bundle", + "fluent-syntax", "glob", "hex", "itertools 0.14.0", diff --git a/Cargo.toml b/Cargo.toml index 88adba12652..752c0cfef85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -364,6 +364,7 @@ digest = "0.10.7" fluent-bundle = "0.16.0" fluent = "0.17.0" unic-langid = "0.9.6" +fluent-syntax = "0.12.0" uucore = { version = "0.1.0", package = "uucore", path = "src/uucore" } uucore_procs = { version = "0.1.0", package = "uucore_procs", path = "src/uucore_procs" } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 7395df34300..cd4f2e1c008 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -60,6 +60,7 @@ selinux = { workspace = true, optional = true } # Fluent dependencies fluent-bundle = { workspace = true } fluent = { workspace = true } +fluent-syntax = { workspace = true } unic-langid = { workspace = true } thiserror = { workspace = true } [target.'cfg(unix)'.dependencies] diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index ba701240c97..3c69ba314ef 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -6,6 +6,7 @@ use crate::error::UError; use fluent::{FluentArgs, FluentBundle, FluentResource}; +use fluent_syntax::parser::ParserError; use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -21,8 +22,14 @@ pub enum LocalizationError { source: std::io::Error, path: PathBuf, }, - #[error("Parse error: {0}")] - Parse(String), + #[error("Parse-locale error: {0}")] + ParseLocale(String), + #[error("Resource parse error at '{snippet}': {error:?}")] + ParseResource { + #[source] + error: ParserError, + snippet: String, + }, #[error("Bundle error: {0}")] Bundle(String), #[error("Locales directory not found: {0}")] @@ -261,9 +268,9 @@ fn detect_system_locale() -> Result { .next() .unwrap_or(DEFAULT_LOCALE) .to_string(); - - LanguageIdentifier::from_str(&locale_str) - .map_err(|_| LocalizationError::Parse(format!("Failed to parse locale: {}", locale_str))) + LanguageIdentifier::from_str(&locale_str).map_err(|_| { + LocalizationError::ParseLocale(format!("Failed to parse locale: {}", locale_str)) + }) } /// Sets up localization using the system locale with English fallback. @@ -461,7 +468,7 @@ invalid-syntax = This is { $missing #[test] fn test_localization_error_uerror_impl() { - let error = LocalizationError::Parse("test error".to_string()); + let error = LocalizationError::Bundle("some error".to_string()); assert_eq!(error.code(), 1); } @@ -500,10 +507,14 @@ invalid-syntax = This is { $missing let result = create_bundle(&locale, temp_dir.path()); assert!(result.is_err()); - if let Err(LocalizationError::Parse(_)) = result { - // Expected parse error + if let Err(LocalizationError::ParseResource { + error: _parser_err, + snippet: _, + }) = result + { + // Expected ParseResource variant } else { - panic!("Expected parse error"); + panic!("Expected ParseResource error"); } } @@ -1042,12 +1053,32 @@ invalid-syntax = This is { $missing assert!(error_string.contains("I/O error loading")); assert!(error_string.contains("/test/path.ftl")); - let parse_error = LocalizationError::Parse("Syntax error".to_string()); - let parse_string = format!("{}", parse_error); - assert!(parse_string.contains("Parse error: Syntax error")); - let bundle_error = LocalizationError::Bundle("Bundle creation failed".to_string()); let bundle_string = format!("{}", bundle_error); assert!(bundle_string.contains("Bundle error: Bundle creation failed")); } + + #[test] + fn test_parse_resource_error_includes_snippet() { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("es-ES").unwrap(); + + let result = create_bundle(&locale, temp_dir.path()); + assert!(result.is_err()); + + if let Err(LocalizationError::ParseResource { + error: _err, + snippet, + }) = result + { + // The snippet should contain exactly the invalid text from es-ES.ftl + assert!( + snippet.contains("This is { $missing"), + "snippet was `{}` but did not include the invalid text", + snippet + ); + } else { + panic!("Expected LocalizationError::ParseResource with snippet"); + } + } } From b904f36636902da8e10832153b9cc4db2cae531c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 22:30:59 +0200 Subject: [PATCH 118/139] l10n: manages aliases of commands --- src/bin/coreutils.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 384ac9876fc..a910f8a164a 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -51,6 +51,23 @@ fn name(binary_path: &Path) -> Option<&str> { binary_path.file_stem()?.to_str() } +fn get_canonical_util_name(util_name: &str) -> &str { + match util_name { + // uu_test aliases - '[' is an alias for test + "[" => "test", + + // hashsum aliases - all these hash commands are aliases for hashsum + "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" + | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum" + | "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum", + + "dir" => "ls", // dir is an alias for ls + + // Default case - return the util name as is + _ => util_name, + } +} + #[allow(clippy::cognitive_complexity)] fn main() { uucore::panic::mute_sigpipe_panic(); @@ -71,6 +88,7 @@ fn main() { // binary name equals prefixed util name? // * prefix/stem may be any string ending in a non-alphanumeric character + // For example, if the binary is named `uu_test`, it will match `test` as a utility. let util_name = if let Some(util) = utils.keys().find(|util| { binary_as_util.ends_with(*util) && !binary_as_util[..binary_as_util.len() - (*util).len()] From 00982c93749e23b8820f21dae1d25b6b5b57e865 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 22:38:54 +0200 Subject: [PATCH 119/139] l10n: Move the prefix removal into a function --- src/bin/coreutils.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index a910f8a164a..5b4c734d515 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -68,6 +68,18 @@ fn get_canonical_util_name(util_name: &str) -> &str { } } +fn find_prefixed_util<'a>( + binary_name: &str, + mut util_keys: impl Iterator, +) -> Option<&'a str> { + util_keys.find(|util| { + binary_name.ends_with(*util) + && binary_name.len() > util.len() // Ensure there's actually a prefix + && !binary_name[..binary_name.len() - (*util).len()] + .ends_with(char::is_alphanumeric) + }) +} + #[allow(clippy::cognitive_complexity)] fn main() { uucore::panic::mute_sigpipe_panic(); @@ -89,13 +101,9 @@ fn main() { // binary name equals prefixed util name? // * prefix/stem may be any string ending in a non-alphanumeric character // For example, if the binary is named `uu_test`, it will match `test` as a utility. - let util_name = if let Some(util) = utils.keys().find(|util| { - binary_as_util.ends_with(*util) - && !binary_as_util[..binary_as_util.len() - (*util).len()] - .ends_with(char::is_alphanumeric) - }) { + let util_name = if let Some(util) = find_prefixed_util(binary_as_util, utils.keys().copied()) { // prefixed util => replace 0th (aka, executable name) argument - Some(OsString::from(*util)) + Some(OsString::from(util)) } else { // unmatched binary name => regard as multi-binary container and advance argument list uucore::set_utility_is_second_arg(); From a7f941577fd49bf26ed02291d3ffd3f2ecccf692 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 22:39:33 +0200 Subject: [PATCH 120/139] bin/coreutils: add unit tests --- src/bin/coreutils.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 5b4c734d515..ffb4ba7efed 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -287,3 +287,70 @@ fn gen_coreutils_app(util_map: &UtilityMap) -> Command { } command } + +#[cfg(test)] +mod tests { + use super::*; + use std::path::Path; + + #[test] + fn test_get_canonical_util_name() { + // Test a few key aliases + assert_eq!(get_canonical_util_name("["), "test"); + assert_eq!(get_canonical_util_name("md5sum"), "hashsum"); + assert_eq!(get_canonical_util_name("dir"), "ls"); + + // Test passthrough case + assert_eq!(get_canonical_util_name("cat"), "cat"); + } + + #[test] + fn test_name() { + // Test normal executable name + assert_eq!(name(Path::new("/usr/bin/ls")), Some("ls")); + assert_eq!(name(Path::new("cat")), Some("cat")); + assert_eq!( + name(Path::new("./target/debug/coreutils")), + Some("coreutils") + ); + + // Test with extensions + assert_eq!(name(Path::new("program.exe")), Some("program")); + assert_eq!(name(Path::new("/path/to/utility.bin")), Some("utility")); + + // Test edge cases + assert_eq!(name(Path::new("")), None); + assert_eq!(name(Path::new("/")), None); + } + + #[test] + fn test_find_prefixed_util() { + let utils = ["test", "cat", "ls", "cp"]; + + // Test exact prefixed matches + assert_eq!( + find_prefixed_util("uu_test", utils.iter().copied()), + Some("test") + ); + assert_eq!( + find_prefixed_util("my-cat", utils.iter().copied()), + Some("cat") + ); + assert_eq!( + find_prefixed_util("prefix_ls", utils.iter().copied()), + Some("ls") + ); + + // Test non-alphanumeric separator requirement + assert_eq!(find_prefixed_util("prefixcat", utils.iter().copied()), None); // no separator + assert_eq!(find_prefixed_util("testcat", utils.iter().copied()), None); // no separator + + // Test no match + assert_eq!(find_prefixed_util("unknown", utils.iter().copied()), None); + assert_eq!(find_prefixed_util("", utils.iter().copied()), None); + + // Test exact util name (should not match as prefixed) + assert_eq!(find_prefixed_util("test", utils.iter().copied()), None); + assert_eq!(find_prefixed_util("cat", utils.iter().copied()), None); + } +} From d9f0868a45ec4b6c963f7f683a699946805eda70 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 22:57:43 +0200 Subject: [PATCH 121/139] l10n: Adjust a test, I don't think we need to full path with 'usage' --- tests/test_util_name.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 7a8a076e893..cd0356edc8f 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -41,7 +41,7 @@ fn execution_phrase_double() { assert!( String::from_utf8(output.stderr) .unwrap() - .contains(&format!("Usage: {} ls", scenario.bin_path.display())) + .contains(&"Usage: ls".to_string()) ); } From c437ac830ed3716674d3f0fa055526e36533d55f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 23:32:53 +0200 Subject: [PATCH 122/139] l10n: the manpage creation should use the locale files --- src/bin/coreutils.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index ffb4ba7efed..5d219d75d39 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -80,6 +80,19 @@ fn find_prefixed_util<'a>( }) } +fn setup_localization_or_exit(util_name: &str) { + locale::setup_localization(get_canonical_util_name(util_name)).unwrap_or_else(|err| { + match err { + uucore::locale::LocalizationError::ParseResource { + error: err_msg, + snippet, + } => eprintln!("Localization parse error at {snippet}: {err_msg}"), + other => eprintln!("Could not init the localization system: {other}"), + } + process::exit(99) + }); +} + #[allow(clippy::cognitive_complexity)] fn main() { uucore::panic::mute_sigpipe_panic(); @@ -143,21 +156,7 @@ fn main() { // binary to avoid the load of the flt // Could be something like: // #[cfg(not(feature = "only_english"))] - match locale::setup_localization(get_canonical_util_name(util)) { - Ok(()) => {} - Err(uucore::locale::LocalizationError::ParseResource { - error: err_msg, - snippet, - }) => { - // Now you have both `err_msg: ParserError` and `snippet: String` - eprintln!("Localization parse error at “{}”: {:?}", snippet, err_msg); - process::exit(1); - } - Err(other) => { - eprintln!("Could not init the localization system: {}", other); - process::exit(1); - } - } + setup_localization_or_exit(util); process::exit(uumain(vec![util_os].into_iter().chain(args))); } None => { @@ -261,6 +260,7 @@ fn gen_manpage( let command = if utility == "coreutils" { gen_coreutils_app(util_map) } else { + setup_localization_or_exit(utility); util_map.get(utility).unwrap().1() }; From c3f2247eca36c40725acc70f3cb72e75de14836b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 23:50:38 +0200 Subject: [PATCH 123/139] l10n: remove help_about --- src/uu/pinky/locales/en-US.ftl | 4 ++++ src/uu/pinky/src/pinky.rs | 20 +++++++------------- src/uu/uptime/locales/en-US.ftl | 3 +++ src/uu/uptime/src/uptime.rs | 19 +++++++------------ src/uu/users/locales/en-US.ftl | 2 ++ src/uu/users/src/users.rs | 15 ++++++--------- src/uu/who/locales/en-US.ftl | 3 +++ src/uu/who/src/who.rs | 19 +++++++------------ 8 files changed, 39 insertions(+), 46 deletions(-) diff --git a/src/uu/pinky/locales/en-US.ftl b/src/uu/pinky/locales/en-US.ftl index daa79f0b473..d3b62d123c2 100644 --- a/src/uu/pinky/locales/en-US.ftl +++ b/src/uu/pinky/locales/en-US.ftl @@ -1,2 +1,6 @@ pinky-about = Displays brief user information on Unix-based systems pinky-usage = pinky [OPTION]... [USER]... +pinky-about-musl-warning = Warning: When built with musl libc, the `pinky` utility may show incomplete + or missing user information due to musl's stub implementation of `utmpx` + functions. This limitation affects the ability to retrieve accurate details + about logged-in users. diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 2f687661154..3f0f13d975e 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -7,21 +7,10 @@ use clap::{Arg, ArgAction, Command}; use uucore::format_usage; +use uucore::locale::get_message; mod platform; -#[cfg(target_env = "musl")] -const ABOUT: &str = concat!( - help_about!("pinky.md"), - "\n\nWarning: When built with musl libc, the `pinky` utility may show incomplete \n", - "or missing user information due to musl's stub implementation of `utmpx` \n", - "functions. This limitation affects the ability to retrieve accurate details \n", - "about logged-in users." -); - -#[cfg(not(target_env = "musl"))] -use uucore::locale::get_message; - mod options { pub const LONG_FORMAT: &str = "long_format"; pub const OMIT_HOME_DIR: &str = "omit_home_dir"; @@ -40,9 +29,14 @@ mod options { use platform::uumain; pub fn uu_app() -> Command { + #[cfg(not(target_env = "musl"))] + let about = get_message("pinky-about"); + #[cfg(target_env = "musl")] + let about = get_message("pinky-about") + &get_message("pinky-about-musl-warning"); + Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(get_message("pinky-about")) + .about(about) .override_usage(format_usage(&get_message("pinky-usage"))) .infer_long_args(true) .disable_help_flag(true) diff --git a/src/uu/uptime/locales/en-US.ftl b/src/uu/uptime/locales/en-US.ftl index 2fe2b23ceb6..6746ee712bc 100644 --- a/src/uu/uptime/locales/en-US.ftl +++ b/src/uu/uptime/locales/en-US.ftl @@ -2,3 +2,6 @@ uptime-about = Display the current time, the length of time the system has been the number of users on the system, and the average number of jobs in the run queue over the last 1, 5 and 15 minutes. uptime-usage = uptime [OPTION]... +uptime-about-musl-warning = Warning: When built with musl libc, the `uptime` utility may show '0 users' + due to musl's stub implementation of utmpx functions. Boot time and load averages + are still calculated using alternative mechanisms. diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 081f18cab68..3bf1b8d7927 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -17,22 +17,12 @@ use uucore::uptime::*; use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser}; use uucore::format_usage; +use uucore::locale::get_message; #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] use uucore::utmpx::*; -#[cfg(target_env = "musl")] -const ABOUT: &str = concat!( - help_about!("uptime.md"), - "\n\nWarning: When built with musl libc, the `uptime` utility may show '0 users' \n", - "due to musl's stub implementation of utmpx functions. Boot time and load averages \n", - "are still calculated using alternative mechanisms." -); - -#[cfg(not(target_env = "musl"))] -use uucore::locale::get_message; - pub mod options { pub static SINCE: &str = "since"; pub static PATH: &str = "path"; @@ -74,9 +64,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { + #[cfg(not(target_env = "musl"))] + let about = get_message("uptime-about"); + #[cfg(target_env = "musl")] + let about = get_message("uptime-about") + &get_message("uptime-about-musl-warning"); + let cmd = Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(get_message("uptime-about")) + .about(about) .override_usage(format_usage(&get_message("uptime-usage"))) .infer_long_args(true) .arg( diff --git a/src/uu/users/locales/en-US.ftl b/src/uu/users/locales/en-US.ftl index a3d5786ac57..d35a99383c3 100644 --- a/src/uu/users/locales/en-US.ftl +++ b/src/uu/users/locales/en-US.ftl @@ -1,2 +1,4 @@ users-about = Print the user names of users currently logged in to the current host. users-usage = users [FILE] +users-about-musl-warning = Warning: When built with musl libc, the `users` utility may show '0 users', + due to musl's stub implementation of utmpx functions. diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 0af2f390b46..19b6c7c6d26 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -18,14 +18,6 @@ use utmp_classic::{UtmpEntry, parse_from_path}; #[cfg(not(target_os = "openbsd"))] use uucore::utmpx::{self, Utmpx}; -#[cfg(target_env = "musl")] -const ABOUT: &str = concat!( - help_about!("users.md"), - "\n\nWarning: When built with musl libc, the `users` utility may show '0 users' \n", - "due to musl's stub implementation of utmpx functions." -); - -#[cfg(not(target_env = "musl"))] use uucore::locale::get_message; #[cfg(target_os = "openbsd")] @@ -93,9 +85,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { + #[cfg(not(target_env = "musl"))] + let about = get_message("users-about"); + #[cfg(target_env = "musl")] + let about = get_message("users-about") + &get_message("users-about-musl-warning"); + Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(get_message("users-about")) + .about(about) .override_usage(format_usage(&get_message("users-usage"))) .infer_long_args(true) .arg( diff --git a/src/uu/who/locales/en-US.ftl b/src/uu/who/locales/en-US.ftl index 9b55893e6a9..7ed207f7201 100644 --- a/src/uu/who/locales/en-US.ftl +++ b/src/uu/who/locales/en-US.ftl @@ -1,2 +1,5 @@ who-about = Print information about users who are currently logged in. who-usage = who [OPTION]... [ FILE | ARG1 ARG2 ] +who-about-musl-warning = Note: When built with musl libc, the `who` utility will not display any + information about logged-in users. This is due to musl's stub implementation + of `utmpx` functions, which prevents access to the necessary data. diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 00e90ebce3d..ea3cc987382 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -7,6 +7,7 @@ use clap::{Arg, ArgAction, Command}; use uucore::format_usage; +use uucore::locale::get_message; mod platform; @@ -28,17 +29,6 @@ mod options { pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2 } -#[cfg(target_env = "musl")] -const ABOUT: &str = concat!( - help_about!("who.md"), - "\n\nNote: When built with musl libc, the `who` utility will not display any \n", - "information about logged-in users. This is due to musl's stub implementation \n", - "of `utmpx` functions, which prevents access to the necessary data." -); - -#[cfg(not(target_env = "musl"))] -use uucore::locale::get_message; - #[cfg(target_os = "linux")] static RUNLEVEL_HELP: &str = "print current runlevel"; #[cfg(not(target_os = "linux"))] @@ -48,9 +38,14 @@ static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non use platform::uumain; pub fn uu_app() -> Command { + #[cfg(not(target_env = "musl"))] + let about = get_message("who-about"); + #[cfg(target_env = "musl")] + let about = get_message("who-about") + &get_message("who-about-musl-warning"); + Command::new(uucore::util_name()) .version(uucore::crate_version!()) - .about(get_message("who-about")) + .about(about) .override_usage(format_usage(&get_message("who-usage"))) .infer_long_args(true) .arg( From 154250ac2457b03943dc6099c9b07e8932a2b480 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Jun 2025 23:56:06 +0200 Subject: [PATCH 124/139] refresh cargo.lock from fuzzing --- fuzz/Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index cd9aae5a479..765d3c0a9b5 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1418,6 +1418,7 @@ dependencies = [ "dunce", "fluent", "fluent-bundle", + "fluent-syntax", "glob", "hex", "itertools", From 005f0b7737d318172ca636704c14083f3cfa0d44 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 00:12:38 +0200 Subject: [PATCH 125/139] cargo fmt --- src/bin/coreutils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 5d219d75d39..9cb49d45c12 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -169,7 +169,6 @@ fn main() { match utils.get(util) { Some(&(uumain, _)) => { - let code = uumain( vec![util_os, OsString::from("--help")] .into_iter() From 578499fbada62fc968aff3867450033a8cbd357e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 09:16:10 +0200 Subject: [PATCH 126/139] coreutils.rs: spell ignore --- src/bin/coreutils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 9cb49d45c12..2821988c2e6 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore manpages mangen +// spell-checker:ignore manpages mangen prefixcat testcat use clap::{Arg, Command}; use clap_complete::Shell; From 4b776ba99573c5834af70000cf6b3fbe1da432e9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 22:48:16 +0200 Subject: [PATCH 127/139] l10n: also manage individual binary usages --- src/uucore/src/lib/lib.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 8dc49cadd97..469c5244422 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -143,6 +143,25 @@ pub fn disable_rust_signal_handlers() -> Result<(), Errno> { Ok(()) } +pub fn get_canonical_util_name(util_name: &str) -> &str { + // remove the "uu_" prefix + let util_name = &util_name[3..]; + match util_name { + // uu_test aliases - '[' is an alias for test + "[" => "test", + + // hashsum aliases - all these hash commands are aliases for hashsum + "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" + | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum" + | "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum", + + "dir" => "ls", // dir is an alias for ls + + // Default case - return the util name as is + _ => util_name, + } +} + /// Execute utility code for `util`. /// /// This macro expands to a main function that invokes the `uumain` function in `util` @@ -152,8 +171,21 @@ macro_rules! bin { ($util:ident) => { pub fn main() { use std::io::Write; + use uucore::locale; // suppress extraneous error output for SIGPIPE failures/panics uucore::panic::mute_sigpipe_panic(); + locale::setup_localization(uucore::get_canonical_util_name(stringify!($util))) + .unwrap_or_else(|err| { + match err { + uucore::locale::LocalizationError::ParseResource { + error: err_msg, + snippet, + } => eprintln!("Localization parse error at {snippet}: {err_msg}"), + other => eprintln!("Could not init the localization system: {other}"), + } + std::process::exit(99) + }); + // execute utility code let code = $util::uumain(uucore::args_os()); // (defensively) flush stdout for utility prior to exit; see From 5b2078b02dd5ebdad5f2ed64d773f99d7d38a071 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Jun 2025 22:30:52 +0200 Subject: [PATCH 128/139] l10n: Disable Unicode directional isolate characters (U+2068, U+2069) --- src/uucore/src/lib/mods/locale.rs | 45 ++++++++++++++++++------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 7bd3c098899..bb4040cd5b0 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -150,6 +150,13 @@ fn create_bundle( let mut bundle = FluentBundle::new(vec![locale.clone()]); + // Disable Unicode directional isolate characters (U+2068, U+2069) + // By default, Fluent wraps variables for security + // and proper text rendering in mixed-script environments (Arabic + English). + // Disabling gives cleaner output: "Welcome, Alice!" but reduces protection + // against bidirectional text attacks. Safe for English-only applications. + bundle.set_use_isolating(false); + bundle.add_resource(resource).map_err(|errs| { LocalizationError::Bundle(format!( "Failed to add resource to bundle for {}: {:?}", @@ -527,7 +534,7 @@ invalid-syntax = This is { $missing args.set("name", "Alice"); let result = localizer.format("welcome", Some(&args)); - assert_eq!(result, "Welcome, \u{2068}Alice\u{2069}!"); + assert_eq!(result, "Welcome, Alice!"); } #[test] @@ -687,7 +694,7 @@ invalid-syntax = This is { $missing args.insert("name".to_string(), "Bob".to_string()); let message = get_message_with_args("welcome", args); - assert_eq!(message, "Welcome, \u{2068}Bob\u{2069}!"); + assert_eq!(message, "Welcome, Bob!"); }) .join() .unwrap(); @@ -705,18 +712,17 @@ invalid-syntax = This is { $missing let mut args1 = HashMap::new(); args1.insert("count".to_string(), "1".to_string()); let message1 = get_message_with_args("count-items", args1); - assert_eq!(message1, "You have \u{2068}\u{2068}1\u{2069} item\u{2069}"); + assert_eq!(message1, "You have 1 item"); // Test plural let mut args2 = HashMap::new(); args2.insert("count".to_string(), "5".to_string()); let message2 = get_message_with_args("count-items", args2); - assert_eq!(message2, "You have \u{2068}\u{2068}5\u{2069} items\u{2069}"); + assert_eq!(message2, "You have 5 items"); }) .join() .unwrap(); } - #[test] fn test_detect_system_locale_from_lang_env() { // Save current LANG value @@ -917,13 +923,13 @@ invalid-syntax = This is { $missing let mut args = HashMap::new(); args.insert("name".to_string(), "田中".to_string()); let welcome = get_message_with_args("welcome", args); - assert_eq!(welcome, "ようこそ、\u{2068}田中\u{2069}さん!"); + assert_eq!(welcome, "ようこそ、田中さん!"); // Test Japanese count (no pluralization) let mut count_args = HashMap::new(); count_args.insert("count".to_string(), "5".to_string()); let count_message = get_message_with_args("count-items", count_args); - assert_eq!(count_message, "\u{2068}5\u{2069}個のアイテムがあります"); + assert_eq!(count_message, "5個のアイテムがあります"); }) .join() .unwrap(); @@ -946,37 +952,38 @@ invalid-syntax = This is { $missing let mut args = HashMap::new(); args.insert("name".to_string(), "أحمد".to_string()); let welcome = get_message_with_args("welcome", args); - assert_eq!(welcome, "أهلاً وسهلاً، \u{2068}أحمد\u{2069}!"); + + assert_eq!(welcome, "أهلاً وسهلاً، أحمد!"); // Test Arabic pluralization (zero case) let mut args_zero = HashMap::new(); args_zero.insert("count".to_string(), "0".to_string()); let message_zero = get_message_with_args("count-items", args_zero); - assert_eq!(message_zero, "لديك \u{2068}لا عناصر\u{2069}"); + assert_eq!(message_zero, "لديك لا عناصر"); // Test Arabic pluralization (one case) let mut args_one = HashMap::new(); args_one.insert("count".to_string(), "1".to_string()); let message_one = get_message_with_args("count-items", args_one); - assert_eq!(message_one, "لديك \u{2068}عنصر واحد\u{2069}"); + assert_eq!(message_one, "لديك عنصر واحد"); // Test Arabic pluralization (two case) let mut args_two = HashMap::new(); args_two.insert("count".to_string(), "2".to_string()); let message_two = get_message_with_args("count-items", args_two); - assert_eq!(message_two, "لديك \u{2068}عنصران\u{2069}"); + assert_eq!(message_two, "لديك عنصران"); // Test Arabic pluralization (few case - 3-10) let mut args_few = HashMap::new(); args_few.insert("count".to_string(), "5".to_string()); let message_few = get_message_with_args("count-items", args_few); - assert_eq!(message_few, "لديك \u{2068}\u{2068}5\u{2069} عناصر\u{2069}"); + assert_eq!(message_few, "لديك 5 عناصر"); // Test Arabic pluralization (other case - 11+) let mut args_many = HashMap::new(); args_many.insert("count".to_string(), "15".to_string()); let message_many = get_message_with_args("count-items", args_many); - assert_eq!(message_many, "لديك \u{2068}\u{2068}15\u{2069} عنصر\u{2069}"); + assert_eq!(message_many, "لديك 15 عنصر"); }) .join() .unwrap(); @@ -1002,23 +1009,23 @@ invalid-syntax = This is { $missing .join() .unwrap(); } - #[test] - fn test_unicode_directional_isolation() { + fn test_unicode_directional_isolation_disabled() { std::thread::spawn(|| { let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); init_localization(&locale, temp_dir.path()).unwrap(); - // Test that Latin script names are properly isolated in RTL context + // Test that Latin script names are NOT isolated in RTL context + // since we disabled Unicode directional isolation let mut args = HashMap::new(); args.insert("name".to_string(), "John Smith".to_string()); let message = get_message_with_args("welcome", args); - // The Latin name should be wrapped in directional isolate characters - assert!(message.contains("\u{2068}John Smith\u{2069}")); - assert_eq!(message, "أهلاً وسهلاً، \u{2068}John Smith\u{2069}!"); + // The Latin name should NOT be wrapped in directional isolate characters + assert!(!message.contains("\u{2068}John Smith\u{2069}")); + assert_eq!(message, "أهلاً وسهلاً، John Smith!"); }) .join() .unwrap(); From 8c1cc5b992d58bf9b67b79f162df1f0ca8ce11ec Mon Sep 17 00:00:00 2001 From: Etienne Cordonnier Date: Wed, 4 Jun 2025 23:59:14 +0200 Subject: [PATCH 129/139] stdbuf: avoid double "lib" prefix libstdbuf is currently compiled as e.g. liblibstdbuf.so, which is confusing. Compile the library as libstdbuf.so instead. Signed-off-by: Etienne Cordonnier --- src/uu/stdbuf/build.rs | 2 +- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/stdbuf/build.rs b/src/uu/stdbuf/build.rs index abb4069adbf..52202ed3345 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -69,7 +69,7 @@ fn main() { assert!(status.success(), "Failed to build libstdbuf"); // Copy the built library to OUT_DIR for include_bytes! to find - let lib_name = format!("liblibstdbuf{}", platform::DYLIB_EXT); + let lib_name = format!("libstdbuf{}", platform::DYLIB_EXT); let dest_path = Path::new(&out_dir).join(format!("libstdbuf{}", platform::DYLIB_EXT)); // Check multiple possible locations for the built library diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 01dd3a49d91..6460c441eec 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -11,7 +11,7 @@ categories.workspace = true edition.workspace = true [lib] -name = "libstdbuf" +name = "stdbuf" path = "src/libstdbuf.rs" crate-type = ["cdylib"] From 61bd11a55118458704c4cbbf4e628cd657238d3e Mon Sep 17 00:00:00 2001 From: Will Shuttleworth Date: Thu, 5 Jun 2025 09:38:51 +0000 Subject: [PATCH 130/139] stty: set control characters (#7931) * reworked arg processing. control character mappings are correctly grouped now, ie 'stty erase ^H' * stty: setting control chars to undefined (disabling them) is implemented * setting control chars * stty: can now set control chars. need to improve checks on valid mappings * stty: matches GNU in what control character mappings are allowed * stty: run rustfmt and remove extra comments * stty: setting control char code review fixes * stty: fix rustfmt errors * stty: more small edits after review * stty: refactor set control char changes for better testing * stty: fix ci error * stty: fix issues from code review --- src/uu/stty/src/flags.rs | 25 ++++ src/uu/stty/src/stty.rs | 274 ++++++++++++++++++++++++++++--------- tests/by-util/test_stty.rs | 33 +++++ 3 files changed, 267 insertions(+), 65 deletions(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index 79c85ceb257..d08029b5f17 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -26,6 +26,31 @@ use nix::sys::termios::{ SpecialCharacterIndices as S, }; +pub enum AllFlags<'a> { + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + Baud(u32), + #[cfg(not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )))] + Baud(BaudRate), + ControlFlags((&'a Flag, bool)), + InputFlags((&'a Flag, bool)), + LocalFlags((&'a Flag, bool)), + OutputFlags((&'a Flag, bool)), +} + pub const CONTROL_FLAGS: &[Flag] = &[ Flag::new("parenb", C::PARENB), Flag::new("parodd", C::PARODD), diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 5b5a9948cd5..6fb06acb8c7 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -3,10 +3,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime +// spell-checker:ignore clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime cflag lflag mod flags; +use crate::flags::AllFlags; use clap::{Arg, ArgAction, ArgMatches, Command}; use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort}; use nix::sys::termios::{ @@ -16,7 +17,6 @@ use nix::sys::termios::{ use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; use std::fs::File; use std::io::{self, Stdout, stdout}; -use std::ops::ControlFlow; use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; @@ -35,6 +35,8 @@ use uucore::locale::get_message; use flags::BAUD_RATES; use flags::{CONTROL_CHARS, CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; +const ASCII_DEL: u8 = 127; + #[derive(Clone, Copy, Debug)] pub struct Flag { name: &'static str, @@ -101,6 +103,22 @@ enum Device { Stdout(Stdout), } +enum ControlCharMappingError { + IntOutOfRange, + MultipleChars, +} + +enum ArgOptions<'a> { + Flags(AllFlags<'a>), + Mapping((SpecialCharacterIndices, u8)), +} + +impl<'a> From> for ArgOptions<'a> { + fn from(flag: AllFlags<'a>) -> Self { + ArgOptions::Flags(flag) + } +} + impl AsFd for Device { fn as_fd(&self) -> BorrowedFd<'_> { match self { @@ -208,19 +226,59 @@ fn stty(opts: &Options) -> UResult<()> { )); } - // TODO: Figure out the right error message for when tcgetattr fails - let mut termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes"); - - if let Some(settings) = &opts.settings { - for setting in settings { - if let ControlFlow::Break(false) = apply_setting(&mut termios, setting) { - return Err(USimpleError::new( - 1, - format!("invalid argument '{setting}'"), - )); + let mut valid_args: Vec = Vec::new(); + + if let Some(args) = &opts.settings { + let mut args_iter = args.iter(); + // iterate over args: skip to next arg if current one is a control char + while let Some(arg) = args_iter.next() { + // control char + if let Some(char_index) = cc_to_index(arg) { + if let Some(mapping) = args_iter.next() { + let cc_mapping = string_to_control_char(mapping).map_err(|e| { + let message = match e { + ControlCharMappingError::IntOutOfRange => format!( + "invalid integer argument: '{mapping}': Value too large for defined data type" + ), + ControlCharMappingError::MultipleChars => { + format!("invalid integer argument: '{mapping}'") + } + }; + USimpleError::new(1, message) + })?; + valid_args.push(ArgOptions::Mapping((char_index, cc_mapping))); + } else { + return Err(USimpleError::new(1, format!("missing argument to '{arg}'"))); + } + // non control char flag + } else if let Some(flag) = string_to_flag(arg) { + let remove_group = match flag { + AllFlags::Baud(_) => false, + AllFlags::ControlFlags((flag, remove)) => check_flag_group(flag, remove), + AllFlags::InputFlags((flag, remove)) => check_flag_group(flag, remove), + AllFlags::LocalFlags((flag, remove)) => check_flag_group(flag, remove), + AllFlags::OutputFlags((flag, remove)) => check_flag_group(flag, remove), + }; + if remove_group { + return Err(USimpleError::new(1, format!("invalid argument '{arg}'"))); + } + valid_args.push(flag.into()); + // not a valid control char or flag + } else { + return Err(USimpleError::new(1, format!("invalid argument '{arg}'"))); } } + // TODO: Figure out the right error message for when tcgetattr fails + let mut termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes"); + + // iterate over valid_args, match on the arg type, do the matching apply function + for arg in &valid_args { + match arg { + ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping), + ArgOptions::Flags(flag) => apply_setting(&mut termios, flag), + } + } tcsetattr( opts.file.as_fd(), nix::sys::termios::SetArg::TCSANOW, @@ -228,11 +286,17 @@ fn stty(opts: &Options) -> UResult<()> { ) .expect("Could not write terminal attributes"); } else { + // TODO: Figure out the right error message for when tcgetattr fails + let termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes"); print_settings(&termios, opts).expect("TODO: make proper error here from nix error"); } Ok(()) } +fn check_flag_group(flag: &Flag, remove: bool) -> bool { + remove && flag.group.is_some() +} + fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { let speed = cfgetospeed(termios); @@ -283,6 +347,70 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { Ok(()) } +fn cc_to_index(option: &str) -> Option { + for cc in CONTROL_CHARS { + if option == cc.0 { + return Some(cc.1); + } + } + None +} + +// return Some(flag) if the input is a valid flag, None if not +fn string_to_flag(option: &str) -> Option { + // BSDs use a u32 for the baud rate, so any decimal number applies. + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + if let Ok(n) = option.parse::() { + return Some(AllFlags::Baud(n)); + } + + #[cfg(not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )))] + for (text, baud_rate) in BAUD_RATES { + if *text == option { + return Some(AllFlags::Baud(*baud_rate)); + } + } + + let remove = option.starts_with('-'); + let name = option.trim_start_matches('-'); + + for cflag in CONTROL_FLAGS { + if name == cflag.name { + return Some(AllFlags::ControlFlags((cflag, remove))); + } + } + for iflag in INPUT_FLAGS { + if name == iflag.name { + return Some(AllFlags::InputFlags((iflag, remove))); + } + } + for lflag in LOCAL_FLAGS { + if name == lflag.name { + return Some(AllFlags::LocalFlags((lflag, remove))); + } + } + for oflag in OUTPUT_FLAGS { + if name == oflag.name { + return Some(AllFlags::OutputFlags((oflag, remove))); + } + } + None +} + fn control_char_to_string(cc: nix::libc::cc_t) -> nix::Result { if cc == 0 { return Ok("".to_string()); @@ -390,55 +518,25 @@ fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag< } /// Apply a single setting -/// -/// The value inside the `Break` variant of the `ControlFlow` indicates whether -/// the setting has been applied. -fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow { - apply_baud_rate_flag(termios, s)?; - - let (remove, name) = match s.strip_prefix('-') { - Some(s) => (true, s), - None => (false, s), - }; - apply_flag(termios, CONTROL_FLAGS, name, remove)?; - apply_flag(termios, INPUT_FLAGS, name, remove)?; - apply_flag(termios, OUTPUT_FLAGS, name, remove)?; - apply_flag(termios, LOCAL_FLAGS, name, remove)?; - ControlFlow::Break(false) -} - -/// Apply a flag to a slice of flags -/// -/// The value inside the `Break` variant of the `ControlFlow` indicates whether -/// the setting has been applied. -fn apply_flag( - termios: &mut Termios, - flags: &[Flag], - input: &str, - remove: bool, -) -> ControlFlow { - for Flag { - name, flag, group, .. - } in flags - { - if input == *name { - // Flags with groups cannot be removed - // Since the name matches, we can short circuit and don't have to check the other flags. - if remove && group.is_some() { - return ControlFlow::Break(false); - } - // If there is a group, the bits for that group should be cleared before applying the flag - if let Some(group) = group { - group.apply(termios, false); - } - flag.apply(termios, !remove); - return ControlFlow::Break(true); +fn apply_setting(termios: &mut Termios, setting: &AllFlags) { + match setting { + AllFlags::Baud(_) => apply_baud_rate_flag(termios, setting), + AllFlags::ControlFlags((setting, disable)) => { + setting.flag.apply(termios, !disable); + } + AllFlags::InputFlags((setting, disable)) => { + setting.flag.apply(termios, !disable); + } + AllFlags::LocalFlags((setting, disable)) => { + setting.flag.apply(termios, !disable); + } + AllFlags::OutputFlags((setting, disable)) => { + setting.flag.apply(termios, !disable); } } - ControlFlow::Continue(()) } -fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow { +fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) { // BSDs use a u32 for the baud rate, so any decimal number applies. #[cfg(any( target_os = "freebsd", @@ -448,9 +546,8 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow target_os = "netbsd", target_os = "openbsd" ))] - if let Ok(n) = input.parse::() { - cfsetospeed(termios, n).expect("Failed to set baud rate"); - return ControlFlow::Break(true); + if let AllFlags::Baud(n) = input { + cfsetospeed(termios, *n).expect("Failed to set baud rate"); } // Other platforms use an enum. @@ -462,13 +559,60 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &str) -> ControlFlow target_os = "netbsd", target_os = "openbsd" )))] - for (text, baud_rate) in BAUD_RATES { - if *text == input { - cfsetospeed(termios, *baud_rate).expect("Failed to set baud rate"); - return ControlFlow::Break(true); + if let AllFlags::Baud(br) = input { + cfsetospeed(termios, *br).expect("Failed to set baud rate"); + } +} + +fn apply_char_mapping(termios: &mut Termios, mapping: &(SpecialCharacterIndices, u8)) { + termios.control_chars[mapping.0 as usize] = mapping.1; +} + +// GNU stty defines some valid values for the control character mappings +// 1. Standard character, can be a a single char (ie 'C') or hat notation (ie '^C') +// 2. Integer +// a. hexadecimal, prefixed by '0x' +// b. octal, prefixed by '0' +// c. decimal, no prefix +// 3. Disabling the control character: '^-' or 'undef' +// +// This function returns the ascii value of valid control chars, or ControlCharMappingError if invalid +fn string_to_control_char(s: &str) -> Result { + if s == "undef" || s == "^-" { + return Ok(0); + } + + // try to parse integer (hex, octal, or decimal) + let ascii_num = if let Some(hex) = s.strip_prefix("0x") { + u32::from_str_radix(hex, 16).ok() + } else if let Some(octal) = s.strip_prefix("0") { + u32::from_str_radix(octal, 8).ok() + } else { + s.parse::().ok() + }; + + if let Some(val) = ascii_num { + if val > 255 { + return Err(ControlCharMappingError::IntOutOfRange); + } else { + return Ok(val as u8); + } + } + // try to parse ^ or just + let mut chars = s.chars(); + match (chars.next(), chars.next()) { + (Some('^'), Some(c)) => { + // special case: ascii value of '^?' is greater than '?' + if c == '?' { + return Ok(ASCII_DEL); + } + // subtract by '@' to turn the char into the ascii value of '^' + Ok((c.to_ascii_uppercase() as u8).wrapping_sub(b'@')) } + (Some(c), None) => Ok(c as u8), + (Some(_), Some(_)) => Err(ControlCharMappingError::MultipleChars), + _ => unreachable!("No arguments provided: must have been caught earlier"), } - ControlFlow::Continue(()) } pub fn uu_app() -> Command { diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index 7ccc56e5dee..e9e455e3208 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -64,3 +64,36 @@ fn save_and_all() { "the options for verbose and stty-readable output styles are mutually exclusive", ); } + +#[test] +fn no_mapping() { + new_ucmd!() + .args(&["intr"]) + .fails() + .stderr_contains("missing argument to 'intr'"); +} + +#[test] +fn invalid_mapping() { + new_ucmd!() + .args(&["intr", "cc"]) + .fails() + .stderr_contains("invalid integer argument: 'cc'"); + + new_ucmd!() + .args(&["intr", "256"]) + .fails() + .stderr_contains("invalid integer argument: '256': Value too large for defined data type"); + + new_ucmd!() + .args(&["intr", "0x100"]) + .fails() + .stderr_contains( + "invalid integer argument: '0x100': Value too large for defined data type", + ); + + new_ucmd!() + .args(&["intr", "0400"]) + .fails() + .stderr_contains("invalid integer argument: '0400': Value too large for defined data type"); +} From 51c5c9c59a0eb5b2196473a2954141c9f46b2f8f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 5 Jun 2025 17:05:48 +0200 Subject: [PATCH 131/139] rm: rename two consts for better readability OPT_PROMPT => OPT_PROMPT_ALWAYS, OPT_PROMPT_MORE => OPT_PROMPT_ONCE --- src/uu/rm/src/rm.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 9e07bc077a4..d7340faa1c7 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -95,8 +95,8 @@ static OPT_FORCE: &str = "force"; static OPT_NO_PRESERVE_ROOT: &str = "no-preserve-root"; static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; static OPT_PRESERVE_ROOT: &str = "preserve-root"; -static OPT_PROMPT: &str = "prompt"; -static OPT_PROMPT_MORE: &str = "prompt-more"; +static OPT_PROMPT_ALWAYS: &str = "prompt-always"; +static OPT_PROMPT_ONCE: &str = "prompt-once"; static OPT_RECURSIVE: &str = "recursive"; static OPT_VERBOSE: &str = "verbose"; static PRESUME_INPUT_TTY: &str = "-presume-input-tty"; @@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // If -f(--force) is before any -i (or variants) we want prompts else no prompts let force_prompt_never = force_flag && { let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); - ![OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] + ![OPT_PROMPT_ALWAYS, OPT_PROMPT_ONCE, OPT_INTERACTIVE] .iter() .any(|flag| { matches.value_source(flag) == Some(ValueSource::CommandLine) @@ -136,9 +136,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { interactive: { if force_prompt_never { InteractiveMode::Never - } else if matches.get_flag(OPT_PROMPT) { + } else if matches.get_flag(OPT_PROMPT_ALWAYS) { InteractiveMode::Always - } else if matches.get_flag(OPT_PROMPT_MORE) { + } else if matches.get_flag(OPT_PROMPT_ONCE) { InteractiveMode::Once } else if matches.contains_id(OPT_INTERACTIVE) { match matches.get_one::(OPT_INTERACTIVE).unwrap().as_str() { @@ -210,18 +210,18 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_PROMPT) + Arg::new(OPT_PROMPT_ALWAYS) .short('i') .help("prompt before every removal") - .overrides_with_all([OPT_PROMPT_MORE, OPT_INTERACTIVE]) + .overrides_with_all([OPT_PROMPT_ONCE, OPT_INTERACTIVE]) .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_PROMPT_MORE) + Arg::new(OPT_PROMPT_ONCE) .short('I') .help("prompt once before removing more than three files, or when removing recursively. \ Less intrusive than -i, while still giving some protection against most mistakes") - .overrides_with_all([OPT_PROMPT, OPT_INTERACTIVE]) + .overrides_with_all([OPT_PROMPT_ALWAYS, OPT_INTERACTIVE]) .action(ArgAction::SetTrue), ) .arg( @@ -235,7 +235,7 @@ pub fn uu_app() -> Command { .num_args(0..=1) .require_equals(true) .default_missing_value("always") - .overrides_with_all([OPT_PROMPT, OPT_PROMPT_MORE]), + .overrides_with_all([OPT_PROMPT_ALWAYS, OPT_PROMPT_ONCE]), ) .arg( Arg::new(OPT_ONE_FILE_SYSTEM) From 8f5f22eb86be880aa5ea1a27843bd75e569eb348 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 23:36:02 +0200 Subject: [PATCH 132/139] l10n: port basename to translation + add french --- src/uu/basename/locales/en-US.ftl | 9 +++++++++ src/uu/basename/locales/fr-FR.ftl | 13 +++++++++++++ src/uu/basename/src/basename.rs | 19 +++++++++++++------ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 src/uu/basename/locales/fr-FR.ftl diff --git a/src/uu/basename/locales/en-US.ftl b/src/uu/basename/locales/en-US.ftl index fd0a8335b42..6ab5f364f71 100644 --- a/src/uu/basename/locales/en-US.ftl +++ b/src/uu/basename/locales/en-US.ftl @@ -2,3 +2,12 @@ basename-about = Print NAME with any leading directory components removed If specified, also remove a trailing SUFFIX basename-usage = basename [-z] NAME [SUFFIX] basename OPTION... NAME... + +# Error messages +basename-error-missing-operand = missing operand +basename-error-extra-operand = extra operand { $operand } + +# Help text for command-line arguments +basename-help-multiple = support multiple arguments and treat each as a NAME +basename-help-suffix = remove a trailing SUFFIX; implies -a +basename-help-zero = end each output line with NUL, not newline diff --git a/src/uu/basename/locales/fr-FR.ftl b/src/uu/basename/locales/fr-FR.ftl new file mode 100644 index 00000000000..69b03e6db72 --- /dev/null +++ b/src/uu/basename/locales/fr-FR.ftl @@ -0,0 +1,13 @@ +basename-about = Affiche NOM sans les composants de répertoire précédents + Si spécifié, supprime également un SUFFIXE final +basename-usage = basename [-z] NOM [SUFFIXE] + basename OPTION... NOM... + +# Messages d'erreur +basename-error-missing-operand = opérande manquant +basename-error-extra-operand = opérande supplémentaire { $operand } + +# Texte d'aide pour les arguments de ligne de commande +basename-help-multiple = prend en charge plusieurs arguments et traite chacun comme un NOM +basename-help-suffix = supprime un SUFFIXE final ; implique -a +basename-help-zero = termine chaque ligne de sortie avec NUL, pas nouvelle ligne diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index c39c329df87..6daf981c247 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -6,13 +6,14 @@ // spell-checker:ignore (ToDO) fullname use clap::{Arg, ArgAction, Command}; +use std::collections::HashMap; use std::path::{PathBuf, is_separator}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; use uucore::format_usage; use uucore::line_ending::LineEnding; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; pub mod options { pub static MULTIPLE: &str = "multiple"; @@ -37,7 +38,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .collect::>(); if name_args.is_empty() { - return Err(UUsageError::new(1, "missing operand".to_string())); + return Err(UUsageError::new( + 1, + get_message("basename-error-missing-operand"), + )); } let multiple_paths = matches.get_one::(options::SUFFIX).is_some() || matches.get_flag(options::MULTIPLE); @@ -55,7 +59,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { _ => { return Err(UUsageError::new( 1, - format!("extra operand {}", name_args[2].quote()), + get_message_with_args( + "basename-error-extra-operand", + HashMap::from([("operand".to_string(), name_args[2].quote().to_string())]), + ), )); } } @@ -82,7 +89,7 @@ pub fn uu_app() -> Command { Arg::new(options::MULTIPLE) .short('a') .long(options::MULTIPLE) - .help("support multiple arguments and treat each as a NAME") + .help(get_message("basename-help-multiple")) .action(ArgAction::SetTrue) .overrides_with(options::MULTIPLE), ) @@ -98,14 +105,14 @@ pub fn uu_app() -> Command { .short('s') .long(options::SUFFIX) .value_name("SUFFIX") - .help("remove a trailing SUFFIX; implies -a") + .help(get_message("basename-help-suffix")) .overrides_with(options::SUFFIX), ) .arg( Arg::new(options::ZERO) .short('z') .long(options::ZERO) - .help("end each output line with NUL, not newline") + .help(get_message("basename-help-zero")) .action(ArgAction::SetTrue) .overrides_with(options::ZERO), ) From 991144a79a902f6554c338fdb321a4088821fcaf Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Jun 2025 00:02:13 +0200 Subject: [PATCH 133/139] l10n: port nproc to translation + add french --- src/uu/nproc/locales/en-US.ftl | 7 +++++++ src/uu/nproc/locales/fr-FR.ftl | 11 +++++++++++ src/uu/nproc/src/nproc.rs | 17 ++++++++++++----- 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 src/uu/nproc/locales/fr-FR.ftl diff --git a/src/uu/nproc/locales/en-US.ftl b/src/uu/nproc/locales/en-US.ftl index 42522997e61..0a7b97cc64d 100644 --- a/src/uu/nproc/locales/en-US.ftl +++ b/src/uu/nproc/locales/en-US.ftl @@ -2,3 +2,10 @@ nproc-about = Print the number of cores available to the current process. If the OMP_NUM_THREADS or OMP_THREAD_LIMIT environment variables are set, then they will determine the minimum and maximum returned value respectively. nproc-usage = nproc [OPTIONS]... + +# Error messages +nproc-error-invalid-number = { $value } is not a valid number: { $error } + +# Help text for command-line arguments +nproc-help-all = print the number of cores available to the system +nproc-help-ignore = ignore up to N cores diff --git a/src/uu/nproc/locales/fr-FR.ftl b/src/uu/nproc/locales/fr-FR.ftl new file mode 100644 index 00000000000..8021cab081f --- /dev/null +++ b/src/uu/nproc/locales/fr-FR.ftl @@ -0,0 +1,11 @@ +nproc-about = Affiche le nombre de cœurs disponibles pour le processus actuel. + Si les variables d'environnement OMP_NUM_THREADS ou OMP_THREAD_LIMIT sont définies, + elles détermineront respectivement la valeur minimale et maximale retournée. +nproc-usage = nproc [OPTIONS]... + +# Messages d'erreur +nproc-error-invalid-number = { $value } n'est pas un nombre valide : { $error } + +# Texte d'aide pour les arguments de ligne de commande +nproc-help-all = affiche le nombre de cœurs disponibles pour le système +nproc-help-ignore = ignore jusqu'à N cœurs diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index beed928349f..f6e84c3960d 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -6,11 +6,12 @@ // spell-checker:ignore (ToDO) NPROCESSORS nprocs numstr sysconf use clap::{Arg, ArgAction, Command}; +use std::collections::HashMap; use std::{env, thread}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; #[cfg(any(target_os = "linux", target_os = "android"))] pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; @@ -29,12 +30,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; let ignore = match matches.get_one::(OPT_IGNORE) { - Some(numstr) => match numstr.trim().parse() { + Some(numstr) => match numstr.trim().parse::() { Ok(num) => num, Err(e) => { return Err(USimpleError::new( 1, - format!("{} is not a valid number: {e}", numstr.quote()), + get_message_with_args( + "nproc-error-invalid-number", + HashMap::from([ + ("value".to_string(), numstr.quote().to_string()), + ("error".to_string(), e.to_string()), + ]), + ), )); } }, @@ -98,14 +105,14 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_ALL) .long(OPT_ALL) - .help("print the number of cores available to the system") + .help(get_message("nproc-help-all")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_IGNORE) .long(OPT_IGNORE) .value_name("N") - .help("ignore up to N cores"), + .help(get_message("nproc-help-ignore")), ) } From 0d00960522d6ddde39049bb08ae7da64ff710e73 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Jun 2025 00:06:20 +0200 Subject: [PATCH 134/139] l10n: port yes to translation + add french --- src/uu/yes/locales/en-US.ftl | 4 ++++ src/uu/yes/locales/fr-FR.ftl | 6 ++++++ src/uu/yes/src/yes.rs | 13 ++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/uu/yes/locales/fr-FR.ftl diff --git a/src/uu/yes/locales/en-US.ftl b/src/uu/yes/locales/en-US.ftl index d4d0806b18b..9daaaa820b0 100644 --- a/src/uu/yes/locales/en-US.ftl +++ b/src/uu/yes/locales/en-US.ftl @@ -1,2 +1,6 @@ yes-about = Repeatedly display a line with STRING (or 'y') yes-usage = yes [STRING]... + +# Error messages +yes-error-standard-output = standard output: { $error } +yes-error-invalid-utf8 = arguments contain invalid UTF-8 diff --git a/src/uu/yes/locales/fr-FR.ftl b/src/uu/yes/locales/fr-FR.ftl new file mode 100644 index 00000000000..c3272b80903 --- /dev/null +++ b/src/uu/yes/locales/fr-FR.ftl @@ -0,0 +1,6 @@ +yes-about = Affiche de façon répétée une ligne avec CHAÎNE (ou 'y') +yes-usage = yes [CHAÎNE]... + +# Messages d'erreur +yes-error-standard-output = sortie standard : { $error } +yes-error-invalid-utf8 = les arguments contiennent de l'UTF-8 invalide diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index add3f70276d..7332297cf2e 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -6,6 +6,7 @@ // cSpell:ignore strs use clap::{Arg, ArgAction, Command, builder::ValueParser}; +use std::collections::HashMap; use std::error::Error; use std::ffi::OsString; use std::io::{self, Write}; @@ -14,7 +15,7 @@ use uucore::format_usage; #[cfg(unix)] use uucore::signals::enable_pipe_errors; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; // it's possible that using a smaller or larger buffer might provide better performance on some // systems, but honestly this is good enough @@ -31,7 +32,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match exec(&buffer) { Ok(()) => Ok(()), Err(err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()), - Err(err) => Err(USimpleError::new(1, format!("standard output: {err}"))), + Err(err) => Err(USimpleError::new( + 1, + get_message_with_args( + "yes-error-standard-output", + HashMap::from([("error".to_string(), err.to_string())]), + ), + )), } } @@ -77,7 +84,7 @@ fn args_into_buffer<'a>( for part in itertools::intersperse(i.map(|a| a.to_str()), Some(" ")) { let bytes = match part { Some(part) => part.as_bytes(), - None => return Err("arguments contain invalid UTF-8".into()), + None => return Err(get_message("yes-error-invalid-utf8").into()), }; buf.extend_from_slice(bytes); } From 86357de0e96cba15d35fb7c5db7c79721621e5f1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Jun 2025 23:14:49 +0200 Subject: [PATCH 135/139] l10n: port uname to translation + add french --- src/uu/uname/locales/en-US.ftl | 17 +++++++++++++++++ src/uu/uname/locales/fr-FR.ftl | 20 ++++++++++++++++++++ src/uu/uname/src/uname.rs | 30 ++++++++++++++---------------- 3 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 src/uu/uname/locales/fr-FR.ftl diff --git a/src/uu/uname/locales/en-US.ftl b/src/uu/uname/locales/en-US.ftl index 78faa1c35e4..b985518427d 100644 --- a/src/uu/uname/locales/en-US.ftl +++ b/src/uu/uname/locales/en-US.ftl @@ -1,3 +1,20 @@ uname-about = Print certain system information. With no OPTION, same as -s. uname-usage = uname [OPTION]... + +# Error messages +uname-error-cannot-get-system-name = cannot get system name + +# Default values +uname-unknown = unknown + +# Help text for command-line arguments +uname-help-all = Behave as though all of the options -mnrsvo were specified. +uname-help-kernel-name = print the kernel name. +uname-help-nodename = print the nodename (the nodename may be a name that the system is known by to a communications network). +uname-help-kernel-release = print the operating system release. +uname-help-kernel-version = print the operating system version. +uname-help-machine = print the machine hardware name. +uname-help-os = print the operating system name. +uname-help-processor = print the processor type (non-portable) +uname-help-hardware-platform = print the hardware platform (non-portable) diff --git a/src/uu/uname/locales/fr-FR.ftl b/src/uu/uname/locales/fr-FR.ftl new file mode 100644 index 00000000000..fc48f3f8ba6 --- /dev/null +++ b/src/uu/uname/locales/fr-FR.ftl @@ -0,0 +1,20 @@ +uname-about = Affiche certaines informations système. + Sans OPTION, identique à -s. +uname-usage = uname [OPTION]... + +# Messages d'erreur +uname-error-cannot-get-system-name = impossible d'obtenir le nom du système + +# Valeurs par défaut +uname-unknown = inconnu + +# Texte d'aide pour les arguments de ligne de commande +uname-help-all = Se comporte comme si toutes les options -mnrsvo étaient spécifiées. +uname-help-kernel-name = affiche le nom du noyau. +uname-help-nodename = affiche le nom du nœud (le nom du nœud peut être un nom par lequel le système est connu d'un réseau de communications). +uname-help-kernel-release = affiche la version du système d'exploitation. +uname-help-kernel-version = affiche la version du système d'exploitation. +uname-help-machine = affiche le nom du matériel de la machine. +uname-help-os = affiche le nom du système d'exploitation. +uname-help-processor = affiche le type de processeur (non portable) +uname-help-hardware-platform = affiche la plateforme matérielle (non portable) diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index cc3a4f471eb..d89f0d93f7b 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -59,8 +59,9 @@ impl UNameOutput { } pub fn new(opts: &Options) -> UResult { - let uname = - PlatformInfo::new().map_err(|_e| USimpleError::new(1, "cannot get system name"))?; + let uname = PlatformInfo::new().map_err(|_e| { + USimpleError::new(1, get_message("uname-error-cannot-get-system-name")) + })?; let none = !(opts.all || opts.kernel_name || opts.nodename @@ -90,11 +91,11 @@ impl UNameOutput { // This option is unsupported on modern Linux systems // See: https://lists.gnu.org/archive/html/bug-coreutils/2005-09/msg00063.html - let processor = opts.processor.then(|| "unknown".to_string()); + let processor = opts.processor.then(|| get_message("uname-unknown")); // This option is unsupported on modern Linux systems // See: https://lists.gnu.org/archive/html/bug-coreutils/2005-09/msg00063.html - let hardware_platform = opts.hardware_platform.then(|| "unknown".to_string()); + let hardware_platform = opts.hardware_platform.then(|| get_message("uname-unknown")); Ok(Self { kernel_name, @@ -151,7 +152,7 @@ pub fn uu_app() -> Command { Arg::new(options::ALL) .short('a') .long(options::ALL) - .help("Behave as though all of the options -mnrsvo were specified.") + .help(get_message("uname-help-all")) .action(ArgAction::SetTrue), ) .arg( @@ -159,17 +160,14 @@ pub fn uu_app() -> Command { .short('s') .long(options::KERNEL_NAME) .alias("sysname") // Obsolescent option in GNU uname - .help("print the kernel name.") + .help(get_message("uname-help-kernel-name")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::NODENAME) .short('n') .long(options::NODENAME) - .help( - "print the nodename (the nodename may be a name that the system \ - is known by to a communications network).", - ) + .help(get_message("uname-help-nodename")) .action(ArgAction::SetTrue), ) .arg( @@ -177,35 +175,35 @@ pub fn uu_app() -> Command { .short('r') .long(options::KERNEL_RELEASE) .alias("release") // Obsolescent option in GNU uname - .help("print the operating system release.") + .help(get_message("uname-help-kernel-release")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::KERNEL_VERSION) .short('v') .long(options::KERNEL_VERSION) - .help("print the operating system version.") + .help(get_message("uname-help-kernel-version")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::MACHINE) .short('m') .long(options::MACHINE) - .help("print the machine hardware name.") + .help(get_message("uname-help-machine")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::OS) .short('o') .long(options::OS) - .help("print the operating system name.") + .help(get_message("uname-help-os")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::PROCESSOR) .short('p') .long(options::PROCESSOR) - .help("print the processor type (non-portable)") + .help(get_message("uname-help-processor")) .action(ArgAction::SetTrue) .hide(true), ) @@ -213,7 +211,7 @@ pub fn uu_app() -> Command { Arg::new(options::HARDWARE_PLATFORM) .short('i') .long(options::HARDWARE_PLATFORM) - .help("print the hardware platform (non-portable)") + .help(get_message("uname-help-hardware-platform")) .action(ArgAction::SetTrue) .hide(true), ) From 980c81a6ce1a159dfcfe815434a580c054f1af1a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Jun 2025 23:26:23 +0200 Subject: [PATCH 136/139] l10n: port whoami to translation + add french --- src/uu/whoami/locales/en-US.ftl | 4 ++++ src/uu/whoami/locales/fr-FR.ftl | 5 +++++ src/uu/whoami/src/whoami.rs | 8 +++----- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/uu/whoami/locales/fr-FR.ftl diff --git a/src/uu/whoami/locales/en-US.ftl b/src/uu/whoami/locales/en-US.ftl index aedac774c31..9d9b2613064 100644 --- a/src/uu/whoami/locales/en-US.ftl +++ b/src/uu/whoami/locales/en-US.ftl @@ -1 +1,5 @@ whoami-about = Print the current username. + +# Error messages +whoami-error-failed-to-print = failed to print username +whoami-error-failed-to-get = failed to get username diff --git a/src/uu/whoami/locales/fr-FR.ftl b/src/uu/whoami/locales/fr-FR.ftl new file mode 100644 index 00000000000..bb5ec1d0237 --- /dev/null +++ b/src/uu/whoami/locales/fr-FR.ftl @@ -0,0 +1,5 @@ +whoami-about = Affiche le nom d'utilisateur actuel. + +# Messages d'erreur +whoami-error-failed-to-print = échec de l'affichage du nom d'utilisateur +whoami-error-failed-to-get = échec de l'obtention du nom d'utilisateur diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 701d3a107ea..7e4276e9084 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -3,10 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::ffi::OsString; - use clap::Command; - +use std::ffi::OsString; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; use uucore::locale::get_message; @@ -17,13 +15,13 @@ mod platform; pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().try_get_matches_from(args)?; let username = whoami()?; - println_verbatim(username).map_err_context(|| "failed to print username".into())?; + println_verbatim(username).map_err_context(|| get_message("whoami-error-failed-to-print"))?; Ok(()) } /// Get the current username pub fn whoami() -> UResult { - platform::get_username().map_err_context(|| "failed to get username".into()) + platform::get_username().map_err_context(|| get_message("whoami-error-failed-to-get")) } pub fn uu_app() -> Command { From fa4e46ad1515de59ef866d16dcccb49b7620440c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Jun 2025 23:30:54 +0200 Subject: [PATCH 137/139] l10n: port id to translation + add french --- src/uu/id/locales/en-US.ftl | 40 +++++++++ src/uu/id/locales/fr-FR.ftl | 50 +++++++++++ src/uu/id/src/id.rs | 160 +++++++++++++++++++++++------------- 3 files changed, 195 insertions(+), 55 deletions(-) create mode 100644 src/uu/id/locales/fr-FR.ftl diff --git a/src/uu/id/locales/en-US.ftl b/src/uu/id/locales/en-US.ftl index fd7b8348b1f..37b477c17a8 100644 --- a/src/uu/id/locales/en-US.ftl +++ b/src/uu/id/locales/en-US.ftl @@ -8,3 +8,43 @@ id-after-help = The id utility displays the user and group names and numeric IDs If a user (login name or user ID) is specified, the user and group IDs of that user are displayed. In this case, the real and effective IDs are assumed to be the same. + +# Context help text +id-context-help-disabled = print only the security context of the process (not enabled) +id-context-help-enabled = print only the security context of the process + +# Error messages +id-error-names-real-ids-require-flags = printing only names or real IDs requires -u, -g, or -G +id-error-zero-not-permitted-default = option --zero not permitted in default format +id-error-cannot-print-context-with-user = cannot print security context when user specified +id-error-cannot-get-context = can't get process context +id-error-context-selinux-only = --context (-Z) works only on an SELinux-enabled kernel +id-error-no-such-user = { $user }: no such user +id-error-cannot-find-group-name = cannot find name for group ID { $gid } +id-error-cannot-find-user-name = cannot find name for user ID { $uid } +id-error-audit-retrieve = couldn't retrieve information + +# Help text for command-line arguments +id-help-audit = Display the process audit user ID and other process audit properties, + which requires privilege (not available on Linux). +id-help-user = Display only the effective user ID as a number. +id-help-group = Display only the effective group ID as a number +id-help-groups = Display only the different group IDs as white-space separated numbers, + in no particular order. +id-help-human-readable = Make the output human-readable. Each display is on a separate line. +id-help-name = Display the name of the user or group ID for the -G, -g and -u options + instead of the number. + If any of the ID numbers cannot be mapped into + names, the number will be displayed as usual. +id-help-password = Display the id as a password file entry. +id-help-real = Display the real ID for the -G, -g and -u options instead of + the effective ID. +id-help-zero = delimit entries with NUL characters, not whitespace; + not permitted in default format + +# Output labels +id-output-uid = uid +id-output-groups = groups +id-output-login = login +id-output-euid = euid +id-output-context = context diff --git a/src/uu/id/locales/fr-FR.ftl b/src/uu/id/locales/fr-FR.ftl new file mode 100644 index 00000000000..f69df611e22 --- /dev/null +++ b/src/uu/id/locales/fr-FR.ftl @@ -0,0 +1,50 @@ +id-about = Affiche les informations d'utilisateur et de groupe pour chaque UTILISATEUR spécifié, + ou (si UTILISATEUR est omis) pour l'utilisateur actuel. +id-usage = id [OPTION]... [UTILISATEUR]... +id-after-help = L'utilitaire id affiche les noms d'utilisateur et de groupe ainsi que leurs ID numériques + du processus appelant, vers la sortie standard. Si les ID réels et effectifs sont + différents, les deux sont affichés, sinon seul l'ID réel est affiché. + + Si un utilisateur (nom de connexion ou ID utilisateur) est spécifié, les ID utilisateur et groupe + de cet utilisateur sont affichés. Dans ce cas, les ID réels et effectifs sont + supposés être identiques. + +# Texte d'aide pour le contexte +id-context-help-disabled = affiche uniquement le contexte de sécurité du processus (non activé) +id-context-help-enabled = affiche uniquement le contexte de sécurité du processus + +# Messages d'erreur +id-error-names-real-ids-require-flags = l'affichage des noms uniquement ou des ID réels nécessite -u, -g, ou -G +id-error-zero-not-permitted-default = l'option --zero n'est pas autorisée dans le format par défaut +id-error-cannot-print-context-with-user = impossible d'afficher le contexte de sécurité quand un utilisateur est spécifié +id-error-cannot-get-context = impossible d'obtenir le contexte du processus +id-error-context-selinux-only = --context (-Z) ne fonctionne que sur un noyau avec SELinux activé +id-error-no-such-user = { $user } : utilisateur inexistant +id-error-cannot-find-group-name = impossible de trouver le nom pour l'ID de groupe { $gid } +id-error-cannot-find-user-name = impossible de trouver le nom pour l'ID utilisateur { $uid } +id-error-audit-retrieve = impossible de récupérer les informations + +# Texte d'aide pour les arguments de ligne de commande +id-help-audit = Affiche l'ID utilisateur d'audit du processus et autres propriétés d'audit, + ce qui nécessite des privilèges (non disponible sous Linux). +id-help-user = Affiche uniquement l'ID utilisateur effectif sous forme de nombre. +id-help-group = Affiche uniquement l'ID de groupe effectif sous forme de nombre +id-help-groups = Affiche uniquement les différents ID de groupe sous forme de nombres séparés par des espaces, + dans un ordre quelconque. +id-help-human-readable = Rend la sortie lisible par l'humain. Chaque affichage est sur une ligne séparée. +id-help-name = Affiche le nom de l'ID utilisateur ou groupe pour les options -G, -g et -u + au lieu du nombre. + Si certains ID numériques ne peuvent pas être convertis en + noms, le nombre sera affiché comme d'habitude. +id-help-password = Affiche l'id comme une entrée de fichier de mots de passe. +id-help-real = Affiche l'ID réel pour les options -G, -g et -u au lieu de + l'ID effectif. +id-help-zero = délimite les entrées avec des caractères NUL, pas des espaces ; + non autorisé dans le format par défaut + +# Étiquettes de sortie +id-output-uid = uid +id-output-groups = groupes +id-output-login = connexion +id-output-euid = euid +id-output-context = contexte diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index a0831474817..52da859f0b1 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -34,6 +34,7 @@ #![allow(dead_code)] use clap::{Arg, ArgAction, Command}; +use std::collections::HashMap; use std::ffi::CStr; use uucore::display::Quotable; use uucore::entries::{self, Group, Locate, Passwd}; @@ -42,7 +43,7 @@ use uucore::error::{USimpleError, set_exit_code}; pub use uucore::libc; use uucore::libc::{getlogin, uid_t}; use uucore::line_ending::LineEnding; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; use uucore::process::{getegid, geteuid, getgid, getuid}; use uucore::{format_usage, show_error}; @@ -60,10 +61,12 @@ macro_rules! cstr2cow { }; } -#[cfg(not(feature = "selinux"))] -static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)"; -#[cfg(feature = "selinux")] -static CONTEXT_HELP_TEXT: &str = "print only the security context of the process"; +fn get_context_help_text() -> String { + #[cfg(not(feature = "selinux"))] + return get_message("id-context-help-disabled"); + #[cfg(feature = "selinux")] + return get_message("id-context-help-enabled"); +} mod options { pub const OPT_AUDIT: &str = "audit"; // GNU's id does not have this @@ -156,20 +159,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if (state.nflag || state.rflag) && default_format && !state.cflag { return Err(USimpleError::new( 1, - "printing only names or real IDs requires -u, -g, or -G", + get_message("id-error-names-real-ids-require-flags"), )); } if state.zflag && default_format && !state.cflag { // NOTE: GNU test suite "id/zero.sh" needs this stderr output: return Err(USimpleError::new( 1, - "option --zero not permitted in default format", + get_message("id-error-zero-not-permitted-default"), )); } if state.user_specified && state.cflag { return Err(USimpleError::new( 1, - "cannot print security context when user specified", + get_message("id-error-cannot-print-context-with-user"), )); } @@ -185,13 +188,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { print!("{}{line_ending}", String::from_utf8_lossy(bytes)); } else { // print error because `cflag` was explicitly requested - return Err(USimpleError::new(1, "can't get process context")); + return Err(USimpleError::new( + 1, + get_message("id-error-cannot-get-context"), + )); } Ok(()) } else { Err(USimpleError::new( 1, - "--context (-Z) works only on an SELinux-enabled kernel", + get_message("id-error-context-selinux-only"), )) }; } @@ -201,7 +207,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match Passwd::locate(users[i].as_str()) { Ok(p) => Some(p), Err(_) => { - show_error!("{}: no such user", users[i].quote()); + show_error!( + "{}", + get_message_with_args( + "id-error-no-such-user", + HashMap::from([("user".to_string(), users[i].quote().to_string())]) + ) + ); set_exit_code(1); if i + 1 >= users.len() { break; @@ -251,7 +263,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { "{}", if state.nflag { entries::gid2grp(gid).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {gid}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-group-name", + HashMap::from([("gid".to_string(), gid.to_string())]) + ) + ); set_exit_code(1); gid.to_string() }) @@ -266,7 +284,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { "{}", if state.nflag { entries::uid2usr(uid).unwrap_or_else(|_| { - show_error!("cannot find name for user ID {uid}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-user-name", + HashMap::from([("uid".to_string(), uid.to_string())]) + ) + ); set_exit_code(1); uid.to_string() }) @@ -291,7 +315,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|&id| { if state.nflag { entries::gid2grp(id).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {id}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-group-name", + HashMap::from([("gid".to_string(), id.to_string())]) + ) + ); set_exit_code(1); id.to_string() }) @@ -341,10 +371,7 @@ pub fn uu_app() -> Command { options::OPT_GROUPS, options::OPT_ZERO, ]) - .help( - "Display the process audit user ID and other process audit properties,\n\ - which requires privilege (not available on Linux).", - ) + .help(get_message("id-help-audit")) .action(ArgAction::SetTrue), ) .arg( @@ -352,7 +379,7 @@ pub fn uu_app() -> Command { .short('u') .long(options::OPT_EFFECTIVE_USER) .conflicts_with(options::OPT_GROUP) - .help("Display only the effective user ID as a number.") + .help(get_message("id-help-user")) .action(ArgAction::SetTrue), ) .arg( @@ -360,7 +387,7 @@ pub fn uu_app() -> Command { .short('g') .long(options::OPT_GROUP) .conflicts_with(options::OPT_EFFECTIVE_USER) - .help("Display only the effective group ID as a number") + .help(get_message("id-help-group")) .action(ArgAction::SetTrue), ) .arg( @@ -375,33 +402,26 @@ pub fn uu_app() -> Command { options::OPT_PASSWORD, options::OPT_AUDIT, ]) - .help( - "Display only the different group IDs as white-space separated numbers, \ - in no particular order.", - ) + .help(get_message("id-help-groups")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::OPT_HUMAN_READABLE) .short('p') - .help("Make the output human-readable. Each display is on a separate line.") + .help(get_message("id-help-human-readable")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::OPT_NAME) .short('n') .long(options::OPT_NAME) - .help( - "Display the name of the user or group ID for the -G, -g and -u options \ - instead of the number.\nIf any of the ID numbers cannot be mapped into \ - names, the number will be displayed as usual.", - ) + .help(get_message("id-help-name")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::OPT_PASSWORD) .short('P') - .help("Display the id as a password file entry.") + .help(get_message("id-help-password")) .conflicts_with(options::OPT_HUMAN_READABLE) .action(ArgAction::SetTrue), ) @@ -409,20 +429,14 @@ pub fn uu_app() -> Command { Arg::new(options::OPT_REAL_ID) .short('r') .long(options::OPT_REAL_ID) - .help( - "Display the real ID for the -G, -g and -u options instead of \ - the effective ID.", - ) + .help(get_message("id-help-real")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::OPT_ZERO) .short('z') .long(options::OPT_ZERO) - .help( - "delimit entries with NUL characters, not whitespace;\n\ - not permitted in default format", - ) + .help(get_message("id-help-zero")) .action(ArgAction::SetTrue), ) .arg( @@ -430,7 +444,7 @@ pub fn uu_app() -> Command { .short('Z') .long(options::OPT_CONTEXT) .conflicts_with_all([options::OPT_GROUP, options::OPT_EFFECTIVE_USER]) - .help(CONTEXT_HELP_TEXT) + .help(get_context_help_text()) .action(ArgAction::SetTrue), ) .arg( @@ -443,7 +457,12 @@ pub fn uu_app() -> Command { fn pretty(possible_pw: Option) { if let Some(p) = possible_pw { - print!("uid\t{}\ngroups\t", p.name); + print!( + "{}\t{}\n{}\t", + get_message("id-output-uid"), + p.name, + get_message("id-output-groups") + ); println!( "{}", p.belongs_to() @@ -457,33 +476,34 @@ fn pretty(possible_pw: Option) { let rid = getuid(); if let Ok(p) = Passwd::locate(rid) { if let Some(user_name) = login { - println!("login\t{user_name}"); + println!("{}\t{user_name}", get_message("id-output-login")); } - println!("uid\t{}", p.name); + println!("{}\t{}", get_message("id-output-uid"), p.name); } else { - println!("uid\t{rid}"); + println!("{}\t{rid}", get_message("id-output-uid")); } let eid = getegid(); if eid == rid { if let Ok(p) = Passwd::locate(eid) { - println!("euid\t{}", p.name); + println!("{}\t{}", get_message("id-output-euid"), p.name); } else { - println!("euid\t{eid}"); + println!("{}\t{eid}", get_message("id-output-euid")); } } let rid = getgid(); if rid != eid { if let Ok(g) = Group::locate(rid) { - println!("euid\t{}", g.name); + println!("{}\t{}", get_message("id-output-euid"), g.name); } else { - println!("euid\t{rid}"); + println!("{}\t{rid}", get_message("id-output-euid")); } } println!( - "groups\t{}", + "{}\t{}", + get_message("id-output-groups"), entries::get_groups_gnu(None) .unwrap() .iter() @@ -541,7 +561,7 @@ fn auditid() { let mut auditinfo: MaybeUninit = MaybeUninit::uninit(); let address = auditinfo.as_mut_ptr(); if unsafe { audit::getaudit(address) } < 0 { - println!("couldn't retrieve information"); + println!("{}", get_message("id-error-audit-retrieve")); return; } @@ -564,7 +584,13 @@ fn id_print(state: &State, groups: &[u32]) { print!( "uid={uid}({})", entries::uid2usr(uid).unwrap_or_else(|_| { - show_error!("cannot find name for user ID {uid}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-user-name", + HashMap::from([("uid".to_string(), uid.to_string())]) + ) + ); set_exit_code(1); uid.to_string() }) @@ -572,7 +598,13 @@ fn id_print(state: &State, groups: &[u32]) { print!( " gid={gid}({})", entries::gid2grp(gid).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {gid}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-group-name", + HashMap::from([("gid".to_string(), gid.to_string())]) + ) + ); set_exit_code(1); gid.to_string() }) @@ -581,7 +613,13 @@ fn id_print(state: &State, groups: &[u32]) { print!( " euid={euid}({})", entries::uid2usr(euid).unwrap_or_else(|_| { - show_error!("cannot find name for user ID {euid}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-user-name", + HashMap::from([("uid".to_string(), euid.to_string())]) + ) + ); set_exit_code(1); euid.to_string() }) @@ -592,7 +630,13 @@ fn id_print(state: &State, groups: &[u32]) { print!( " egid={egid}({})", entries::gid2grp(egid).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {egid}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-group-name", + HashMap::from([("gid".to_string(), egid.to_string())]) + ) + ); set_exit_code(1); egid.to_string() }) @@ -605,7 +649,13 @@ fn id_print(state: &State, groups: &[u32]) { .map(|&gr| format!( "{gr}({})", entries::gid2grp(gr).unwrap_or_else(|_| { - show_error!("cannot find name for group ID {gr}"); + show_error!( + "{}", + get_message_with_args( + "id-error-cannot-find-group-name", + HashMap::from([("gid".to_string(), gr.to_string())]) + ) + ); set_exit_code(1); gr.to_string() }) From 4d40671d7924b34a1769ac51f6b151804f12c6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Teemu=20P=C3=A4tsi?= <44954973+frendsick@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:01:31 +0300 Subject: [PATCH 138/139] cat: Fix reporting "input file is output file" error when outputting to an input file (#8025) * cat: Check if a file can be overwritten safely in Unix * cat: Check if a file can be overwritten safely in Windows * cat: Test writing read-write file that is input and output * cat: Unit test `is_appending` function * cat: Unit test `is_unsafe_overwrite` function * cat: Comment why a few function calls could return Err * cat: Remove obvious comments from test --- Cargo.lock | 3 + src/uu/cat/Cargo.toml | 7 ++ src/uu/cat/src/cat.rs | 54 +++------------ src/uu/cat/src/platform/mod.rs | 16 +++++ src/uu/cat/src/platform/unix.rs | 108 +++++++++++++++++++++++++++++ src/uu/cat/src/platform/windows.rs | 56 +++++++++++++++ tests/by-util/test_cat.rs | 52 ++++++++++++++ 7 files changed, 253 insertions(+), 43 deletions(-) create mode 100644 src/uu/cat/src/platform/mod.rs create mode 100644 src/uu/cat/src/platform/unix.rs create mode 100644 src/uu/cat/src/platform/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 15c880338db..2140867b92c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2613,8 +2613,11 @@ dependencies = [ "clap", "memchr", "nix", + "tempfile", "thiserror 2.0.12", "uucore", + "winapi-util", + "windows-sys 0.59.0", ] [[package]] diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index f5ac6a64eff..387ecbe238e 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -26,6 +26,13 @@ uucore = { workspace = true, features = ["fast-inc", "fs", "pipes"] } [target.'cfg(unix)'.dependencies] nix = { workspace = true } +[target.'cfg(windows)'.dependencies] +winapi-util = { workspace = true } +windows-sys = { workspace = true, features = ["Win32_Storage_FileSystem"] } + +[dev-dependencies] +tempfile = { workspace = true } + [[bin]] name = "cat" path = "src/main.rs" diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index cd89c3bc385..be47e5c2873 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -4,6 +4,10 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) nonprint nonblank nonprinting ELOOP + +mod platform; + +use crate::platform::is_unsafe_overwrite; use std::fs::{File, metadata}; use std::io::{self, BufWriter, IsTerminal, Read, Write}; /// Unix domain socket support @@ -18,12 +22,9 @@ use std::os::unix::net::UnixStream; use clap::{Arg, ArgAction, Command}; use memchr::memchr2; -#[cfg(unix)] -use nix::fcntl::{FcntlArg, fcntl}; use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; -use uucore::fs::FileInformation; use uucore::locale::get_message; use uucore::{fast_inc::fast_inc_one, format_usage}; @@ -366,42 +367,17 @@ fn cat_handle( } } -/// Whether this process is appending to stdout. -#[cfg(unix)] -fn is_appending() -> bool { - let stdout = io::stdout(); - let Ok(flags) = fcntl(stdout.as_fd(), FcntlArg::F_GETFL) else { - return false; - }; - // TODO Replace `1 << 10` with `nix::fcntl::Oflag::O_APPEND`. - let o_append = 1 << 10; - (flags & o_append) > 0 -} - -#[cfg(not(unix))] -fn is_appending() -> bool { - false -} - -fn cat_path( - path: &str, - options: &OutputOptions, - state: &mut OutputState, - out_info: Option<&FileInformation>, -) -> CatResult<()> { +fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { match get_input_type(path)? { InputType::StdIn => { let stdin = io::stdin(); - let in_info = FileInformation::from_file(&stdin)?; + if is_unsafe_overwrite(&stdin, &io::stdout()) { + return Err(CatError::OutputIsInput); + } let mut handle = InputHandle { reader: stdin, is_interactive: io::stdin().is_terminal(), }; - if let Some(out_info) = out_info { - if in_info == *out_info && is_appending() { - return Err(CatError::OutputIsInput); - } - } cat_handle(&mut handle, options, state) } InputType::Directory => Err(CatError::IsDirectory), @@ -417,15 +393,9 @@ fn cat_path( } _ => { let file = File::open(path)?; - - if let Some(out_info) = out_info { - if out_info.file_size() != 0 - && FileInformation::from_file(&file).ok().as_ref() == Some(out_info) - { - return Err(CatError::OutputIsInput); - } + if is_unsafe_overwrite(&file, &io::stdout()) { + return Err(CatError::OutputIsInput); } - let mut handle = InputHandle { reader: file, is_interactive: false, @@ -436,8 +406,6 @@ fn cat_path( } fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> { - let out_info = FileInformation::from_file(&io::stdout()).ok(); - let mut state = OutputState { line_number: LineNumber::new(), at_line_start: true, @@ -447,7 +415,7 @@ fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> { let mut error_messages: Vec = Vec::new(); for path in files { - if let Err(err) = cat_path(path, options, &mut state, out_info.as_ref()) { + if let Err(err) = cat_path(path, options, &mut state) { error_messages.push(format!("{}: {err}", path.maybe_quote())); } } diff --git a/src/uu/cat/src/platform/mod.rs b/src/uu/cat/src/platform/mod.rs new file mode 100644 index 00000000000..3fa27a27686 --- /dev/null +++ b/src/uu/cat/src/platform/mod.rs @@ -0,0 +1,16 @@ +// 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. + +#[cfg(unix)] +pub use self::unix::is_unsafe_overwrite; + +#[cfg(windows)] +pub use self::windows::is_unsafe_overwrite; + +#[cfg(unix)] +mod unix; + +#[cfg(windows)] +mod windows; diff --git a/src/uu/cat/src/platform/unix.rs b/src/uu/cat/src/platform/unix.rs new file mode 100644 index 00000000000..8c55c9a4209 --- /dev/null +++ b/src/uu/cat/src/platform/unix.rs @@ -0,0 +1,108 @@ +// 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 lseek seekable + +use nix::fcntl::{FcntlArg, OFlag, fcntl}; +use nix::unistd::{Whence, lseek}; +use std::os::fd::AsFd; +use uucore::fs::FileInformation; + +/// An unsafe overwrite occurs when the same nonempty file is used as both stdin and stdout, +/// and the file offset of stdin is positioned earlier than that of stdout. +/// In this scenario, bytes read from stdin are written to a later part of the file +/// via stdout, which can then be read again by stdin and written again by stdout, +/// causing an infinite loop and potential file corruption. +pub fn is_unsafe_overwrite(input: &I, output: &O) -> bool { + // `FileInformation::from_file` returns an error if the file descriptor is closed, invalid, + // or refers to a non-regular file (e.g., socket, pipe, or special device). + let Ok(input_info) = FileInformation::from_file(input) else { + return false; + }; + let Ok(output_info) = FileInformation::from_file(output) else { + return false; + }; + if input_info != output_info || output_info.file_size() == 0 { + return false; + } + if is_appending(output) { + return true; + } + // `lseek` returns an error if the file descriptor is closed or it refers to + // a non-seekable resource (e.g., pipe, socket, or some devices). + let Ok(input_pos) = lseek(input.as_fd(), 0, Whence::SeekCur) else { + return false; + }; + let Ok(output_pos) = lseek(output.as_fd(), 0, Whence::SeekCur) else { + return false; + }; + input_pos < output_pos +} + +/// Whether the file is opened with the `O_APPEND` flag +fn is_appending(file: &F) -> bool { + let flags_raw = fcntl(file.as_fd(), FcntlArg::F_GETFL).unwrap_or_default(); + let flags = OFlag::from_bits_truncate(flags_raw); + flags.contains(OFlag::O_APPEND) +} + +#[cfg(test)] +mod tests { + use crate::platform::unix::{is_appending, is_unsafe_overwrite}; + use std::fs::OpenOptions; + use std::io::{Seek, SeekFrom, Write}; + use tempfile::NamedTempFile; + + #[test] + fn test_is_appending() { + let temp_file = NamedTempFile::new().unwrap(); + assert!(!is_appending(&temp_file)); + + let read_file = OpenOptions::new().read(true).open(&temp_file).unwrap(); + assert!(!is_appending(&read_file)); + + let write_file = OpenOptions::new().write(true).open(&temp_file).unwrap(); + assert!(!is_appending(&write_file)); + + let append_file = OpenOptions::new().append(true).open(&temp_file).unwrap(); + assert!(is_appending(&append_file)); + } + + #[test] + fn test_is_unsafe_overwrite() { + // Create two temp files one of which is empty + let empty = NamedTempFile::new().unwrap(); + let mut nonempty = NamedTempFile::new().unwrap(); + nonempty.write_all(b"anything").unwrap(); + nonempty.seek(SeekFrom::Start(0)).unwrap(); + + // Using a different file as input and output does not result in an overwrite + assert!(!is_unsafe_overwrite(&empty, &nonempty)); + + // Overwriting an empty file is always safe + assert!(!is_unsafe_overwrite(&empty, &empty)); + + // Overwriting a nonempty file with itself is safe + assert!(!is_unsafe_overwrite(&nonempty, &nonempty)); + + // Overwriting an empty file opened in append mode is safe + let empty_append = OpenOptions::new().append(true).open(&empty).unwrap(); + assert!(!is_unsafe_overwrite(&empty, &empty_append)); + + // Overwriting a nonempty file opened in append mode is unsafe + let nonempty_append = OpenOptions::new().append(true).open(&nonempty).unwrap(); + assert!(is_unsafe_overwrite(&nonempty, &nonempty_append)); + + // Overwriting a file opened in write mode is safe + let mut nonempty_write = OpenOptions::new().write(true).open(&nonempty).unwrap(); + assert!(!is_unsafe_overwrite(&nonempty, &nonempty_write)); + + // Overwriting a file when the input and output file descriptors are pointing to + // different offsets is safe if the input offset is further than the output offset + nonempty_write.seek(SeekFrom::Start(1)).unwrap(); + assert!(!is_unsafe_overwrite(&nonempty_write, &nonempty)); + assert!(is_unsafe_overwrite(&nonempty, &nonempty_write)); + } +} diff --git a/src/uu/cat/src/platform/windows.rs b/src/uu/cat/src/platform/windows.rs new file mode 100644 index 00000000000..ebf375b324e --- /dev/null +++ b/src/uu/cat/src/platform/windows.rs @@ -0,0 +1,56 @@ +// 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. + +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use std::path::PathBuf; +use uucore::fs::FileInformation; +use winapi_util::AsHandleRef; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_NAME_NORMALIZED, GetFinalPathNameByHandleW, VOLUME_NAME_NT, +}; + +/// An unsafe overwrite occurs when the same file is used as both stdin and stdout +/// and the stdout file is not empty. +pub fn is_unsafe_overwrite(input: &I, output: &O) -> bool { + if !is_same_file_by_path(input, output) { + return false; + } + + // Check if the output file is empty + FileInformation::from_file(output) + .map(|info| info.file_size() > 0) + .unwrap_or(false) +} + +/// Get the file path for a file handle +fn get_file_path_from_handle(file: &F) -> Option { + let handle = file.as_raw(); + let mut path_buf = vec![0u16; 4096]; + + // SAFETY: We should check how many bytes was written to `path_buf` + // and only read that many bytes from it. + let len = unsafe { + GetFinalPathNameByHandleW( + handle, + path_buf.as_mut_ptr(), + path_buf.len() as u32, + FILE_NAME_NORMALIZED | VOLUME_NAME_NT, + ) + }; + if len == 0 { + return None; + } + let path = OsString::from_wide(&path_buf[..len as usize]); + Some(PathBuf::from(path)) +} + +/// Compare two file handles if they correspond to the same file +fn is_same_file_by_path(a: &A, b: &B) -> bool { + match (get_file_path_from_handle(a), get_file_path_from_handle(b)) { + (Some(path1), Some(path2)) => path1 == path2, + _ => false, + } +} diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 926befe72ff..9fca79753c6 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -9,6 +9,7 @@ use rlimit::Resource; #[cfg(unix)] use std::fs::File; use std::fs::OpenOptions; +use std::fs::read_to_string; use std::process::Stdio; use uutests::at_and_ucmd; use uutests::new_ucmd; @@ -637,6 +638,57 @@ fn test_write_to_self() { ); } +/// Test derived from the following GNU test in `tests/cat/cat-self.sh`: +/// +/// `cat fxy2 fy 1<>fxy2` +// TODO: make this work on windows +#[test] +#[cfg(unix)] +fn test_successful_write_to_read_write_self() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("fy", "y"); + at.write("fxy2", "x"); + + // Open `rw_file` as both stdin and stdout (read/write) + let fxy2_file_path = at.plus("fxy2"); + let fxy2_file = OpenOptions::new() + .read(true) + .write(true) + .open(&fxy2_file_path) + .unwrap(); + ucmd.args(&["fxy2", "fy"]).set_stdout(fxy2_file).succeeds(); + + // The contents of `fxy2` and `fy` files should be merged + let fxy2_contents = read_to_string(fxy2_file_path).unwrap(); + assert_eq!(fxy2_contents, "xy"); +} + +/// Test derived from the following GNU test in `tests/cat/cat-self.sh`: +/// +/// `cat fx fx3 1<>fx3` +#[test] +fn test_failed_write_to_read_write_self() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("fx", "g"); + at.write("fx3", "bold"); + + // Open `rw_file` as both stdin and stdout (read/write) + let fx3_file_path = at.plus("fx3"); + let fx3_file = OpenOptions::new() + .read(true) + .write(true) + .open(&fx3_file_path) + .unwrap(); + ucmd.args(&["fx", "fx3"]) + .set_stdout(fx3_file) + .fails_with_code(1) + .stderr_only("cat: fx3: input file is output file\n"); + + // The contents of `fx` should have overwritten the beginning of `fx3` + let fx3_contents = read_to_string(fx3_file_path).unwrap(); + assert_eq!(fx3_contents, "gold"); +} + #[test] #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] From 60c55d7c0709d9db8ac2415c29133304a0767a4b Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 6 Jun 2025 07:56:08 -0400 Subject: [PATCH 139/139] chore: cleanup workspace crates (#8058) * chore: cleanup workspace crates * properly add all crates to the workspace cargo.toml as members * except `fuzz` because it still has some issues, TBD * use quotes around `true` and `false` to ensure there is no bool confusion * remove a few leftover readme comments * mark all unpublishable crates as `publish = false` to avoid accidental publishing * Add `uutests` to the main workspace * grammar * a bit more cleanup based on feedback * revert true/false * Update tests/benches/factor dependencies --- Cargo.lock | 181 +++++++++++++++++++++++++++++++- Cargo.toml | 20 +++- fuzz/Cargo.toml | 9 +- fuzz/uufuzz/Cargo.toml | 1 - src/uucore/Cargo.toml | 1 - src/uucore_procs/Cargo.toml | 1 - tests/benches/factor/Cargo.toml | 15 ++- tests/uutests/Cargo.toml | 1 - 8 files changed, 206 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2140867b92c..ccdd5ee9acc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi-width" version = "0.1.0" @@ -106,6 +112,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "arrayref" version = "0.3.9" @@ -272,6 +284,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.25" @@ -314,6 +332,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -597,6 +642,39 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -862,7 +940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1241,6 +1319,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1277,7 +1364,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1662,6 +1749,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -1781,6 +1874,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "portable-atomic" version = "1.11.0" @@ -2074,7 +2195,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2083,6 +2204,12 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -2165,6 +2292,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2318,7 +2457,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2435,6 +2574,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "toml_datetime" version = "0.6.9" @@ -2849,6 +2998,20 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_factor_benches" +version = "0.0.0" +dependencies = [ + "array-init", + "criterion", + "num-bigint", + "num-prime", + "num-traits", + "rand 0.9.1", + "rand_chacha 0.9.0", + "uu_factor", +] + [[package]] name = "uu_false" version = "0.1.0" @@ -3781,6 +3944,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 752c0cfef85..07c6e1238b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -256,6 +256,20 @@ feat_os_windows_legacy = [ # * bypass/override ~ translate 'test' feature name to avoid dependency collision with rust core 'test' crate (o/w surfaces as compiler errors during testing) test = ["uu_test"] +[workspace] +resolver = "3" +members = [ + ".", + "src/uu/*", + "src/uu/stdbuf/src/libstdbuf", + "src/uucore", + "src/uucore_procs", + "src/uuhelp_parser", + "tests/benches/factor", + "tests/uutests", + # "fuzz", # TODO +] + [workspace.package] authors = ["uutils developers"] categories = ["command-line-utilities"] @@ -370,7 +384,7 @@ uucore = { version = "0.1.0", package = "uucore", path = "src/uucore" } uucore_procs = { version = "0.1.0", package = "uucore_procs", path = "src/uucore_procs" } uu_ls = { version = "0.1.0", path = "src/uu/ls" } uu_base32 = { version = "0.1.0", path = "src/uu/base32" } -uutests = { version = "0.1.0", package = "uutests", path = "tests/uutests/" } +uutests = { version = "0.1.0", package = "uutests", path = "tests/uutests" } [dependencies] clap = { workspace = true } @@ -528,8 +542,8 @@ nix = { workspace = true, features = ["process", "signal", "user", "term"] } rlimit = "0.10.1" xattr = { workspace = true } -# Specifically used in test_uptime::test_uptime_with_file_containing_valid_boot_time_utmpx_record -# to deserialize a utmpx struct into a binary file +# Used in test_uptime::test_uptime_with_file_containing_valid_boot_time_utmpx_record +# to deserialize an utmpx struct into a binary file [target.'cfg(all(target_family= "unix",not(target_os = "macos")))'.dev-dependencies] serde = { version = "1.0.202", features = ["derive"] } bincode = { version = "2.0.1", features = ["serde"] } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 48da8e846b4..2212904908d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -4,8 +4,13 @@ version = "0.0.0" description = "uutils ~ 'core' uutils fuzzers" repository = "https://github.com/uutils/coreutils/tree/main/fuzz/" edition.workspace = true +license.workspace = true publish = false +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + [workspace.package] edition = "2024" license = "MIT" @@ -32,10 +37,6 @@ uu_tr = { path = "../src/uu/tr/" } uu_env = { path = "../src/uu/env/" } uu_cksum = { path = "../src/uu/cksum/" } -# Prevent this from interfering with workspaces -[workspace] -members = ["."] - [[bin]] name = "fuzz_date" path = "fuzz_targets/fuzz_date.rs" diff --git a/fuzz/uufuzz/Cargo.toml b/fuzz/uufuzz/Cargo.toml index d206d86319a..fb082ee487a 100644 --- a/fuzz/uufuzz/Cargo.toml +++ b/fuzz/uufuzz/Cargo.toml @@ -7,7 +7,6 @@ version = "0.1.0" edition.workspace = true license.workspace = true - [dependencies] console = "0.15.0" libc = "0.2.153" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index cd4f2e1c008..7101cca70fe 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -4,7 +4,6 @@ name = "uucore" description = "uutils ~ 'core' uutils code library (cross-platform)" repository = "https://github.com/uutils/coreutils/tree/main/src/uucore" -# readme = "README.md" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index bd6b48edd1d..f3dbec52bb1 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -3,7 +3,6 @@ name = "uucore_procs" description = "uutils ~ 'uucore' proc-macros" repository = "https://github.com/uutils/coreutils/tree/main/src/uucore_procs" -# readme = "README.md" keywords = ["cross-platform", "proc-macros", "uucore", "uutils"] # categories = ["os"] authors.workspace = true diff --git a/tests/benches/factor/Cargo.toml b/tests/benches/factor/Cargo.toml index 066a8b52ff4..e7397f7fa11 100644 --- a/tests/benches/factor/Cargo.toml +++ b/tests/benches/factor/Cargo.toml @@ -2,21 +2,20 @@ name = "uu_factor_benches" version = "0.0.0" authors = ["nicoo "] -license = "MIT" description = "Benchmarks for the uu_factor integer factorization tool" -homepage = "https://github.com/uutils/coreutils" -edition = "2024" - -[workspace] +edition.workspace = true +homepage.workspace = true +license.workspace = true +publish = false [dependencies] uu_factor = { path = "../../../src/uu/factor" } [dev-dependencies] array-init = "2.0.0" -criterion = "0.3" -rand = "0.8" -rand_chacha = "0.3.1" +criterion = "0.6.0" +rand = "0.9.1" +rand_chacha = "0.9.0" num-bigint = "0.4.4" num-prime = "0.4.4" num-traits = "0.2.18" diff --git a/tests/uutests/Cargo.toml b/tests/uutests/Cargo.toml index e7a0dbef9b3..45c080a34ce 100644 --- a/tests/uutests/Cargo.toml +++ b/tests/uutests/Cargo.toml @@ -4,7 +4,6 @@ name = "uutests" description = "uutils ~ 'core' uutils test library (cross-platform)" repository = "https://github.com/uutils/coreutils/tree/main/tests/uutests" -# readme = "README.md" authors.workspace = true categories.workspace = true edition.workspace = true