From 8e816a6a720bbd15aea9b7df4fc681c5142801d0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:03:13 +0000 Subject: [PATCH 01/24] chore(deps): update rust crate terminal_size to v0.4.3 --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c90da6e88..94ab55c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -940,7 +940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2394,7 +2394,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2674,17 +2674,17 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4313,7 +4313,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.52.0", ] [[package]] From 6d8f8c92e9a91292799258d504ef1edc27e28889 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:25:57 +0000 Subject: [PATCH 02/24] chore(deps): update rust crate clap to v4.5.44 --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c90da6e88..7c0ce7a63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,18 +372,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.43" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" +checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.43" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -940,7 +940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2394,7 +2394,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2674,7 +2674,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4313,7 +4313,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.52.0", ] [[package]] From 9b5b08dc1956d792508ca3e6de7335e17112a187 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:26:03 +0000 Subject: [PATCH 03/24] chore(deps): update rust crate clap_complete to v4.5.57 --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c90da6e88..d14334811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,9 +394,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e4efcbb5da11a92e8a609233aa1e8a7d91e38de0be865f016d14700d45a7fd" +checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad" dependencies = [ "clap", ] @@ -940,7 +940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2394,7 +2394,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2674,7 +2674,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4313,7 +4313,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.52.0", ] [[package]] From ae6b8feb51bc53e0d718724cb0dfeb1ccc44b70f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:06:14 +0000 Subject: [PATCH 04/24] chore(deps): update rust crate thiserror to v2.0.14 --- Cargo.lock | 94 +++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c90da6e88..e0c51ce10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,7 +1054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -2442,7 +2442,7 @@ dependencies = [ "once_cell", "parking_lot", "selinux-sys", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -2710,11 +2710,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -2730,9 +2730,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -2998,7 +2998,7 @@ dependencies = [ "memchr", "nix", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "winapi-util", "windows-sys 0.60.2", @@ -3013,7 +3013,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3032,7 +3032,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3051,7 +3051,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3086,7 +3086,7 @@ dependencies = [ "libc", "linux-raw-sys 0.10.0", "selinux", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "walkdir", "xattr", @@ -3099,7 +3099,7 @@ dependencies = [ "clap", "fluent", "regex", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3138,7 +3138,7 @@ dependencies = [ "libc", "nix", "signal-hook", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3149,7 +3149,7 @@ dependencies = [ "clap", "fluent", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.2.1", "uucore", ] @@ -3188,7 +3188,7 @@ dependencies = [ "clap", "fluent", "glob", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "windows-sys 0.60.2", ] @@ -3210,7 +3210,7 @@ dependencies = [ "fluent", "nix", "rust-ini", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3220,7 +3220,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.2.1", "uucore", ] @@ -3234,7 +3234,7 @@ dependencies = [ "num-bigint", "num-traits", "onig", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3276,7 +3276,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.2.1", "uucore", ] @@ -3296,7 +3296,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3316,7 +3316,7 @@ dependencies = [ "clap", "fluent", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3360,7 +3360,7 @@ dependencies = [ "file_diff", "filetime", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3371,7 +3371,7 @@ dependencies = [ "clap", "fluent", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3400,7 +3400,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3426,7 +3426,7 @@ dependencies = [ "lscolors", "selinux", "terminal_size", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "uutils_term_grid", ] @@ -3468,7 +3468,7 @@ dependencies = [ "fluent", "rand 0.9.2", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3493,7 +3493,7 @@ dependencies = [ "fs_extra", "indicatif", "libc", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "windows-sys 0.60.2", ] @@ -3526,7 +3526,7 @@ dependencies = [ "clap", "fluent", "libc", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3546,7 +3546,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3597,7 +3597,7 @@ dependencies = [ "fluent", "itertools 0.14.0", "regex", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3626,7 +3626,7 @@ dependencies = [ "clap", "fluent", "regex", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3664,7 +3664,7 @@ dependencies = [ "clap", "fluent", "libc", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "windows-sys 0.60.2", ] @@ -3687,7 +3687,7 @@ dependencies = [ "fluent", "libc", "selinux", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3700,7 +3700,7 @@ dependencies = [ "fluent", "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3753,7 +3753,7 @@ dependencies = [ "rayon", "self_cell", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.2.1", "uucore", ] @@ -3765,7 +3765,7 @@ dependencies = [ "clap", "fluent", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3775,7 +3775,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3786,7 +3786,7 @@ dependencies = [ "clap", "fluent", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "uu_stdbuf_libstdbuf", "uucore", ] @@ -3838,7 +3838,7 @@ dependencies = [ "memchr", "memmap2", "regex", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3875,7 +3875,7 @@ dependencies = [ "clap", "fluent", "libc", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3899,7 +3899,7 @@ dependencies = [ "filetime", "fluent", "parse_datetime", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", "windows-sys 0.60.2", ] @@ -3938,7 +3938,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "uucore", ] @@ -3968,7 +3968,7 @@ version = "0.1.0" dependencies = [ "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.2.1", "uucore", ] @@ -3998,7 +3998,7 @@ dependencies = [ "chrono", "clap", "fluent", - "thiserror 2.0.12", + "thiserror 2.0.14", "utmp-classic", "uucore", ] @@ -4031,7 +4031,7 @@ dependencies = [ "fluent", "libc", "nix", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.2.1", "uucore", ] @@ -4106,7 +4106,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "time", "unic-langid", "utmp-classic", From a31cb73813c3510fe9e93095c87af1f57d851d89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 22:34:28 +0000 Subject: [PATCH 05/24] chore(deps): update rust crate clap to v4.5.45 --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 924eccdc6..b3d1c5e03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.44" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", ] @@ -940,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]] @@ -1532,7 +1532,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2394,7 +2394,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2674,7 +2674,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4313,7 +4313,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] From 408937d86f361d678c2628adf73c79ccfa01a92a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 03:08:19 +0000 Subject: [PATCH 06/24] chore(deps): update rust crate rayon to v1.11.0 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 924eccdc6..c28888b54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2253,9 +2253,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -2263,9 +2263,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", From e674fb958ad3d495a994f04d6908fd555c801be6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 18:23:52 +0200 Subject: [PATCH 07/24] l10n: Add LocalizedCommand trait for clap localization --- src/uucore/locales/en-US.ftl | 33 ++++ src/uucore/locales/fr-FR.ftl | 33 ++++ src/uucore/src/lib/lib.rs | 2 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/clap_localization.rs | 184 +++++++++++++++++++ src/uucore/src/lib/mods/locale.rs | 127 +++++++++++++ 6 files changed, 380 insertions(+) create mode 100644 src/uucore/locales/en-US.ftl create mode 100644 src/uucore/locales/fr-FR.ftl create mode 100644 src/uucore/src/lib/mods/clap_localization.rs diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl new file mode 100644 index 000000000..358eda0d5 --- /dev/null +++ b/src/uucore/locales/en-US.ftl @@ -0,0 +1,33 @@ +# Common strings shared across all uutils commands +# Mostly clap + +# Generic words +common-error = error +common-tip = tip +common-usage = Usage +common-help = help +common-version = version + +# Common clap error messages +clap-error-unexpected-argument = { $error_word }: unexpected argument '{ $arg }' found +clap-error-similar-argument = { $tip_word }: a similar argument exists: '{ $suggestion }' +clap-error-pass-as-value = { $tip_word }: to pass '{ $arg }' as a value, use '{ $tip_command }' +clap-error-help-suggestion = For more information, try '{ $command } --help'. + +# Common help text patterns +help-flag-help = Print help information +help-flag-version = Print version information + +# Common error contexts +error-io = I/O error +error-permission-denied = Permission denied +error-file-not-found = No such file or directory +error-invalid-argument = Invalid argument + +# Common actions +action-copying = copying +action-moving = moving +action-removing = removing +action-creating = creating +action-reading = reading +action-writing = writing diff --git a/src/uucore/locales/fr-FR.ftl b/src/uucore/locales/fr-FR.ftl new file mode 100644 index 000000000..cbeab358d --- /dev/null +++ b/src/uucore/locales/fr-FR.ftl @@ -0,0 +1,33 @@ +# Chaînes communes partagées entre toutes les commandes uutils +# Principalement pour clap + +# Mots génériques +common-error = erreur +common-tip = conseil +common-usage = Utilisation +common-help = aide +common-version = version + +# Messages d'erreur clap communs +clap-error-unexpected-argument = { $error_word } : argument inattendu '{ $arg }' trouvé +clap-error-similar-argument = { $tip_word } : un argument similaire existe : '{ $suggestion }' +clap-error-pass-as-value = { $tip_word } : pour passer '{ $arg }' comme valeur, utilisez '{ $tip_command }' +clap-error-help-suggestion = Pour plus d'informations, essayez '{ $command } --help'. + +# Modèles de texte d'aide communs +help-flag-help = Afficher les informations d'aide +help-flag-version = Afficher les informations de version + +# Contextes d'erreur communs +error-io = Erreur E/S +error-permission-denied = Permission refusée +error-file-not-found = Aucun fichier ou répertoire de ce type +error-invalid-argument = Argument invalide + +# Actions communes +action-copying = copie +action-moving = déplacement +action-removing = suppression +action-creating = création +action-reading = lecture +action-writing = écriture diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index ab28d657b..8c8dbb74e 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -22,6 +22,8 @@ mod mods; // core cross-platform modules pub use uucore_procs::*; // * cross-platform modules +pub use crate::mods::clap_localization; +pub use crate::mods::clap_localization::LocalizedCommand; pub use crate::mods::display; pub use crate::mods::error; #[cfg(feature = "fs")] diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 7af54ff5a..e33bf0319 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -4,6 +4,7 @@ // file that was distributed with this source code. // mods ~ cross-platforms modules (core/bundler file) +pub mod clap_localization; pub mod display; pub mod error; #[cfg(feature = "fs")] diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs new file mode 100644 index 000000000..f5aed26f2 --- /dev/null +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -0,0 +1,184 @@ +// 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 (path) osrelease + +//! Helper clap functions to localize error handling and options +//! + +use crate::locale::translate; + +use clap::error::{ContextKind, ErrorKind}; +use clap::{ArgMatches, Command, Error}; +use std::ffi::OsString; + +/// Apply color to text using ANSI escape codes +pub fn colorize(text: &str, color_code: &str) -> String { + format!("\x1b[{color_code}m{text}\x1b[0m") +} + +/// Color constants for consistent styling +pub mod colors { + pub const RED: &str = "31"; + pub const YELLOW: &str = "33"; + pub const GREEN: &str = "32"; +} + +pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { + // Try to ensure localization is initialized for this utility + // If it's already initialized, that's fine - we'll use the existing one + let _ = crate::locale::setup_localization_with_common(util_name); + + match err.kind() { + ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { + // For help and version, use clap's built-in formatting and exit with 0 + // Output to stdout as expected by tests + print!("{}", err.render()); + std::process::exit(0); + } + ErrorKind::UnknownArgument => { + // Use clap's rendering system but capture the output to check if colors are used + let rendered = err.render(); + let rendered_str = rendered.to_string(); + + // Simple check - if the rendered output contains ANSI escape codes, colors are enabled + let colors_enabled = rendered_str.contains("\x1b["); + + if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { + let arg_str = invalid_arg.to_string(); + + // Get the uncolored words from common strings + let error_word = translate!("common-error"); + let tip_word = translate!("common-tip"); + + // Apply colors only if they're enabled in the original error + let (colored_arg, colored_error_word, colored_tip_word) = if colors_enabled { + ( + colorize(&arg_str, colors::YELLOW), + colorize(&error_word, colors::RED), + colorize(&tip_word, colors::GREEN), + ) + } else { + (arg_str.clone(), error_word.clone(), tip_word.clone()) + }; + + // Print main error message + let error_msg = translate!( + "clap-error-unexpected-argument", + "arg" => colored_arg.clone(), + "error_word" => colored_error_word + ); + eprintln!("{error_msg}"); + eprintln!(); + + // Show suggestion or generic tip + let suggestion = err.get(ContextKind::SuggestedArg); + if let Some(suggested_arg) = suggestion { + let colored_suggestion = if colors_enabled { + colorize(&suggested_arg.to_string(), colors::GREEN) + } else { + suggested_arg.to_string() + }; + let suggestion_msg = translate!( + "clap-error-similar-argument", + "tip_word" => colored_tip_word, + "suggestion" => colored_suggestion + ); + eprintln!(" {suggestion_msg}"); + } else { + let colored_tip_command = if colors_enabled { + colorize(&format!("-- {arg_str}"), colors::GREEN) + } else { + format!("-- {arg_str}") + }; + let tip_msg = translate!( + "clap-error-pass-as-value", + "arg" => colored_arg, + "tip_word" => colored_tip_word, + "tip_command" => colored_tip_command + ); + eprintln!(" {tip_msg}"); + } + + // Show usage and help + eprintln!(); + let usage_label = translate!("common-usage"); + let usage_pattern = translate!(&format!("{util_name}-usage")); + eprintln!("{usage_label}: {usage_pattern}"); + eprintln!(); + + let help_msg = translate!("clap-error-help-suggestion", "command" => util_name); + eprintln!("{help_msg}"); + + std::process::exit(exit_code); + } else { + // Generic fallback case + let rendered = err.render(); + let rendered_str = rendered.to_string(); + let colors_enabled = rendered_str.contains("\x1b["); + + let colored_error_word = if colors_enabled { + colorize(&translate!("common-error"), colors::RED) + } else { + translate!("common-error") + }; + eprintln!("{colored_error_word}: unexpected argument"); + std::process::exit(exit_code); + } + } + _ => { + // For other errors, print using clap's formatter but exit with code 1 + eprint!("{}", err.render()); + std::process::exit(1); + } + } +} + +/// Trait extension to provide localized clap error handling +/// This provides a cleaner API than wrapping with macros +pub trait LocalizedCommand { + /// Get matches with localized error handling + fn get_matches_localized(self) -> ArgMatches + where + Self: Sized; + + /// Try to get matches from args with localized error handling + fn try_get_matches_from_localized(self, itr: I) -> ArgMatches + where + Self: Sized, + I: IntoIterator, + T: Into + Clone; + + /// Try to get matches from mutable args with localized error handling + fn try_get_matches_from_mut_localized(self, itr: I) -> ArgMatches + where + Self: Sized, + I: IntoIterator, + T: Into + Clone; +} + +impl LocalizedCommand for Command { + fn get_matches_localized(self) -> ArgMatches { + self.try_get_matches() + .unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1)) + } + + fn try_get_matches_from_localized(self, itr: I) -> ArgMatches + where + I: IntoIterator, + T: Into + Clone, + { + self.try_get_matches_from(itr) + .unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1)) + } + + fn try_get_matches_from_mut_localized(mut self, itr: I) -> ArgMatches + where + I: IntoIterator, + T: Into + Clone, + { + self.try_get_matches_from_mut(itr) + .unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1)) + } +} diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 7b86af32c..aec455cfe 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -147,6 +147,104 @@ fn init_localization( Ok(()) } +/// Helper function to find the uucore locales directory from a utility's locales directory +fn find_uucore_locales_dir(utility_locales_dir: &Path) -> Option { + // Normalize the path to get absolute path + let normalized_dir = utility_locales_dir + .canonicalize() + .unwrap_or_else(|_| utility_locales_dir.to_path_buf()); + + // Walk up: locales -> printenv -> uu -> src + let uucore_locales = normalized_dir + .parent()? // printenv + .parent()? // uu + .parent()? // src + .join("uucore") + .join("locales"); + + // Only return if the directory actually exists + uucore_locales.exists().then_some(uucore_locales) +} + +/// Create a bundle that combines common and utility-specific strings +fn create_bundle_with_common( + locale: &LanguageIdentifier, + locales_dir: &Path, + util_name: &str, +) -> Result, LocalizationError> { + let mut bundle = FluentBundle::new(vec![locale.clone()]); + + // Disable Unicode directional isolate characters + bundle.set_use_isolating(false); + + // Load common strings from uucore locales directory + if let Some(common_dir) = find_uucore_locales_dir(locales_dir) { + let common_locale_path = common_dir.join(format!("{locale}.ftl")); + if let Ok(common_ftl) = fs::read_to_string(&common_locale_path) { + if let Ok(common_resource) = FluentResource::try_new(common_ftl) { + bundle.add_resource_overriding(common_resource); + } + } + } + + // Then, try to load utility-specific strings from the utility's locale directory + let util_locales_dir = get_locales_dir(util_name).ok(); + if let Some(util_dir) = util_locales_dir { + let util_locale_path = util_dir.join(format!("{locale}.ftl")); + if let Ok(util_ftl) = fs::read_to_string(&util_locale_path) { + if let Ok(util_resource) = FluentResource::try_new(util_ftl) { + bundle.add_resource_overriding(util_resource); + } + } + } + + // If we have at least one resource, return the bundle + if bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about")) { + Ok(bundle) + } else { + Err(LocalizationError::LocalesDirNotFound(format!( + "No localization strings found for {locale} and utility {util_name}" + ))) + } +} + +/// Initialize localization with common strings in addition to utility-specific strings +fn init_localization_with_common( + locale: &LanguageIdentifier, + locales_dir: &Path, + util_name: &str, +) -> Result<(), LocalizationError> { + let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) + .expect("Default locale should always be valid"); + + // Try to create a bundle that combines common and utility-specific strings + let english_bundle = create_bundle_with_common(&default_locale, locales_dir, util_name) + .or_else(|_| { + // Fallback to embedded utility-specific strings only + create_english_bundle_from_embedded(&default_locale, util_name) + })?; + + let loc = if locale == &default_locale { + // If requesting English, just use English as primary (no fallback needed) + Localizer::new(english_bundle) + } else { + // Try to load the requested locale with common strings + if let Ok(primary_bundle) = create_bundle_with_common(locale, locales_dir, util_name) { + // 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| { + lock.set(loc) + .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) + })?; + Ok(()) +} + /// Create a bundle for a specific locale fn create_bundle( locale: &LanguageIdentifier, @@ -389,6 +487,35 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> { } } +/// Enhanced version of setup_localization that also loads common/clap error strings +/// This function loads both utility-specific strings and common strings for clap error handling +pub fn setup_localization_with_common(p: &str) -> Result<(), LocalizationError> { + let locale = detect_system_locale().unwrap_or_else(|_| { + LanguageIdentifier::from_str(DEFAULT_LOCALE).expect("Default locale should always be valid") + }); + + // Load common strings along with utility-specific strings + match get_locales_dir(p) { + Ok(locales_dir) => { + // Load both utility-specific and common strings + init_localization_with_common(&locale, &locales_dir, p) + } + Err(_) => { + // No locales directory found, use embedded English directly + let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) + .expect("Default locale should always be valid"); + let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?; + let localizer = Localizer::new(english_bundle); + + LOCALIZER.with(|lock| { + lock.set(localizer) + .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) + })?; + Ok(()) + } + } +} + #[cfg(not(debug_assertions))] fn resolve_locales_dir_from_exe_dir(exe_dir: &Path, p: &str) -> Option { // 1. /locales/ From b754c673c99f47fcb1f743e87a221d1ff6ac5bd7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 18:27:45 +0200 Subject: [PATCH 08/24] l10n: Migrate all utilities to use LocalizedCommand --- src/bin/coreutils.rs | 22 ++++++++++++---------- src/uu/arch/src/arch.rs | 3 ++- src/uu/base32/src/base_common.rs | 7 ++++--- src/uu/basename/src/basename.rs | 3 ++- src/uu/cat/src/cat.rs | 3 ++- src/uu/chcon/src/chcon.rs | 3 ++- src/uu/chmod/src/chmod.rs | 3 ++- src/uu/cksum/src/cksum.rs | 3 ++- src/uu/comm/src/comm.rs | 3 ++- src/uu/cp/src/cp.rs | 3 ++- src/uu/csplit/src/csplit.rs | 3 ++- src/uu/cut/src/cut.rs | 3 ++- src/uu/date/src/date.rs | 3 ++- src/uu/dd/src/dd.rs | 3 ++- src/uu/df/src/df.rs | 3 ++- src/uu/dircolors/src/dircolors.rs | 3 ++- src/uu/dirname/src/dirname.rs | 3 ++- src/uu/du/src/du.rs | 3 ++- src/uu/factor/src/factor.rs | 3 ++- src/uu/false/src/false.rs | 2 ++ src/uu/fmt/src/fmt.rs | 3 ++- src/uu/fold/src/fold.rs | 3 ++- src/uu/groups/src/groups.rs | 3 ++- src/uu/hashsum/src/hashsum.rs | 3 ++- src/uu/head/src/head.rs | 4 +++- src/uu/hostid/src/hostid.rs | 3 ++- src/uu/hostname/src/hostname.rs | 3 ++- src/uu/id/src/id.rs | 3 ++- src/uu/install/Cargo.toml | 2 +- src/uu/install/src/install.rs | 3 ++- src/uu/join/src/join.rs | 3 ++- src/uu/kill/src/kill.rs | 3 ++- src/uu/link/src/link.rs | 3 ++- src/uu/ln/src/ln.rs | 5 ++++- src/uu/logname/src/logname.rs | 3 ++- src/uu/ls/src/ls.rs | 6 ++---- src/uu/mkdir/src/mkdir.rs | 3 ++- src/uu/mkfifo/src/mkfifo.rs | 3 ++- src/uu/mknod/src/mknod.rs | 3 ++- src/uu/mktemp/src/mktemp.rs | 4 ++++ src/uu/more/src/more.rs | 3 ++- src/uu/mv/src/mv.rs | 18 ++++++++++-------- src/uu/nl/src/nl.rs | 3 ++- src/uu/nproc/src/nproc.rs | 3 ++- src/uu/numfmt/src/numfmt.rs | 3 ++- src/uu/od/src/od.rs | 3 ++- src/uu/paste/src/paste.rs | 3 ++- src/uu/pathchk/src/pathchk.rs | 3 ++- src/uu/pinky/src/platform/openbsd.rs | 3 ++- src/uu/pinky/src/platform/unix.rs | 3 ++- src/uu/pr/src/pr.rs | 5 +++-- src/uu/printenv/src/printenv.rs | 3 ++- src/uu/printf/src/printf.rs | 3 ++- src/uu/ptx/src/ptx.rs | 3 ++- src/uu/pwd/src/pwd.rs | 3 ++- src/uu/readlink/src/readlink.rs | 3 ++- src/uu/rm/src/rm.rs | 3 ++- src/uu/rmdir/src/rmdir.rs | 3 ++- src/uu/shred/src/shred.rs | 3 ++- src/uu/shuf/src/shuf.rs | 3 ++- src/uu/sleep/src/sleep.rs | 3 ++- src/uu/sort/src/sort.rs | 4 ++++ src/uu/split/src/split.rs | 3 ++- src/uu/stat/src/stat.rs | 3 ++- src/uu/stty/src/stty.rs | 3 ++- src/uu/sum/src/sum.rs | 3 ++- src/uu/sync/src/sync.rs | 3 ++- src/uu/tac/src/tac.rs | 3 ++- src/uu/tee/src/tee.rs | 3 ++- src/uu/touch/src/touch.rs | 3 ++- src/uu/tr/src/tr.rs | 3 ++- src/uu/tsort/src/tsort.rs | 3 ++- src/uu/tty/src/tty.rs | 5 ++++- src/uu/uname/src/uname.rs | 3 ++- src/uu/unlink/src/unlink.rs | 3 ++- src/uu/uptime/src/uptime.rs | 3 ++- src/uu/users/src/users.rs | 3 ++- src/uu/wc/src/wc.rs | 3 ++- src/uu/who/src/platform/openbsd.rs | 3 ++- src/uu/who/src/platform/unix.rs | 3 ++- src/uu/whoami/src/whoami.rs | 3 ++- src/uu/yes/src/yes.rs | 3 ++- 82 files changed, 193 insertions(+), 101 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 64a79a3fd..c6d2283bd 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -81,16 +81,18 @@ 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) - }); + locale::setup_localization_with_common(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)] diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 29ad9d273..cd398a72b 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -6,12 +6,13 @@ use platform_info::*; use clap::Command; +use uucore::LocalizedCommand; use uucore::error::{UResult, USimpleError}; use uucore::translate; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().try_get_matches_from(args)?; + uu_app().try_get_matches_from_localized(args); let uts = PlatformInfo::new().map_err(|_e| USimpleError::new(1, translate!("cannot-get-system")))?; diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index b7d1124ac..e60ea5366 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -9,12 +9,12 @@ use clap::{Arg, ArgAction, Command}; use std::fs::File; use std::io::{self, ErrorKind, Read, Seek, SeekFrom}; use std::path::{Path, PathBuf}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::encoding::{ - BASE2LSBF, BASE2MSBF, Format, Z85Wrapper, + BASE2LSBF, BASE2MSBF, EncodingWrapper, Format, SupportsFastDecodeAndEncode, Z85Wrapper, for_base_common::{BASE32, BASE32HEX, BASE64, BASE64_NOPAD, BASE64URL, HEXUPPER_PERMISSIVE}, }; -use uucore::encoding::{EncodingWrapper, SupportsFastDecodeAndEncode}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::format_usage; use uucore::translate; @@ -100,7 +100,8 @@ pub fn parse_base_cmd_args( usage: &str, ) -> UResult { let command = base_app(about, usage); - Config::from(&command.try_get_matches_from(args)?) + let matches = command.try_get_matches_from_localized(args); + Config::from(&matches) } pub fn base_app(about: &'static str, usage: &str) -> Command { diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 255ff6110..802eb4464 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -15,6 +15,7 @@ use uucore::error::{UResult, UUsageError}; use uucore::format_usage; use uucore::line_ending::LineEnding; +use uucore::LocalizedCommand; use uucore::translate; pub mod options { @@ -29,7 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // // Argument parsing // - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index c7f975952..22cd38f38 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -22,6 +22,7 @@ use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use std::os::unix::net::UnixStream; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::UResult; #[cfg(not(target_os = "windows"))] @@ -230,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let number_mode = if matches.get_flag(options::NUMBER_NONBLANK) { NumberingMode::NonEmpty diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index ca65cf4a7..de1dc2f64 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -7,6 +7,7 @@ #![allow(clippy::upper_case_acronyms)] use clap::builder::ValueParser; +use uucore::LocalizedCommand; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::translate; use uucore::{display::Quotable, format_usage, show_error, show_warning}; @@ -303,7 +304,7 @@ struct Options { } fn parse_command_line(config: Command, args: impl uucore::Args) -> Result { - let matches = config.try_get_matches_from(args)?; + let matches = config.try_get_matches_from_localized(args); let verbose = matches.get_flag(options::VERBOSE); diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index f2d2bd47a..cb540aa7a 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -11,6 +11,7 @@ use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError, set_exit_code}; use uucore::fs::display_permissions_unix; @@ -112,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name let matches = uu_app() .after_help(translate!("chmod-after-help")) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let changes = matches.get_flag(options::CHANGES); let quiet = matches.get_flag(options::QUIET); diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 6dc28040e..959c23e40 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -20,6 +20,7 @@ use uucore::checksum::{ }; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{ encoding, error::{FromIo, UResult, USimpleError}, @@ -236,7 +237,7 @@ fn handle_tag_text_binary_flags>( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let check = matches.get_flag(options::CHECK); diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index e55d181d6..81bcab418 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -8,6 +8,7 @@ use std::cmp::Ordering; use std::fs::{File, metadata}; use std::io::{self, BufRead, BufReader, Read, Stdin, stdin}; +use uucore::LocalizedCommand; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::format_usage; use uucore::fs::paths_refer_to_same_file; @@ -280,7 +281,7 @@ fn open_file(name: &str, line_ending: LineEnding) -> io::Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); let filename1 = matches.get_one::(options::FILE_1).unwrap(); let filename2 = matches.get_one::(options::FILE_2).unwrap(); diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index e826ac3c4..3dee0fe01 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -13,6 +13,7 @@ use std::fs::{self, Metadata, OpenOptions, Permissions}; use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf, StripPrefixError}; use std::{fmt, io}; +use uucore::LocalizedCommand; #[cfg(all(unix, not(target_os = "android")))] use uucore::fsxattr::copy_xattrs; use uucore::translate; @@ -778,7 +779,7 @@ pub fn uu_app() -> Command { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let options = Options::from_matches(&matches)?; diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index ba58479b2..8c98d37c5 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -25,6 +25,7 @@ mod split_name; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; +use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -604,7 +605,7 @@ where #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); // get the file to split let file_name = matches.get_one::(options::FILE).unwrap(); diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 4bf323507..cf9bfdbe4 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -18,6 +18,7 @@ use uucore::os_str_as_bytes; use self::searcher::Searcher; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; +use uucore::LocalizedCommand; use uucore::ranges::Range; use uucore::translate; use uucore::{format_usage, show_error, show_if_err}; @@ -482,7 +483,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }) .collect(); - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let complement = matches.get_flag(options::COMPLEMENT); let only_delimited = matches.get_flag(options::ONLY_DELIMITED); diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 6d5a418c2..1eadd6ce2 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -21,6 +21,7 @@ use uucore::{format_usage, show}; #[cfg(windows)] use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime}; +use uucore::LocalizedCommand; use uucore::parser::shortcut_value_parser::ShortcutValueParser; // Options @@ -111,7 +112,7 @@ impl From<&str> for Rfc3339Format { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let format = if let Some(form) = matches.get_one::(OPT_FORMAT) { if !form.starts_with('+') { diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4d77dcfe7..d231da8b1 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -55,6 +55,7 @@ use nix::{ errno::Errno, fcntl::{PosixFadviseAdvice, posix_fadvise}, }; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; #[cfg(unix)] @@ -1415,7 +1416,7 @@ fn is_fifo(filename: &str) -> bool { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let settings: Settings = Parser::new().parse( matches diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 6c8a1ee7a..2d6d34b23 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -11,6 +11,7 @@ mod table; use blocks::HumanReadable; use clap::builder::ValueParser; use table::HeaderMode; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{UError, UResult, USimpleError, get_exit_code}; use uucore::fsext::{MountInfo, read_fs_list}; @@ -406,7 +407,7 @@ impl UError for DfError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); #[cfg(windows)] { diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index b76d49f67..f6bd9ec40 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -18,6 +18,7 @@ use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{format_usage, parser::parse_glob}; mod options { @@ -120,7 +121,7 @@ fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let files = matches .get_many::(options::FILE) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 6d7a4a5a5..73618d7e0 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -5,6 +5,7 @@ use clap::{Arg, ArgAction, Command}; use std::path::Path; +use uucore::LocalizedCommand; use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; use uucore::format_usage; @@ -21,7 +22,7 @@ mod options { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(translate!("dirname-after-help")) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index f39d257de..b43cee530 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -25,6 +25,7 @@ use uucore::fsext::{MetadataTimeField, metadata_get_time}; use uucore::line_ending::LineEnding; use uucore::translate; +use uucore::LocalizedCommand; use uucore::parser::parse_glob; use uucore::parser::parse_size::{ParseSizeError, parse_size_u64}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; @@ -580,7 +581,7 @@ fn read_files_from(file_name: &str) -> Result, std::io::Error> { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let summarize = matches.get_flag(options::SUMMARIZE); diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 776a98536..6dec840da 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -12,6 +12,7 @@ use std::io::{self, Write, stdin, stdout}; use clap::{Arg, ArgAction, Command}; use num_bigint::BigUint; use num_traits::FromPrimitive; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; use uucore::translate; @@ -79,7 +80,7 @@ fn write_result( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); // If matches find --exponents flag than variable print_exponents is true and p^e output format will be used. let print_exponents = matches.get_flag(options::EXPONENTS); diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 92128f5f1..a0a3f944f 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -24,6 +24,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if let Err(e) = command.try_get_matches_from_mut(args) { + // For the false command, we don't want to show any error messages for UnknownArgument + // since false should produce no output and just exit with code 1 let error = match e.kind() { clap::error::ErrorKind::DisplayHelp => command.print_help(), clap::error::ErrorKind::DisplayVersion => { diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index f6e555055..80c1af0e3 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -12,6 +12,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::format_usage; use linebreak::break_lines; @@ -334,7 +335,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let matches = uu_app().try_get_matches_from(&args)?; + let matches = uu_app().try_get_matches_from_localized(&args); let files = extract_files(&matches)?; diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 318e3875b..c3e743d85 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -9,6 +9,7 @@ use clap::{Arg, ArgAction, Command}; use std::fs::File; use std::io::{BufRead, BufReader, Read, Write, stdin, stdout}; use std::path::Path; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::format_usage; @@ -31,7 +32,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); let (args, obs_width) = handle_obsolete(&args[..]); - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let bytes = matches.get_flag(options::BYTES); let spaces = matches.get_flag(options::SPACES); diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 498c72802..9d6947223 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -14,6 +14,7 @@ use uucore::{ }; use clap::{Arg, ArgAction, Command}; +use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -47,7 +48,7 @@ fn infallible_gid2grp(gid: &u32) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let users: Vec = matches .get_many::(options::USERS) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index a6fc13e23..4204b979e 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -15,6 +15,7 @@ use std::io::{BufReader, Read, stdin}; use std::iter; use std::num::ParseIntError; use std::path::Path; +use uucore::LocalizedCommand; use uucore::checksum::ChecksumError; use uucore::checksum::ChecksumOptions; use uucore::checksum::ChecksumVerbose; @@ -181,7 +182,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { // causes "error: " to be printed twice (once from crash!() and once from clap). With // the current setup, the name of the utility is not printed, but I think this is at // least somewhat better from a user's perspective. - let matches = command.try_get_matches_from(args)?; + let matches = command.try_get_matches_from_localized(args); let input_length: Option<&usize> = if binary_name == "b2sum" { matches.get_one::(options::LENGTH) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index b7e4334c4..bfd8a076a 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -14,6 +14,7 @@ use std::num::TryFromIntError; #[cfg(unix)] use std::os::fd::{AsRawFd, FromRawFd}; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; use uucore::line_ending::LineEnding; @@ -549,7 +550,8 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(arg_iterate(args)?)?; + let args_vec: Vec<_> = arg_iterate(args)?.collect(); + let matches = uu_app().try_get_matches_from_localized(args_vec); let args = match HeadOptions::get_from(&matches) { Ok(o) => o, Err(s) => { diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index f5054389b..c29b57489 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -9,11 +9,12 @@ use clap::Command; use libc::{c_long, gethostid}; use uucore::{error::UResult, format_usage}; +use uucore::LocalizedCommand; use uucore::translate; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().try_get_matches_from(args)?; + uu_app().try_get_matches_from_localized(args); hostid(); Ok(()) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 558c6aff1..74f2c48d9 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -12,6 +12,7 @@ use std::{collections::hash_set::HashSet, ffi::OsString}; use clap::builder::ValueParser; use clap::{Arg, ArgAction, ArgMatches, Command}; +use uucore::LocalizedCommand; #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] use dns_lookup::lookup_host; @@ -60,7 +61,7 @@ mod wsa { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); #[cfg(windows)] let _handle = wsa::start().map_err_context(|| translate!("hostname-error-winsock"))?; diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 41e926450..688ac232a 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -44,6 +44,7 @@ use uucore::libc::{getlogin, uid_t}; use uucore::line_ending::LineEnding; use uucore::translate; +use uucore::LocalizedCommand; use uucore::process::{getegid, geteuid, getgid, getuid}; use uucore::{format_usage, show_error}; @@ -121,7 +122,7 @@ struct State { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(translate!("id-after-help")) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let users: Vec = matches .get_many::(options::ARG_USERS) diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 4f434485e..dc249b5d0 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -22,7 +22,7 @@ clap = { workspace = true } filetime = { workspace = true } file_diff = { workspace = true } thiserror = { workspace = true } -uucore = { workspace = true, features = [ +uucore = { workspace = true, default-features = true, features = [ "backup-control", "buf-copy", "fs", diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index ac1948c51..338f90f6c 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -16,6 +16,7 @@ use std::fs::{self, metadata}; use std::path::{MAIN_SEPARATOR, Path, PathBuf}; use std::process; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::backup_control::{self, BackupMode}; use uucore::buf_copy::copy_stream; use uucore::display::Quotable; @@ -165,7 +166,7 @@ static ARG_FILES: &str = "files"; /// #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let paths: Vec = matches .get_many::(ARG_FILES) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 3dec9bef1..6ba16d101 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -16,6 +16,7 @@ use std::num::IntErrorKind; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; use uucore::format_usage; @@ -821,7 +822,7 @@ fn parse_settings(matches: &clap::ArgMatches) -> UResult { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let settings = parse_settings(&matches)?; diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 148489b2d..b1dc4dc92 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -13,6 +13,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value}; use uucore::{format_usage, show}; @@ -40,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args.collect_ignore(); let obs_signal = handle_obsolete(&mut args); - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mode = if matches.get_flag(options::TABLE) { Mode::Table diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 20528a700..327ef09b0 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -8,6 +8,7 @@ use clap::{Arg, Command}; use std::ffi::OsString; use std::fs::hard_link; use std::path::Path; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; use uucore::format_usage; @@ -19,7 +20,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let files: Vec<_> = matches .get_many::(options::FILES) .unwrap_or_default() diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index df53b16b8..5d5ce9bdc 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -23,6 +23,7 @@ use std::os::unix::fs::symlink; #[cfg(windows)] use std::os::windows::fs::{symlink_dir, symlink_file}; use std::path::{Path, PathBuf}; +use uucore::LocalizedCommand; use uucore::backup_control::{self, BackupMode}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; @@ -94,7 +95,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { backup_control::BACKUP_CONTROL_LONG_HELP ); - let matches = uu_app().after_help(after_help).try_get_matches_from(args)?; + let matches = uu_app() + .after_help(after_help) + .try_get_matches_from_localized(args); /* the list of files */ diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 77d8fe15c..d34712339 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -7,6 +7,7 @@ use clap::Command; use std::ffi::CStr; +use uucore::LocalizedCommand; use uucore::translate; use uucore::{error::UResult, show_error}; @@ -23,7 +24,7 @@ fn get_userlogin() -> Option { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _ = uu_app().try_get_matches_from(args)?; + let _ = uu_app().try_get_matches_from_localized(args); match get_userlogin() { Some(userlogin) => println!("{userlogin}"), diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f4c685863..5e9477c29 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1103,9 +1103,7 @@ impl Config { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let command = uu_app(); - - let matches = match command.try_get_matches_from(args) { + let matches = match uu_app().try_get_matches_from(args) { // clap successfully parsed the arguments: Ok(matches) => matches, // --help, --version, etc.: @@ -1118,7 +1116,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } // All other argument parsing errors cause exit code 2: Err(e) => { - return Err(USimpleError::new(2, e.to_string())); + uucore::clap_localization::handle_clap_error_with_exit_code(e, "ls", 2); } }; diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index b2c349337..d1cf51647 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -15,6 +15,7 @@ use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; use uucore::translate; +use uucore::LocalizedCommand; #[cfg(not(windows))] use uucore::mode; use uucore::{display::Quotable, fs::dir_strip_dot_for_creation}; @@ -81,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // " of each created directory to CTX"), let matches = uu_app() .after_help(translate!("mkdir-after-help")) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let dirs = matches .get_many::(options::DIRS) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 33d842d92..8032a5fad 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -12,6 +12,7 @@ use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{format_usage, show}; mod options { @@ -23,7 +24,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mode = calculate_mode(matches.get_one::(options::MODE)) .map_err(|e| USimpleError::new(1, translate!("mkfifo-error-invalid-mode", "error" => e)))?; diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 5bd79ade9..53b7eb8d2 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -10,6 +10,7 @@ use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOT use libc::{dev_t, mode_t}; use std::ffi::CString; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError, set_exit_code}; use uucore::format_usage; @@ -111,7 +112,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let file_type = matches.get_one::("type").unwrap(); diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 90ac7c875..b1dbacf71 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -333,6 +333,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = match uu_app().try_get_matches_from(&args) { Ok(m) => m, Err(e) => { + use uucore::clap_localization::handle_clap_error_with_exit_code; + if e.kind() == clap::error::ErrorKind::UnknownArgument { + handle_clap_error_with_exit_code(e, uucore::util_name(), 1); + } if e.kind() == clap::error::ErrorKind::TooManyValues && e.context().any(|(kind, val)| { kind == clap::error::ContextKind::InvalidArg diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 4bd2e80df..829e4f53b 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -26,6 +26,7 @@ use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::format_usage; use uucore::{display::Quotable, show}; +use uucore::LocalizedCommand; use uucore::translate; #[derive(Debug)] @@ -151,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { print!("\r"); println!("{panic_info}"); })); - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mut options = Options::from(&matches); if let Some(files) = matches.get_many::(options::FILES) { let length = files.len(); diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 948e486c9..0fd44643e 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -10,7 +10,8 @@ mod error; mod hardlink; use clap::builder::ValueParser; -use clap::{Arg, ArgAction, ArgMatches, Command, error::ErrorKind}; +use clap::error::ErrorKind; +use clap::{Arg, ArgAction, ArgMatches, Command}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] @@ -51,6 +52,7 @@ use uucore::update_control; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which // requires these enums +use uucore::LocalizedCommand; pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{format_usage, prompt_yes, show}; @@ -151,8 +153,7 @@ static OPT_SELINUX: &str = "selinux"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let mut app = uu_app(); - let matches = app.try_get_matches_from_mut(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let files: Vec = matches .get_many::(ARG_FILES) @@ -161,11 +162,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect(); if files.len() == 1 && !matches.contains_id(OPT_TARGET_DIRECTORY) { - app.error( - ErrorKind::TooFewValues, - translate!("mv-error-insufficient-arguments", "arg_files" => ARG_FILES), - ) - .exit(); + return Err(UUsageError::new( + 1, + format!( + "The argument '<{ARG_FILES}>...' requires at least 2 values, but only 1 was provided" + ), + )); } let overwrite_mode = determine_overwrite_mode(&matches); diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 1e5eb3d72..414f5d735 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -10,6 +10,7 @@ use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{format_usage, show_error}; mod helper; @@ -176,7 +177,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mut settings = Settings::default(); diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index f7be033bd..9d913d33e 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -7,6 +7,7 @@ use clap::{Arg, ArgAction, Command}; use std::{env, thread}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; @@ -26,7 +27,7 @@ static OPT_IGNORE: &str = "ignore"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let ignore = match matches.get_one::(OPT_IGNORE) { Some(numstr) => match numstr.trim().parse::() { diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index f8dddd8c6..852016495 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -13,6 +13,7 @@ use std::result::Result as StdResult; use std::str::FromStr; use units::{IEC_BASES, SI_BASES}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::UResult; use uucore::translate; @@ -254,7 +255,7 @@ fn parse_options(args: &ArgMatches) -> Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let options = parse_options(&matches).map_err(NumfmtError::IllegalArgument)?; diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 7db53b6b6..3562e38c3 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -46,6 +46,7 @@ use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::parser::parse_size::ParseSizeError; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, show_error, show_warning}; @@ -220,7 +221,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let clap_opts = uu_app(); - let clap_matches = clap_opts.try_get_matches_from(&args)?; + let clap_matches = clap_opts.try_get_matches_from_localized(&args); let od_options = OdOptions::new(&clap_matches, &args)?; diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index b6b9b59a8..6dc3d79e9 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -10,6 +10,7 @@ use std::io::{BufRead, BufReader, Stdin, Write, stdin, stdout}; use std::iter::Cycle; use std::rc::Rc; use std::slice::Iter; +use uucore::LocalizedCommand; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; use uucore::line_ending::LineEnding; @@ -24,7 +25,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let serial = matches.get_flag(options::SERIAL); let delimiters = matches.get_one::(options::DELIMITER).unwrap(); diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 3b7a3c164..d5a318f0d 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -8,6 +8,7 @@ use clap::{Arg, ArgAction, Command}; use std::fs; use std::io::{ErrorKind, Write}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError, set_exit_code}; use uucore::format_usage; @@ -34,7 +35,7 @@ const POSIX_NAME_MAX: usize = 14; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); // set working mode let is_posix = matches.get_flag(options::POSIX); diff --git a/src/uu/pinky/src/platform/openbsd.rs b/src/uu/pinky/src/platform/openbsd.rs index fb7cd155b..c53839c47 100644 --- a/src/uu/pinky/src/platform/openbsd.rs +++ b/src/uu/pinky/src/platform/openbsd.rs @@ -5,11 +5,12 @@ // Specific implementation for OpenBSD: tool unsupported (utmpx not supported) use crate::uu_app; +use uucore::LocalizedCommand; use uucore::error::UResult; use uucore::translate; pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _matches = uu_app().try_get_matches_from(args)?; + let _matches = uu_app().try_get_matches_from_localized(args); println!("{}", translate!("pinky-unsupported-openbsd")); Ok(()) } diff --git a/src/uu/pinky/src/platform/unix.rs b/src/uu/pinky/src/platform/unix.rs index 59c226046..e80f02d05 100644 --- a/src/uu/pinky/src/platform/unix.rs +++ b/src/uu/pinky/src/platform/unix.rs @@ -9,6 +9,7 @@ use crate::Capitalize; use crate::options; use crate::uu_app; +use uucore::LocalizedCommand; use uucore::entries::{Locate, Passwd}; use uucore::error::{FromIo, UResult}; use uucore::libc::S_IWGRP; @@ -34,7 +35,7 @@ fn get_long_usage() -> String { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(get_long_usage()) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let users: Vec = matches .get_many::(options::USER) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index fbad8c93a..3e7bcd3d9 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -16,6 +16,7 @@ use std::os::unix::fs::FileTypeExt; use std::time::SystemTime; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::UResult; use uucore::format_usage; @@ -315,8 +316,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let opt_args = recreate_arguments(&args); - let mut command = uu_app(); - let matches = command.try_get_matches_from_mut(opt_args)?; + let command = uu_app(); + let matches = command.try_get_matches_from_mut_localized(opt_args); let mut files = matches .get_many::(options::FILES) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 063be33ff..6d875c537 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -5,6 +5,7 @@ use clap::{Arg, ArgAction, Command}; use std::env; +use uucore::LocalizedCommand; use uucore::translate; use uucore::{error::UResult, format_usage}; @@ -14,7 +15,7 @@ static ARG_VARIABLES: &str = "variables"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from(args); + let matches = uu_app().try_get_matches_from_localized(args); let variables: Vec = matches .get_many::(ARG_VARIABLES) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 2c536bcb6..08408b40b 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -6,6 +6,7 @@ use clap::{Arg, ArgAction, Command}; use std::ffi::OsString; use std::io::stdout; use std::ops::ControlFlow; +use uucore::LocalizedCommand; use uucore::error::{UResult, UUsageError}; use uucore::format::{FormatArgument, FormatArguments, FormatItem, parse_spec_and_escape}; use uucore::translate; @@ -21,7 +22,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from(args); + let matches = uu_app().try_get_matches_from_localized(args); let format = matches .get_one::(options::FORMAT) diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 78ec37a47..f2a26ae82 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -15,6 +15,7 @@ use std::num::ParseIntError; use clap::{Arg, ArgAction, Command}; use regex::Regex; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, UUsageError}; use uucore::format_usage; @@ -728,7 +729,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let config = get_config(&matches)?; let input_files; diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 39dad5f9e..5fe5ae0ae 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -13,6 +13,7 @@ use uucore::format_usage; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; +use uucore::LocalizedCommand; use uucore::translate; const OPT_LOGICAL: &str = "logical"; const OPT_PHYSICAL: &str = "physical"; @@ -109,7 +110,7 @@ fn logical_path() -> io::Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); // if POSIXLY_CORRECT is set, we want to a logical resolution. // This produces a different output when doing mkdir -p a/b && ln -s a/b c && cd c && pwd // We should get c in this case instead of a/b at the end of the path diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 8f61d9ab2..c9f06ff5b 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -9,6 +9,7 @@ use clap::{Arg, ArgAction, Command}; use std::fs; use std::io::{Write, stdout}; use std::path::{Path, PathBuf}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; @@ -29,7 +30,7 @@ const ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mut no_trailing_delimiter = matches.get_flag(OPT_NO_NEWLINE); let use_zero = matches.get_flag(OPT_ZERO); diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index f7971aa80..9595e25f6 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -23,6 +23,7 @@ use uucore::error::{FromIo, UError, UResult}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{format_usage, os_str_as_bytes, prompt_yes, show_error}; #[derive(Debug, Error)] @@ -143,7 +144,7 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let files: Vec<_> = matches .get_many::(ARG_FILES) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 34346c0d6..bbbcae744 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -15,6 +15,7 @@ use uucore::display::Quotable; use uucore::error::{UResult, set_exit_code, strip_errno}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{format_usage, show_error, util_name}; static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; @@ -25,7 +26,7 @@ static ARG_DIRS: &str = "dirs"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let opts = Opts { ignore: matches.get_flag(OPT_IGNORE_FAIL_NON_EMPTY), diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index a34797835..73f4c067c 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -14,6 +14,7 @@ use std::io::{self, Read, Seek, Write}; #[cfg(unix)] use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parser::parse_size::parse_size_u64; @@ -238,7 +239,7 @@ impl<'a> BytesWriter<'a> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); if !matches.contains_id(options::FILE) { return Err(UUsageError::new( diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 254875eaa..d2e854b9b 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -17,6 +17,7 @@ use std::io::{BufWriter, Error, Read, Write, stdin, stdout}; use std::ops::RangeInclusive; use std::path::{Path, PathBuf}; use std::str::FromStr; +use uucore::LocalizedCommand; use uucore::display::{OsWrite, Quotable}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::format_usage; @@ -51,7 +52,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mode = if matches.get_flag(options::ECHO) { Mode::Echo( diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 1731c2af1..37d54ee99 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -6,6 +6,7 @@ use clap::{Arg, ArgAction, Command}; use std::thread; use std::time::Duration; +use uucore::LocalizedCommand; use uucore::translate; use uucore::{ error::{UResult, USimpleError, UUsageError}, @@ -20,7 +21,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let numbers = matches .get_many::(options::NUMBER) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 74b6253fb..29346315b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1050,6 +1050,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // nor return with a non-zero exit code in this case (we should print to stdout and return 0). // This logic is similar to the code in clap, but we return 2 as the exit code in case of real failure // (clap returns 1). + use uucore::clap_localization::handle_clap_error_with_exit_code; + if e.kind() == clap::error::ErrorKind::UnknownArgument { + handle_clap_error_with_exit_code(e, uucore::util_name(), 2); + } e.print().unwrap(); if e.use_stderr() { set_exit_code(2); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index b8351c31e..a2c85464f 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -26,6 +26,7 @@ use uucore::translate; use uucore::parser::parse_size::parse_size_u64; +use uucore::LocalizedCommand; use uucore::format_usage; use uucore::uio_error; @@ -51,7 +52,7 @@ static ARG_PREFIX: &str = "prefix"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (args, obs_lines) = handle_obsolete(args); - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); match Settings::from(&matches, obs_lines.as_deref()) { Ok(settings) => split(&settings), diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 6bc0aa0b3..f39e24842 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -27,6 +27,7 @@ use std::path::Path; use std::{env, fs}; use thiserror::Error; +use uucore::LocalizedCommand; use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec}; #[derive(Debug, Error)] @@ -1220,7 +1221,7 @@ impl Stater { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(translate!("stat-after-help")) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let stater = Stater::new(&matches)?; let exit_status = stater.exec(); diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 5de8f9e36..63b0a6e62 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -29,6 +29,7 @@ use std::num::IntErrorKind; use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; +use uucore::LocalizedCommand; use uucore::error::{UError, UResult, USimpleError}; use uucore::format_usage; use uucore::translate; @@ -242,7 +243,7 @@ ioctl_write_ptr_bad!( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let opts = Options::from(&matches)?; diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 8359ec002..061fc2509 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -13,6 +13,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{format_usage, show}; fn bsd_sum(mut reader: impl Read) -> std::io::Result<(usize, u16)> { @@ -98,7 +99,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let files: Vec = match matches.get_many::(options::FILE) { Some(v) => v.cloned().collect(), diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 95af36c80..9e643aa7c 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -13,6 +13,7 @@ use nix::fcntl::{OFlag, open}; #[cfg(any(target_os = "linux", target_os = "android"))] use nix::sys::stat::Mode; use std::path::Path; +use uucore::LocalizedCommand; use uucore::display::Quotable; #[cfg(any(target_os = "linux", target_os = "android"))] use uucore::error::FromIo; @@ -173,7 +174,7 @@ mod platform { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let files: Vec = matches .get_many::(ARG_FILES) .map(|v| v.map(ToString::to_string).collect()) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 52d885bf4..4911a4b09 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -21,6 +21,7 @@ use uucore::{format_usage, show}; use crate::error::TacError; +use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -32,7 +33,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let before = matches.get_flag(options::BEFORE); let regex = matches.get_flag(options::REGEX); diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index d7d4b6704..14c015bf7 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -17,6 +17,7 @@ use uucore::{format_usage, show_error}; // spell-checker:ignore nopipe +use uucore::LocalizedCommand; #[cfg(unix)] use uucore::signals::{enable_pipe_errors, ignore_interrupts}; @@ -51,7 +52,7 @@ enum OutputErrorMode { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let append = matches.get_flag(options::APPEND); let ignore_interrupts = matches.get_flag(options::IGNORE_INTERRUPTS); diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index fde4cdc1b..1f413e4e0 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -20,6 +20,7 @@ use std::ffi::OsString; use std::fs::{self, File}; use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; @@ -186,7 +187,7 @@ fn shr2(s: &str) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mut filenames: Vec<&String> = matches .get_many::(ARG_FILES) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 5e5316dbc..fbcde13d4 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -13,6 +13,7 @@ use operation::{ }; use std::ffi::OsString; use std::io::{Write, stdin, stdout}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; @@ -40,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let delete_flag = matches.get_flag(options::DELETE); let complement_flag = matches.get_flag(options::COMPLEMENT); diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 646303bab..dab1ffc9c 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -11,6 +11,7 @@ use uucore::display::Quotable; use uucore::error::{UError, UResult}; use uucore::{format_usage, show}; +use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -43,7 +44,7 @@ impl UError for TsortError {} #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let input = matches .get_one::(options::FILE) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index d5c843fcd..b893fa8b4 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -18,7 +18,10 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from(args); + let matches = uu_app().try_get_matches_from(args).unwrap_or_else(|e| { + use uucore::clap_localization::handle_clap_error_with_exit_code; + handle_clap_error_with_exit_code(e, uucore::util_name(), 2) + }); let silent = matches.get_flag(options::SILENT); diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 092ec1a64..c30c00bb4 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -7,6 +7,7 @@ use clap::{Arg, ArgAction, Command}; use platform_info::*; +use uucore::LocalizedCommand; use uucore::translate; use uucore::{ error::{UResult, USimpleError}, @@ -120,7 +121,7 @@ pub struct Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let options = Options { all: matches.get_flag(options::ALL), diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 47d6e04d0..c5935b40e 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -9,6 +9,7 @@ use std::path::Path; use clap::builder::ValueParser; use clap::{Arg, Command}; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; use uucore::format_usage; @@ -18,7 +19,7 @@ static OPT_PATH: &str = "FILE"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let path: &Path = matches.get_one::(OPT_PATH).unwrap().as_ref(); diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 41d0f6464..d8470e196 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -17,6 +17,7 @@ use uucore::uptime::*; use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser}; +use uucore::LocalizedCommand; use uucore::format_usage; #[cfg(unix)] @@ -47,7 +48,7 @@ impl UError for UptimeError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); #[cfg(unix)] let file_path = matches.get_one::(options::PATH); diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 67761ca06..92c50d7cc 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -16,6 +16,7 @@ use uucore::translate; #[cfg(target_os = "openbsd")] use utmp_classic::{UtmpEntry, parse_from_path}; +use uucore::LocalizedCommand; #[cfg(not(target_os = "openbsd"))] use uucore::utmpx::{self, Utmpx}; @@ -37,7 +38,7 @@ fn get_long_usage() -> String { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(get_long_usage()) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let maybe_file: Option<&Path> = matches.get_one::(ARG_FILE).map(AsRef::as_ref); diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 920f4602f..140001c9e 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -26,6 +26,7 @@ use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::{ error::{FromIo, UError, UResult}, format_usage, @@ -376,7 +377,7 @@ impl UError for WcError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let settings = Settings::new(&matches); let inputs = Inputs::new(&matches)?; diff --git a/src/uu/who/src/platform/openbsd.rs b/src/uu/who/src/platform/openbsd.rs index 8e0bbd3b9..8d1e31dab 100644 --- a/src/uu/who/src/platform/openbsd.rs +++ b/src/uu/who/src/platform/openbsd.rs @@ -7,11 +7,12 @@ use crate::uu_app; +use uucore::LocalizedCommand; use uucore::error::UResult; use uucore::translate; pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _matches = uu_app().try_get_matches_from(args)?; + let _matches = uu_app().try_get_matches_from_localized(args); println!("{}", translate!("who-unsupported-openbsd")); Ok(()) } diff --git a/src/uu/who/src/platform/unix.rs b/src/uu/who/src/platform/unix.rs index f9f322a5d..70b46a593 100644 --- a/src/uu/who/src/platform/unix.rs +++ b/src/uu/who/src/platform/unix.rs @@ -13,6 +13,7 @@ use uucore::error::{FromIo, UResult}; use uucore::libc::{S_IWGRP, STDIN_FILENO, ttyname}; use uucore::translate; +use uucore::LocalizedCommand; use uucore::utmpx::{self, Utmpx, time}; use std::borrow::Cow; @@ -28,7 +29,7 @@ fn get_long_usage() -> String { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(get_long_usage()) - .try_get_matches_from(args)?; + .try_get_matches_from_localized(args); let files: Vec = matches .get_many::(options::FILE) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 928e81edf..3b0b11ead 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -5,6 +5,7 @@ use clap::Command; use std::ffi::OsString; +use uucore::LocalizedCommand; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; use uucore::translate; @@ -13,7 +14,7 @@ mod platform; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().try_get_matches_from(args)?; + uu_app().try_get_matches_from_localized(args); let username = whoami()?; println_verbatim(username).map_err_context(|| translate!("whoami-error-failed-to-print"))?; Ok(()) diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 6b2bc6495..ec818a955 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -9,6 +9,7 @@ use clap::{Arg, ArgAction, Command, builder::ValueParser}; use std::error::Error; use std::ffi::OsString; use std::io::{self, Write}; +use uucore::LocalizedCommand; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; #[cfg(unix)] @@ -21,7 +22,7 @@ const BUF_SIZE: usize = 16 * 1024; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from_localized(args); let mut buffer = Vec::with_capacity(BUF_SIZE); args_into_buffer(&mut buffer, matches.get_many::("STRING")).unwrap(); From 8767cfd39f8981dfd6d7df042c16e2ad5c5b41d8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 10:51:29 +0200 Subject: [PATCH 09/24] l10n/github action: verify that clap localization works --- .github/workflows/l10n.yml | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml index b4a151d81..ccec22d54 100644 --- a/.github/workflows/l10n.yml +++ b/.github/workflows/l10n.yml @@ -129,6 +129,176 @@ jobs: echo "::notice::All Fluent files passed Mozilla Fluent Linter validation" + l10n_clap_error_localization: + name: L10n/Clap Error Localization Test + runs-on: ubuntu-latest + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + - name: Install/setup prerequisites + shell: bash + run: | + sudo apt-get -y update ; sudo apt-get -y install libselinux1-dev locales + sudo locale-gen --keep-existing fr_FR.UTF-8 + locale -a | grep -i fr || exit 1 + - name: Build coreutils with clap localization support + shell: bash + run: | + cargo build --features feat_os_unix --bin coreutils + - name: Test English clap error localization + shell: bash + run: | + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + + # Test invalid argument error - should show colored error message + echo "Testing invalid argument error..." + error_output=$(cargo run --features feat_os_unix --bin coreutils -- cp --invalid-arg 2>&1 || echo "Expected error occurred") + echo "Error output: $error_output" + + # Check for expected English clap error patterns + english_errors_found=0 + + if echo "$error_output" | grep -q "error.*unexpected argument"; then + echo "✓ Found English clap error message pattern" + english_errors_found=$((english_errors_found + 1)) + fi + + if echo "$error_output" | grep -q "Usage:"; then + echo "✓ Found English usage pattern" + english_errors_found=$((english_errors_found + 1)) + fi + + if echo "$error_output" | grep -q "For more information.*--help"; then + echo "✓ Found English help suggestion" + english_errors_found=$((english_errors_found + 1)) + fi + + # Test typo suggestion + echo "Testing typo suggestion..." + typo_output=$(cargo run --features feat_os_unix --bin coreutils -- ls --verbos 2>&1 || echo "Expected error occurred") + echo "Typo output: $typo_output" + + if echo "$typo_output" | grep -q "similar.*verbose"; then + echo "✓ Found English typo suggestion" + english_errors_found=$((english_errors_found + 1)) + fi + + echo "English clap errors found: $english_errors_found" + if [ "$english_errors_found" -ge 2 ]; then + echo "✓ SUCCESS: English clap error localization working" + else + echo "✗ ERROR: English clap error localization not working properly" + exit 1 + fi + env: + RUST_BACKTRACE: "1" + + - name: Test French clap error localization + shell: bash + run: | + export LANG=fr_FR.UTF-8 + export LC_ALL=fr_FR.UTF-8 + + # Test invalid argument error - should show French colored error message + echo "Testing invalid argument error in French..." + error_output=$(cargo run --features feat_os_unix --bin coreutils -- cp --invalid-arg 2>&1 || echo "Expected error occurred") + echo "French error output: $error_output" + + # Check for expected French clap error patterns + french_errors_found=0 + + if echo "$error_output" | grep -q "erreur.*argument inattendu"; then + echo "✓ Found French clap error message: 'erreur: argument inattendu'" + french_errors_found=$((french_errors_found + 1)) + fi + + if echo "$error_output" | grep -q "conseil.*pour passer.*comme valeur"; then + echo "✓ Found French tip message: 'conseil: pour passer ... comme valeur'" + french_errors_found=$((french_errors_found + 1)) + fi + + if echo "$error_output" | grep -q "Utilisation:"; then + echo "✓ Found French usage pattern: 'Utilisation:'" + french_errors_found=$((french_errors_found + 1)) + fi + + if echo "$error_output" | grep -q "Pour plus d'informations.*--help"; then + echo "✓ Found French help suggestion: 'Pour plus d'informations'" + french_errors_found=$((french_errors_found + 1)) + fi + + # Test typo suggestion in French + echo "Testing typo suggestion in French..." + typo_output=$(cargo run --features feat_os_unix --bin coreutils -- ls --verbos 2>&1 || echo "Expected error occurred") + echo "French typo output: $typo_output" + + if echo "$typo_output" | grep -q "conseil.*similaire.*verbose"; then + echo "✓ Found French typo suggestion with 'conseil'" + french_errors_found=$((french_errors_found + 1)) + fi + + echo "French clap errors found: $french_errors_found" + if [ "$french_errors_found" -ge 2 ]; then + echo "✓ SUCCESS: French clap error localization working - found $french_errors_found French patterns" + else + echo "✗ ERROR: French clap error localization not working properly" + echo "Note: This might be expected if French common locale files are not available" + # Don't fail the build - French clap localization might not be fully set up yet + echo "::warning::French clap error localization not working, but continuing" + fi + + # Test that colors are working (ANSI escape codes) + echo "Testing ANSI color codes in error output..." + if echo "$error_output" | grep -q $'\x1b\[3[0-7]m'; then + echo "✓ Found ANSI color codes in error output" + else + echo "✗ No ANSI color codes found - colors may not be working" + echo "::warning::ANSI color codes not detected in clap error output" + fi + env: + RUST_BACKTRACE: "1" + + - name: Test clap localization with multiple utilities + shell: bash + run: | + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + + utilities_to_test=("ls" "cat" "touch" "cp" "mv") + utilities_passed=0 + + for util in "${utilities_to_test[@]}"; do + echo "Testing $util with invalid argument..." + util_error=$(cargo run --features feat_os_unix --bin coreutils -- "$util" --nonexistent-flag 2>&1 || echo "Expected error occurred") + + if echo "$util_error" | grep -q "error.*unexpected argument"; then + echo "✓ $util: clap localization working" + utilities_passed=$((utilities_passed + 1)) + else + echo "✗ $util: clap localization not working" + echo "Output: $util_error" + fi + done + + echo "Utilities with working clap localization: $utilities_passed/${#utilities_to_test[@]}" + if [ "$utilities_passed" -ge 3 ]; then + echo "✓ SUCCESS: Clap localization working across multiple utilities" + else + echo "✗ ERROR: Clap localization not working for enough utilities" + exit 1 + fi + env: + RUST_BACKTRACE: "1" + l10n_french_integration: name: L10n/French Integration Test runs-on: ubuntu-latest From 0881b6ec0d89200cdf43c6733c6f260e96e8ff73 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 22:22:31 +0200 Subject: [PATCH 10/24] add test (in sort) to verify that the clap error mgmt work (english and french) --- tests/by-util/test_sort.rs | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 657a3addd..15872affc 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1568,3 +1568,42 @@ fn test_g_float_hex() { .succeeds() .stdout_is(output); } + +/* spell-checker: disable */ +#[test] +fn test_french_translations() { + // Test that French translations work for clap error messages + // Set LANG to French and test with an invalid argument + let result = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--invalid-arg") + .fails(); + + let stderr = result.stderr_str(); + assert!(stderr.contains("erreur")); + assert!(stderr.contains("argument inattendu")); + assert!(stderr.contains("trouvé")); +} + +#[test] +fn test_argument_suggestion() { + let test_cases = vec![ + ("en_US.UTF-8", vec!["tip", "similar", "--reverse"]), + ("fr_FR.UTF-8", vec!["conseil", "similaire", "--reverse"]), + ]; + + for (locale, expected_strings) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .arg("--revrse") // Typo + .fails(); + + let stderr = result.stderr_str(); + for expected in expected_strings { + assert!(stderr.contains(expected)); + } + } +} +/* spell-checker: enable */ From 19aa5f8854f86fcc1d9ba485fd5e7ff7605986b9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 22:55:18 +0200 Subject: [PATCH 11/24] cspell: also ignore flt files in uucore --- .vscode/cSpell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index 199830c2d..51dd4a30c 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -27,7 +27,8 @@ "src/uu/dd/test-resources/**", "vendor/**", "**/*.svg", - "src/uu/*/locales/*.ftl" + "src/uu/*/locales/*.ftl", + "src/uucore/locales/*.ftl" ], "enableGlobDot": true, From 90b4fd743f3fb9cc00f8fae7a2dbd7b2a927fe62 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 5 Aug 2025 10:44:12 +0200 Subject: [PATCH 12/24] l10n: also install uucore locales --- GNUmakefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 54aea865f..20dc731d3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -418,6 +418,14 @@ endif ifeq ($(LOCALES),y) locales: + @# Copy uucore common locales + @if [ -d "$(BASEDIR)/src/uucore/locales" ]; then \ + mkdir -p "$(BUILDDIR)/locales/uucore"; \ + for locale_file in "$(BASEDIR)"/src/uucore/locales/*.ftl; do \ + $(INSTALL) -v "$$locale_file" "$(BUILDDIR)/locales/uucore/"; \ + done; \ + fi; \ + # Copy utility-specific locales @for prog in $(INSTALLEES); do \ if [ -d "$(BASEDIR)/src/uu/$$prog/locales" ]; then \ mkdir -p "$(BUILDDIR)/locales/$$prog"; \ From d76a8d1b14b16d758b10733d9d3a81e628735be7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 18:49:04 +0200 Subject: [PATCH 13/24] clap localization: address PR review comments - Make colorize() function private since only used internally - Remove redundant colors_enabled checks - Add apply_color helper closure to reduce code duplication - Remove try_ prefix from function names for consistency - Update all utilities to use renamed functions - Fix app variable reference in mv utility --- src/uu/arch/src/arch.rs | 2 +- src/uu/base32/src/base_common.rs | 2 +- src/uu/basename/src/basename.rs | 2 +- src/uu/cat/src/cat.rs | 2 +- src/uu/chcon/src/chcon.rs | 2 +- src/uu/chmod/src/chmod.rs | 2 +- src/uu/cksum/src/cksum.rs | 2 +- src/uu/comm/src/comm.rs | 2 +- src/uu/cp/src/cp.rs | 2 +- src/uu/csplit/src/csplit.rs | 2 +- src/uu/cut/src/cut.rs | 2 +- src/uu/date/src/date.rs | 2 +- src/uu/dd/src/dd.rs | 2 +- src/uu/df/src/df.rs | 2 +- src/uu/dircolors/src/dircolors.rs | 2 +- src/uu/dirname/src/dirname.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/factor/src/factor.rs | 2 +- src/uu/fmt/src/fmt.rs | 2 +- src/uu/fold/src/fold.rs | 2 +- src/uu/groups/src/groups.rs | 2 +- src/uu/hashsum/src/hashsum.rs | 2 +- src/uu/head/src/head.rs | 2 +- src/uu/hostid/src/hostid.rs | 2 +- src/uu/hostname/src/hostname.rs | 2 +- src/uu/id/src/id.rs | 2 +- src/uu/install/src/install.rs | 2 +- src/uu/join/src/join.rs | 2 +- src/uu/kill/src/kill.rs | 2 +- src/uu/link/src/link.rs | 2 +- src/uu/ln/src/ln.rs | 2 +- src/uu/logname/src/logname.rs | 2 +- src/uu/mkdir/src/mkdir.rs | 2 +- src/uu/mkfifo/src/mkfifo.rs | 2 +- src/uu/mknod/src/mknod.rs | 2 +- src/uu/more/src/more.rs | 2 +- src/uu/mv/src/mv.rs | 13 +- src/uu/nl/src/nl.rs | 2 +- src/uu/nproc/src/nproc.rs | 2 +- src/uu/numfmt/src/numfmt.rs | 2 +- src/uu/od/src/od.rs | 2 +- src/uu/paste/src/paste.rs | 2 +- src/uu/pathchk/src/pathchk.rs | 2 +- src/uu/pinky/src/platform/openbsd.rs | 2 +- src/uu/pinky/src/platform/unix.rs | 2 +- src/uu/pr/src/pr.rs | 2 +- src/uu/printenv/src/printenv.rs | 2 +- src/uu/printf/src/printf.rs | 2 +- src/uu/ptx/src/ptx.rs | 2 +- src/uu/pwd/src/pwd.rs | 2 +- src/uu/readlink/src/readlink.rs | 2 +- src/uu/rm/src/rm.rs | 2 +- src/uu/rmdir/src/rmdir.rs | 2 +- src/uu/shred/src/shred.rs | 2 +- src/uu/shuf/src/shuf.rs | 2 +- src/uu/sleep/src/sleep.rs | 2 +- src/uu/split/src/split.rs | 2 +- src/uu/stat/src/stat.rs | 2 +- src/uu/stty/src/stty.rs | 2 +- src/uu/sum/src/sum.rs | 2 +- src/uu/sync/src/sync.rs | 2 +- src/uu/tac/src/tac.rs | 2 +- src/uu/tee/src/tee.rs | 2 +- src/uu/touch/src/touch.rs | 2 +- src/uu/tr/src/tr.rs | 2 +- src/uu/tsort/src/tsort.rs | 2 +- src/uu/uname/src/uname.rs | 2 +- src/uu/unlink/src/unlink.rs | 2 +- src/uu/uptime/src/uptime.rs | 2 +- src/uu/users/src/users.rs | 2 +- src/uu/wc/src/wc.rs | 2 +- src/uu/who/src/platform/openbsd.rs | 2 +- src/uu/who/src/platform/unix.rs | 2 +- src/uu/whoami/src/whoami.rs | 2 +- src/uu/yes/src/yes.rs | 2 +- src/uucore/src/lib/mods/clap_localization.rs | 211 +++++++++++++------ tests/by-util/test_du.rs | 10 + tests/by-util/test_mv.rs | 24 +++ tests/test_util_name.rs | 1 + 79 files changed, 267 insertions(+), 140 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index cd398a72b..3b7b4dc10 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -12,7 +12,7 @@ use uucore::translate; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().try_get_matches_from_localized(args); + uu_app().get_matches_from_localized(args); let uts = PlatformInfo::new().map_err(|_e| USimpleError::new(1, translate!("cannot-get-system")))?; diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index e60ea5366..619eb9b4a 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -100,7 +100,7 @@ pub fn parse_base_cmd_args( usage: &str, ) -> UResult { let command = base_app(about, usage); - let matches = command.try_get_matches_from_localized(args); + let matches = command.get_matches_from_localized(args); Config::from(&matches) } diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 802eb4464..557d981d7 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -30,7 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // // Argument parsing // - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 22cd38f38..f69364310 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -231,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let number_mode = if matches.get_flag(options::NUMBER_NONBLANK) { NumberingMode::NonEmpty diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index de1dc2f64..263d73475 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -304,7 +304,7 @@ struct Options { } fn parse_command_line(config: Command, args: impl uucore::Args) -> Result { - let matches = config.try_get_matches_from_localized(args); + let matches = config.get_matches_from_localized(args); let verbose = matches.get_flag(options::VERBOSE); diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index cb540aa7a..01fa0422c 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -113,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name let matches = uu_app() .after_help(translate!("chmod-after-help")) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let changes = matches.get_flag(options::CHANGES); let quiet = matches.get_flag(options::QUIET); diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 959c23e40..dd36cfefc 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -237,7 +237,7 @@ fn handle_tag_text_binary_flags>( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let check = matches.get_flag(options::CHECK); diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 81bcab418..8ba393b5b 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -281,7 +281,7 @@ fn open_file(name: &str, line_ending: LineEnding) -> io::Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); let filename1 = matches.get_one::(options::FILE_1).unwrap(); let filename2 = matches.get_one::(options::FILE_2).unwrap(); diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 3dee0fe01..01c08324e 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -779,7 +779,7 @@ pub fn uu_app() -> Command { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let options = Options::from_matches(&matches)?; diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 8c98d37c5..4a13ad40f 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -605,7 +605,7 @@ where #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); // get the file to split let file_name = matches.get_one::(options::FILE).unwrap(); diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index cf9bfdbe4..1bc8cb0d6 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -483,7 +483,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }) .collect(); - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let complement = matches.get_flag(options::COMPLEMENT); let only_delimited = matches.get_flag(options::ONLY_DELIMITED); diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 1eadd6ce2..6a33aa74f 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -112,7 +112,7 @@ impl From<&str> for Rfc3339Format { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let format = if let Some(form) = matches.get_one::(OPT_FORMAT) { if !form.starts_with('+') { diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index d231da8b1..0de57fe70 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -1416,7 +1416,7 @@ fn is_fifo(filename: &str) -> bool { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let settings: Settings = Parser::new().parse( matches diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 2d6d34b23..7db47355d 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -407,7 +407,7 @@ impl UError for DfError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); #[cfg(windows)] { diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index f6bd9ec40..0a58ce37a 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -121,7 +121,7 @@ fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let files = matches .get_many::(options::FILE) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 73618d7e0..5f301edd7 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -22,7 +22,7 @@ mod options { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(translate!("dirname-after-help")) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index b43cee530..12d94edf1 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -581,7 +581,7 @@ fn read_files_from(file_name: &str) -> Result, std::io::Error> { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let summarize = matches.get_flag(options::SUMMARIZE); diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 6dec840da..1e7a176b6 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -80,7 +80,7 @@ fn write_result( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); // If matches find --exponents flag than variable print_exponents is true and p^e output format will be used. let print_exponents = matches.get_flag(options::EXPONENTS); diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 80c1af0e3..ed7ab7ff7 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -335,7 +335,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let matches = uu_app().try_get_matches_from_localized(&args); + let matches = uu_app().get_matches_from_localized(&args); let files = extract_files(&matches)?; diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index c3e743d85..34bc6b656 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -32,7 +32,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); let (args, obs_width) = handle_obsolete(&args[..]); - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let bytes = matches.get_flag(options::BYTES); let spaces = matches.get_flag(options::SPACES); diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 9d6947223..156555798 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -48,7 +48,7 @@ fn infallible_gid2grp(gid: &u32) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let users: Vec = matches .get_many::(options::USERS) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 4204b979e..199a09bc7 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -182,7 +182,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { // causes "error: " to be printed twice (once from crash!() and once from clap). With // the current setup, the name of the utility is not printed, but I think this is at // least somewhat better from a user's perspective. - let matches = command.try_get_matches_from_localized(args); + let matches = command.get_matches_from_localized(args); let input_length: Option<&usize> = if binary_name == "b2sum" { matches.get_one::(options::LENGTH) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index bfd8a076a..c3f1c2c0c 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -551,7 +551,7 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args_vec: Vec<_> = arg_iterate(args)?.collect(); - let matches = uu_app().try_get_matches_from_localized(args_vec); + let matches = uu_app().get_matches_from_localized(args_vec); let args = match HeadOptions::get_from(&matches) { Ok(o) => o, Err(s) => { diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index c29b57489..efbe75475 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -14,7 +14,7 @@ use uucore::translate; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().try_get_matches_from_localized(args); + uu_app().get_matches_from_localized(args); hostid(); Ok(()) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 74f2c48d9..734cfdbed 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -61,7 +61,7 @@ mod wsa { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); #[cfg(windows)] let _handle = wsa::start().map_err_context(|| translate!("hostname-error-winsock"))?; diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 688ac232a..86fd86a17 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -122,7 +122,7 @@ struct State { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(translate!("id-after-help")) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let users: Vec = matches .get_many::(options::ARG_USERS) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 338f90f6c..bb3663779 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -166,7 +166,7 @@ static ARG_FILES: &str = "files"; /// #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let paths: Vec = matches .get_many::(ARG_FILES) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 6ba16d101..a55c9afd5 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -822,7 +822,7 @@ fn parse_settings(matches: &clap::ArgMatches) -> UResult { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let settings = parse_settings(&matches)?; diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index b1dc4dc92..aef9b45e3 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -41,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args.collect_ignore(); let obs_signal = handle_obsolete(&mut args); - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mode = if matches.get_flag(options::TABLE) { Mode::Table diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 327ef09b0..8f18bf86b 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -20,7 +20,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let files: Vec<_> = matches .get_many::(options::FILES) .unwrap_or_default() diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 5d5ce9bdc..d9a7afbd7 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -97,7 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(after_help) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); /* the list of files */ diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index d34712339..bb34f4b74 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -24,7 +24,7 @@ fn get_userlogin() -> Option { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _ = uu_app().try_get_matches_from_localized(args); + let _ = uu_app().get_matches_from_localized(args); match get_userlogin() { Some(userlogin) => println!("{userlogin}"), diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index d1cf51647..c82309087 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -82,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // " of each created directory to CTX"), let matches = uu_app() .after_help(translate!("mkdir-after-help")) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let dirs = matches .get_many::(options::DIRS) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 8032a5fad..ae3466da6 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -24,7 +24,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mode = calculate_mode(matches.get_one::(options::MODE)) .map_err(|e| USimpleError::new(1, translate!("mkfifo-error-invalid-mode", "error" => e)))?; diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 53b7eb8d2..b67e48e0c 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -112,7 +112,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let file_type = matches.get_one::("type").unwrap(); diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 829e4f53b..473a7f859 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { print!("\r"); println!("{panic_info}"); })); - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mut options = Options::from(&matches); if let Some(files) = matches.get_many::(options::FILES) { let length = files.len(); diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 0fd44643e..b3ae7114e 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -153,7 +153,7 @@ static OPT_SELINUX: &str = "selinux"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let files: Vec = matches .get_many::(ARG_FILES) @@ -162,12 +162,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect(); if files.len() == 1 && !matches.contains_id(OPT_TARGET_DIRECTORY) { - return Err(UUsageError::new( - 1, - format!( - "The argument '<{ARG_FILES}>...' requires at least 2 values, but only 1 was provided" - ), - )); + let err = uu_app().error( + ErrorKind::TooFewValues, + translate!("mv-error-insufficient-arguments", "arg_files" => ARG_FILES), + ); + uucore::clap_localization::handle_clap_error_with_exit_code(err, uucore::util_name(), 1); } let overwrite_mode = determine_overwrite_mode(&matches); diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 414f5d735..f759b5926 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -177,7 +177,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mut settings = Settings::default(); diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 9d913d33e..7137ebda7 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -27,7 +27,7 @@ static OPT_IGNORE: &str = "ignore"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let ignore = match matches.get_one::(OPT_IGNORE) { Some(numstr) => match numstr.trim().parse::() { diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 852016495..008f51558 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -255,7 +255,7 @@ fn parse_options(args: &ArgMatches) -> Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let options = parse_options(&matches).map_err(NumfmtError::IllegalArgument)?; diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 3562e38c3..936e32c88 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -221,7 +221,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let clap_opts = uu_app(); - let clap_matches = clap_opts.try_get_matches_from_localized(&args); + let clap_matches = clap_opts.get_matches_from_localized(&args); let od_options = OdOptions::new(&clap_matches, &args)?; diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 6dc3d79e9..807e4debf 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -25,7 +25,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let serial = matches.get_flag(options::SERIAL); let delimiters = matches.get_one::(options::DELIMITER).unwrap(); diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index d5a318f0d..9016a4878 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -35,7 +35,7 @@ const POSIX_NAME_MAX: usize = 14; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); // set working mode let is_posix = matches.get_flag(options::POSIX); diff --git a/src/uu/pinky/src/platform/openbsd.rs b/src/uu/pinky/src/platform/openbsd.rs index c53839c47..d8dcbc928 100644 --- a/src/uu/pinky/src/platform/openbsd.rs +++ b/src/uu/pinky/src/platform/openbsd.rs @@ -10,7 +10,7 @@ use uucore::error::UResult; use uucore::translate; pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _matches = uu_app().try_get_matches_from_localized(args); + let _matches = uu_app().get_matches_from_localized(args); println!("{}", translate!("pinky-unsupported-openbsd")); Ok(()) } diff --git a/src/uu/pinky/src/platform/unix.rs b/src/uu/pinky/src/platform/unix.rs index e80f02d05..3581cedba 100644 --- a/src/uu/pinky/src/platform/unix.rs +++ b/src/uu/pinky/src/platform/unix.rs @@ -35,7 +35,7 @@ fn get_long_usage() -> String { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(get_long_usage()) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let users: Vec = matches .get_many::(options::USER) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 3e7bcd3d9..fef4ba5cc 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -317,7 +317,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let opt_args = recreate_arguments(&args); let command = uu_app(); - let matches = command.try_get_matches_from_mut_localized(opt_args); + let matches = command.get_matches_from_mut_localized(opt_args); let mut files = matches .get_many::(options::FILES) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 6d875c537..a7809b830 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -15,7 +15,7 @@ static ARG_VARIABLES: &str = "variables"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let variables: Vec = matches .get_many::(ARG_VARIABLES) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 08408b40b..f5a7bc67c 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -22,7 +22,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let format = matches .get_one::(options::FORMAT) diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index f2a26ae82..728c17d4c 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -729,7 +729,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let config = get_config(&matches)?; let input_files; diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 5fe5ae0ae..130b11096 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -110,7 +110,7 @@ fn logical_path() -> io::Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); // if POSIXLY_CORRECT is set, we want to a logical resolution. // This produces a different output when doing mkdir -p a/b && ln -s a/b c && cd c && pwd // We should get c in this case instead of a/b at the end of the path diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index c9f06ff5b..b9aca641c 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -30,7 +30,7 @@ const ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mut no_trailing_delimiter = matches.get_flag(OPT_NO_NEWLINE); let use_zero = matches.get_flag(OPT_ZERO); diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 9595e25f6..7030d0627 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -144,7 +144,7 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let files: Vec<_> = matches .get_many::(ARG_FILES) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index bbbcae744..25bc666b6 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -26,7 +26,7 @@ static ARG_DIRS: &str = "dirs"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let opts = Opts { ignore: matches.get_flag(OPT_IGNORE_FAIL_NON_EMPTY), diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 73f4c067c..adc0db2bd 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -239,7 +239,7 @@ impl<'a> BytesWriter<'a> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); if !matches.contains_id(options::FILE) { return Err(UUsageError::new( diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index d2e854b9b..3e80e745e 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -52,7 +52,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mode = if matches.get_flag(options::ECHO) { Mode::Echo( diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 37d54ee99..87141e571 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -21,7 +21,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let numbers = matches .get_many::(options::NUMBER) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index a2c85464f..2bcb5a3a0 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -52,7 +52,7 @@ static ARG_PREFIX: &str = "prefix"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (args, obs_lines) = handle_obsolete(args); - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); match Settings::from(&matches, obs_lines.as_deref()) { Ok(settings) => split(&settings), diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index f39e24842..0142f3dd5 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -1221,7 +1221,7 @@ impl Stater { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(translate!("stat-after-help")) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let stater = Stater::new(&matches)?; let exit_status = stater.exec(); diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 63b0a6e62..0391b32a5 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -243,7 +243,7 @@ ioctl_write_ptr_bad!( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let opts = Options::from(&matches)?; diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 061fc2509..c45ffc944 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -99,7 +99,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let files: Vec = match matches.get_many::(options::FILE) { Some(v) => v.cloned().collect(), diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 9e643aa7c..4efaebd5c 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -174,7 +174,7 @@ mod platform { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let files: Vec = matches .get_many::(ARG_FILES) .map(|v| v.map(ToString::to_string).collect()) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 4911a4b09..f24320183 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -33,7 +33,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let before = matches.get_flag(options::BEFORE); let regex = matches.get_flag(options::REGEX); diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 14c015bf7..29dfef9d5 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -52,7 +52,7 @@ enum OutputErrorMode { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let append = matches.get_flag(options::APPEND); let ignore_interrupts = matches.get_flag(options::IGNORE_INTERRUPTS); diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 1f413e4e0..d61802b3c 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -187,7 +187,7 @@ fn shr2(s: &str) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mut filenames: Vec<&String> = matches .get_many::(ARG_FILES) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index fbcde13d4..c254252fb 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -41,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let delete_flag = matches.get_flag(options::DELETE); let complement_flag = matches.get_flag(options::COMPLEMENT); diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index dab1ffc9c..a83478d20 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -44,7 +44,7 @@ impl UError for TsortError {} #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let input = matches .get_one::(options::FILE) diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index c30c00bb4..84dd4ca7c 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -121,7 +121,7 @@ pub struct Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let options = Options { all: matches.get_flag(options::ALL), diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index c5935b40e..4fc5492c6 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -19,7 +19,7 @@ static OPT_PATH: &str = "FILE"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let path: &Path = matches.get_one::(OPT_PATH).unwrap().as_ref(); diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index d8470e196..22a715306 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -48,7 +48,7 @@ impl UError for UptimeError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); #[cfg(unix)] let file_path = matches.get_one::(options::PATH); diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 92c50d7cc..9439a3efa 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -38,7 +38,7 @@ fn get_long_usage() -> String { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(get_long_usage()) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let maybe_file: Option<&Path> = matches.get_one::(ARG_FILE).map(AsRef::as_ref); diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 140001c9e..8b3070989 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -377,7 +377,7 @@ impl UError for WcError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let settings = Settings::new(&matches); let inputs = Inputs::new(&matches)?; diff --git a/src/uu/who/src/platform/openbsd.rs b/src/uu/who/src/platform/openbsd.rs index 8d1e31dab..4a2954d27 100644 --- a/src/uu/who/src/platform/openbsd.rs +++ b/src/uu/who/src/platform/openbsd.rs @@ -12,7 +12,7 @@ use uucore::error::UResult; use uucore::translate; pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _matches = uu_app().try_get_matches_from_localized(args); + let _matches = uu_app().get_matches_from_localized(args); println!("{}", translate!("who-unsupported-openbsd")); Ok(()) } diff --git a/src/uu/who/src/platform/unix.rs b/src/uu/who/src/platform/unix.rs index 70b46a593..7f7f5dc91 100644 --- a/src/uu/who/src/platform/unix.rs +++ b/src/uu/who/src/platform/unix.rs @@ -29,7 +29,7 @@ fn get_long_usage() -> String { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .after_help(get_long_usage()) - .try_get_matches_from_localized(args); + .get_matches_from_localized(args); let files: Vec = matches .get_many::(options::FILE) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 3b0b11ead..40398accf 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -14,7 +14,7 @@ mod platform; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().try_get_matches_from_localized(args); + uu_app().get_matches_from_localized(args); let username = whoami()?; println_verbatim(username).map_err_context(|| translate!("whoami-error-failed-to-print"))?; Ok(()) diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index ec818a955..5affbb6e3 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -22,7 +22,7 @@ const BUF_SIZE: usize = 16 * 1024; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from_localized(args); + let matches = uu_app().get_matches_from_localized(args); let mut buffer = Vec::with_capacity(BUF_SIZE); args_into_buffer(&mut buffer, matches.get_many::("STRING")).unwrap(); diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index f5aed26f2..e14dbb0b7 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -13,16 +13,79 @@ use clap::error::{ContextKind, ErrorKind}; use clap::{ArgMatches, Command, Error}; use std::ffi::OsString; -/// Apply color to text using ANSI escape codes -pub fn colorize(text: &str, color_code: &str) -> String { - format!("\x1b[{color_code}m{text}\x1b[0m") +/// Determines if a clap error should show simple help instead of full usage +/// Based on clap's own design patterns and error categorization +fn should_show_simple_help_for_clap_error(kind: ErrorKind) -> bool { + match kind { + // Most validation errors should show simple help + ErrorKind::InvalidValue + | ErrorKind::InvalidSubcommand + | ErrorKind::ValueValidation + | ErrorKind::InvalidUtf8 + | ErrorKind::ArgumentConflict + | ErrorKind::NoEquals => true, + + // Argument count and structural errors need special formatting + ErrorKind::TooFewValues + | ErrorKind::TooManyValues + | ErrorKind::WrongNumberOfValues + | ErrorKind::MissingSubcommand => false, + + // MissingRequiredArgument needs different handling + ErrorKind::MissingRequiredArgument => false, + + // Special cases - handle their own display + ErrorKind::DisplayHelp + | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand + | ErrorKind::DisplayVersion => false, + + // UnknownArgument gets special handling elsewhere, so mark as false here + ErrorKind::UnknownArgument => false, + + // System errors - keep simple + ErrorKind::Io | ErrorKind::Format => true, + + // Default for any new ErrorKind variants - be conservative and show simple help + _ => true, + } } -/// Color constants for consistent styling -pub mod colors { - pub const RED: &str = "31"; - pub const YELLOW: &str = "33"; - pub const GREEN: &str = "32"; +/// Color enum for consistent styling +#[derive(Debug, Clone, Copy)] +pub enum Color { + Red, + Yellow, + Green, +} + +impl Color { + fn code(self) -> &'static str { + match self { + Color::Red => "31", + Color::Yellow => "33", + Color::Green => "32", + } + } +} + +/// Apply color to text using ANSI escape codes +fn colorize(text: &str, color: Color) -> String { + format!("\x1b[{}m{text}\x1b[0m", color.code()) +} + +/// Display usage information and help suggestion for errors that require it +/// This consolidates the shared logic between clap errors and UUsageError +pub fn display_usage_and_help(util_name: &str) { + eprintln!(); + // Try to get usage information from localization + let usage_key = format!("{}-usage", util_name); + let usage_text = translate!(&usage_key); + let formatted_usage = crate::format_usage(&usage_text); + let usage_label = translate!("common-usage"); + eprintln!("{}: {}", usage_label, formatted_usage); + eprintln!(); + let help_msg = translate!("clap-error-help-suggestion", "command" => crate::execution_phrase()); + eprintln!("{help_msg}"); } pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { @@ -30,6 +93,19 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // If it's already initialized, that's fine - we'll use the existing one let _ = crate::locale::setup_localization_with_common(util_name); + // Check if colors are enabled by examining clap's rendered output + let rendered_str = err.render().to_string(); + let colors_enabled = rendered_str.contains("\x1b["); + + // Helper function to conditionally colorize text + let maybe_colorize = |text: &str, color: Color| -> String { + if colors_enabled { + colorize(text, color) + } else { + text.to_string() + } + }; + match err.kind() { ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { // For help and version, use clap's built-in formatting and exit with 0 @@ -38,13 +114,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: std::process::exit(0); } ErrorKind::UnknownArgument => { - // Use clap's rendering system but capture the output to check if colors are used - let rendered = err.render(); - let rendered_str = rendered.to_string(); - - // Simple check - if the rendered output contains ANSI escape codes, colors are enabled - let colors_enabled = rendered_str.contains("\x1b["); - + // UnknownArgument gets special handling for suggestions, but should still show simple help if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); @@ -52,16 +122,9 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let error_word = translate!("common-error"); let tip_word = translate!("common-tip"); - // Apply colors only if they're enabled in the original error - let (colored_arg, colored_error_word, colored_tip_word) = if colors_enabled { - ( - colorize(&arg_str, colors::YELLOW), - colorize(&error_word, colors::RED), - colorize(&tip_word, colors::GREEN), - ) - } else { - (arg_str.clone(), error_word.clone(), tip_word.clone()) - }; + let colored_arg = maybe_colorize(&arg_str, Color::Yellow); + let colored_error_word = maybe_colorize(&error_word, Color::Red); + let colored_tip_word = maybe_colorize(&tip_word, Color::Green); // Print main error message let error_msg = translate!( @@ -75,11 +138,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Show suggestion or generic tip let suggestion = err.get(ContextKind::SuggestedArg); if let Some(suggested_arg) = suggestion { - let colored_suggestion = if colors_enabled { - colorize(&suggested_arg.to_string(), colors::GREEN) - } else { - suggested_arg.to_string() - }; + let colored_suggestion = + maybe_colorize(&suggested_arg.to_string(), Color::Green); let suggestion_msg = translate!( "clap-error-similar-argument", "tip_word" => colored_tip_word, @@ -87,11 +147,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: ); eprintln!(" {suggestion_msg}"); } else { - let colored_tip_command = if colors_enabled { - colorize(&format!("-- {arg_str}"), colors::GREEN) - } else { - format!("-- {arg_str}") - }; + let colored_tip_command = + maybe_colorize(&format!("-- {arg_str}"), Color::Green); let tip_msg = translate!( "clap-error-pass-as-value", "arg" => colored_arg, @@ -101,36 +158,72 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: eprintln!(" {tip_msg}"); } - // Show usage and help + // Show usage information for unknown arguments but use simple --help format eprintln!(); + // Try to get usage information from localization + let usage_key = format!("{}-usage", util_name); + let usage_text = translate!(&usage_key); + let formatted_usage = crate::format_usage(&usage_text); let usage_label = translate!("common-usage"); - let usage_pattern = translate!(&format!("{util_name}-usage")); - eprintln!("{usage_label}: {usage_pattern}"); + eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); - - let help_msg = translate!("clap-error-help-suggestion", "command" => util_name); - eprintln!("{help_msg}"); + // Use simple --help format for GNU test compatibility + eprintln!("For more information, try '--help'."); std::process::exit(exit_code); } else { // Generic fallback case - let rendered = err.render(); - let rendered_str = rendered.to_string(); - let colors_enabled = rendered_str.contains("\x1b["); - - let colored_error_word = if colors_enabled { - colorize(&translate!("common-error"), colors::RED) - } else { - translate!("common-error") - }; + let colored_error_word = maybe_colorize(&translate!("common-error"), Color::Red); eprintln!("{colored_error_word}: unexpected argument"); std::process::exit(exit_code); } } + // Check if this is a simple validation error that should show simple help + kind if should_show_simple_help_for_clap_error(kind) => { + // For simple validation errors, use the same simple format as other errors + let lines: Vec<&str> = rendered_str.lines().collect(); + if let Some(main_error_line) = lines.first() { + // Keep the "error: " prefix for test compatibility + eprintln!("{}", main_error_line); + eprintln!(); + // Use the execution phrase for the help suggestion to match test expectations + eprintln!("For more information, try '--help'."); + } else { + // Fallback to original rendering if we can't parse + eprint!("{}", err.render()); + } + std::process::exit(exit_code); + } _ => { - // For other errors, print using clap's formatter but exit with code 1 - eprint!("{}", err.render()); - std::process::exit(1); + // For MissingRequiredArgument, use the full clap error as it includes proper usage + if matches!(err.kind(), ErrorKind::MissingRequiredArgument) { + eprint!("{}", err.render()); + std::process::exit(exit_code); + } + + // For TooFewValues and similar structural errors, use the full clap error + if matches!( + err.kind(), + ErrorKind::TooFewValues | ErrorKind::TooManyValues | ErrorKind::WrongNumberOfValues + ) { + eprint!("{}", err.render()); + std::process::exit(exit_code); + } + + // For other errors, show just the error and help suggestion + let rendered_str = err.render().to_string(); + let lines: Vec<&str> = rendered_str.lines().collect(); + + // Print error message (first line) + if let Some(first_line) = lines.first() { + eprintln!("{}", first_line); + } + + // For other errors, just show help suggestion + eprintln!(); + eprintln!("For more information, try '--help'."); + + std::process::exit(exit_code); } } } @@ -143,15 +236,15 @@ pub trait LocalizedCommand { where Self: Sized; - /// Try to get matches from args with localized error handling - fn try_get_matches_from_localized(self, itr: I) -> ArgMatches + /// Get matches from args with localized error handling + fn get_matches_from_localized(self, itr: I) -> ArgMatches where Self: Sized, I: IntoIterator, T: Into + Clone; - /// Try to get matches from mutable args with localized error handling - fn try_get_matches_from_mut_localized(self, itr: I) -> ArgMatches + /// Get matches from mutable args with localized error handling + fn get_matches_from_mut_localized(self, itr: I) -> ArgMatches where Self: Sized, I: IntoIterator, @@ -164,7 +257,7 @@ impl LocalizedCommand for Command { .unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1)) } - fn try_get_matches_from_localized(self, itr: I) -> ArgMatches + fn get_matches_from_localized(self, itr: I) -> ArgMatches where I: IntoIterator, T: Into + Clone, @@ -173,7 +266,7 @@ impl LocalizedCommand for Command { .unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1)) } - fn try_get_matches_from_mut_localized(mut self, itr: I) -> ArgMatches + fn get_matches_from_mut_localized(mut self, itr: I) -> ArgMatches where I: IntoIterator, T: Into + Clone, diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 6c4191a20..ba64152e7 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -856,6 +856,16 @@ fn test_du_invalid_threshold() { ts.ucmd().arg(format!("--threshold={threshold}")).fails(); } +#[test] +fn test_du_threshold_error_handling() { + // Test missing threshold value - the specific case from GNU test + new_ucmd!() + .arg("--threshold") + .fails() + .stderr_contains("a value is required for '--threshold ' but none was supplied") + .stderr_contains("For more information, try '--help'."); +} + #[test] fn test_du_apparent_size() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 8da94e864..2f2f5d1de 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2547,3 +2547,27 @@ fn test_mv_selinux_context() { let _ = std::fs::remove_file(at.plus_as_string(src)); } } + +#[test] +fn test_mv_error_usage_display_missing_arg() { + + new_ucmd!() + .arg("--target-directory=.") + .fails() + .code_is(1) + .stderr_contains("error: the following required arguments were not provided:") + .stderr_contains("...") + .stderr_contains("Usage: mv [OPTION]... [-T] SOURCE DEST") + .stderr_contains("For more information, try '--help'."); +} + +#[test] +fn test_mv_error_usage_display_too_few() { + new_ucmd!() + .arg("file1") + .fails() + .code_is(1) + .stderr_contains("requires at least 2 values, but only 1 was provided") + .stderr_contains("Usage: mv [OPTION]... [-T] SOURCE DEST") + .stderr_contains("For more information, try '--help'."); +} diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index a9fbb29d9..23dbccfe7 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -36,6 +36,7 @@ fn execution_phrase_double() { let output = Command::new(&scenario.bin_path) .arg("ls") .arg("--some-invalid-arg") + .env("LANG", "en_US.UTF-8") .output() .unwrap(); assert!( From f0290e4754dc9068dd3bf8a98743607afbad3f2b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 23:43:47 +0200 Subject: [PATCH 14/24] clap: override the usage section --- src/uucore/src/lib/mods/clap_localization.rs | 87 +++++++++++++------- tests/by-util/test_comm.rs | 15 ++++ 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index e14dbb0b7..5c47fa14e 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -114,57 +114,82 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: std::process::exit(0); } ErrorKind::UnknownArgument => { + // Force localization initialization - ignore any previous failures + crate::locale::setup_localization_with_common(util_name).ok(); + // UnknownArgument gets special handling for suggestions, but should still show simple help if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); - // Get the uncolored words from common strings - let error_word = translate!("common-error"); - let tip_word = translate!("common-tip"); + // Get the uncolored words from common strings with fallbacks + let error_word = { + let translated = translate!("common-error"); + if translated == "common-error" { "error".to_string() } else { translated } + }; + let tip_word = { + let translated = translate!("common-tip"); + if translated == "common-tip" { "tip".to_string() } else { translated } + }; let colored_arg = maybe_colorize(&arg_str, Color::Yellow); let colored_error_word = maybe_colorize(&error_word, Color::Red); let colored_tip_word = maybe_colorize(&tip_word, Color::Green); - // Print main error message - let error_msg = translate!( - "clap-error-unexpected-argument", - "arg" => colored_arg.clone(), - "error_word" => colored_error_word - ); + // Print main error message with fallback + let error_msg = { + let translated = translate!( + "clap-error-unexpected-argument", + "arg" => colored_arg.clone(), + "error_word" => colored_error_word.clone() + ); + if translated.starts_with("clap-error-unexpected-argument") { + format!("{}: unexpected argument '{}' found", colored_error_word, colored_arg) + } else { + translated + } + }; eprintln!("{error_msg}"); eprintln!(); - // Show suggestion or generic tip + // Show suggestion if available let suggestion = err.get(ContextKind::SuggestedArg); if let Some(suggested_arg) = suggestion { let colored_suggestion = maybe_colorize(&suggested_arg.to_string(), Color::Green); - let suggestion_msg = translate!( - "clap-error-similar-argument", - "tip_word" => colored_tip_word, - "suggestion" => colored_suggestion - ); - eprintln!(" {suggestion_msg}"); + let suggestion_msg = { + let translated = translate!( + "clap-error-similar-argument", + "tip_word" => colored_tip_word.clone(), + "suggestion" => colored_suggestion.clone() + ); + if translated.starts_with("clap-error-similar-argument") { + format!(" {}: a similar argument exists: '{}'", colored_tip_word, colored_suggestion) + } else { + format!(" {}", translated) + } + }; + eprintln!("{suggestion_msg}"); + eprintln!(); } else { - let colored_tip_command = - maybe_colorize(&format!("-- {arg_str}"), Color::Green); - let tip_msg = translate!( - "clap-error-pass-as-value", - "arg" => colored_arg, - "tip_word" => colored_tip_word, - "tip_command" => colored_tip_command - ); - eprintln!(" {tip_msg}"); + // Look for other clap tips (like "-- --file-with-dash") that aren't suggestions + // These usually start with " tip:" and contain useful information + for line in _lines.iter() { + if line.trim().starts_with("tip:") && !line.contains("similar argument") { + eprintln!("{}", line); + eprintln!(); + } + } } // Show usage information for unknown arguments but use simple --help format - eprintln!(); - // Try to get usage information from localization + // Try to get usage information from localization with fallback let usage_key = format!("{}-usage", util_name); let usage_text = translate!(&usage_key); let formatted_usage = crate::format_usage(&usage_text); - let usage_label = translate!("common-usage"); + let usage_label = { + let translated = translate!("common-usage"); + if translated == "common-usage" { "Usage".to_string() } else { translated } + }; eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); // Use simple --help format for GNU test compatibility @@ -173,7 +198,11 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: std::process::exit(exit_code); } else { // Generic fallback case - let colored_error_word = maybe_colorize(&translate!("common-error"), Color::Red); + let error_word = { + let translated = translate!("common-error"); + if translated == "common-error" { "error".to_string() } else { translated } + }; + let colored_error_word = maybe_colorize(&error_word, Color::Red); eprintln!("{colored_error_word}: unexpected argument"); std::process::exit(exit_code); } diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 0177216c7..9c04f39c6 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -572,3 +572,18 @@ fn test_both_inputs_out_of_order_but_identical() { .stdout_is("\t\t2\n\t\t1\n\t\t0\n") .no_stderr(); } + +#[test] +fn test_comm_extra_arg_error() { + let scene = TestScenario::new(util_name!()); + + // Test extra argument error case from GNU test + scene + .ucmd() + .args(&["a", "b", "no-such"]) + .fails() + .code_is(1) + .stderr_contains("error: unexpected argument 'no-such' found") + .stderr_contains("Usage: comm [OPTION]... FILE1 FILE2") + .stderr_contains("For more information, try '--help'."); +} From 4514bf0aca4ced19be750bc0cbcf3f4805607c5d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 23:45:01 +0200 Subject: [PATCH 15/24] clap: improve the clap support + add tests --- src/uucore/src/lib/mods/clap_localization.rs | 38 +++++++++++++++---- src/uucore/src/lib/mods/locale.rs | 40 ++++++++++++++++++++ 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 5c47fa14e..96e0681e2 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -116,7 +116,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: ErrorKind::UnknownArgument => { // Force localization initialization - ignore any previous failures crate::locale::setup_localization_with_common(util_name).ok(); - + // UnknownArgument gets special handling for suggestions, but should still show simple help if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); @@ -124,11 +124,19 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Get the uncolored words from common strings with fallbacks let error_word = { let translated = translate!("common-error"); - if translated == "common-error" { "error".to_string() } else { translated } + if translated == "common-error" { + "error".to_string() + } else { + translated + } }; let tip_word = { let translated = translate!("common-tip"); - if translated == "common-tip" { "tip".to_string() } else { translated } + if translated == "common-tip" { + "tip".to_string() + } else { + translated + } }; let colored_arg = maybe_colorize(&arg_str, Color::Yellow); @@ -143,7 +151,10 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: "error_word" => colored_error_word.clone() ); if translated.starts_with("clap-error-unexpected-argument") { - format!("{}: unexpected argument '{}' found", colored_error_word, colored_arg) + format!( + "{}: unexpected argument '{}' found", + colored_error_word, colored_arg + ) } else { translated } @@ -151,7 +162,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: eprintln!("{error_msg}"); eprintln!(); - // Show suggestion if available + // Show suggestion if available let suggestion = err.get(ContextKind::SuggestedArg); if let Some(suggested_arg) = suggestion { let colored_suggestion = @@ -163,7 +174,10 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: "suggestion" => colored_suggestion.clone() ); if translated.starts_with("clap-error-similar-argument") { - format!(" {}: a similar argument exists: '{}'", colored_tip_word, colored_suggestion) + format!( + " {}: a similar argument exists: '{}'", + colored_tip_word, colored_suggestion + ) } else { format!(" {}", translated) } @@ -188,7 +202,11 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let formatted_usage = crate::format_usage(&usage_text); let usage_label = { let translated = translate!("common-usage"); - if translated == "common-usage" { "Usage".to_string() } else { translated } + if translated == "common-usage" { + "Usage".to_string() + } else { + translated + } }; eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); @@ -200,7 +218,11 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Generic fallback case let error_word = { let translated = translate!("common-error"); - if translated == "common-error" { "error".to_string() } else { translated } + if translated == "common-error" { + "error".to_string() + } else { + translated + } }; let colored_error_word = maybe_colorize(&error_word, Color::Red); eprintln!("{colored_error_word}: unexpected argument"); diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index aec455cfe..8e1a09a23 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -1402,6 +1402,46 @@ invalid-syntax = This is { $missing panic!("Expected LocalizationError::ParseResource with snippet"); } } + + #[test] + fn test_clap_localization_fallbacks() { + std::thread::spawn(|| { + // Test the scenario where localization isn't properly initialized + // and we need fallbacks for clap error handling + + // First, test when localizer is not initialized + let error_msg = get_message("common-error"); + assert_eq!(error_msg, "common-error"); // Should return key when not initialized + + let tip_msg = get_message("common-tip"); + assert_eq!(tip_msg, "common-tip"); // Should return key when not initialized + + // Now initialize with setup_localization_with_common + let result = setup_localization_with_common("comm"); + if result.is_err() { + // If setup fails (e.g., no embedded locales for comm), try with a known utility + let _ = setup_localization_with_common("test"); + } + + // Test that common strings are available after initialization + let error_after_init = get_message("common-error"); + // Should either be translated or return the key (but not panic) + assert!(!error_after_init.is_empty()); + + let tip_after_init = get_message("common-tip"); + assert!(!tip_after_init.is_empty()); + + // Test that clap error keys work with fallbacks + let unknown_arg_key = get_message("clap-error-unexpected-argument"); + assert!(!unknown_arg_key.is_empty()); + + // Test usage key fallback + let usage_key = get_message("common-usage"); + assert!(!usage_key.is_empty()); + }) + .join() + .unwrap(); + } } #[cfg(all(test, not(debug_assertions)))] From 4b87d5abcad9d5d74597b8ac7e47c17601af012e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 09:31:22 +0200 Subject: [PATCH 16/24] Port printenv translation: fix tests/misc/invalid-opt --- src/uu/printenv/src/printenv.rs | 6 ++++-- src/uucore/src/lib/mods/clap_localization.rs | 8 ++++++++ tests/by-util/test_printenv.rs | 12 ++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index a7809b830..ea93904a1 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -5,7 +5,6 @@ use clap::{Arg, ArgAction, Command}; use std::env; -use uucore::LocalizedCommand; use uucore::translate; use uucore::{error::UResult, format_usage}; @@ -15,7 +14,10 @@ static ARG_VARIABLES: &str = "variables"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uu_app().try_get_matches_from(args).unwrap_or_else(|e| { + use uucore::clap_localization::handle_clap_error_with_exit_code; + handle_clap_error_with_exit_code(e, uucore::util_name(), 2) + }); let variables: Vec = matches .get_many::(ARG_VARIABLES) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 96e0681e2..673f15b22 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -117,6 +117,14 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Force localization initialization - ignore any previous failures crate::locale::setup_localization_with_common(util_name).ok(); + // Choose exit code based on utility name + let exit_code = match util_name { + // These utilities expect exit code 2 for invalid options + "ls" | "dir" | "vdir" | "sort" | "tty" | "printenv" => 2, + // Most utilities expect exit code 1 + _ => 1, + }; + // UnknownArgument gets special handling for suggestions, but should still show simple help if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); diff --git a/tests/by-util/test_printenv.rs b/tests/by-util/test_printenv.rs index 4f01e526d..0bfc71f07 100644 --- a/tests/by-util/test_printenv.rs +++ b/tests/by-util/test_printenv.rs @@ -28,3 +28,15 @@ fn test_ignore_equal_var() { // tested by gnu/tests/misc/printenv.sh new_ucmd!().env("a=b", "c").arg("a=b").fails().no_stdout(); } + +#[test] +fn test_invalid_option_exit_code() { + // printenv should return exit code 2 for invalid options + // This matches GNU printenv behavior and the GNU tests expectation + new_ucmd!() + .arg("-/") + .fails() + .code_is(2) + .stderr_contains("unexpected argument") + .stderr_contains("For more information, try '--help'"); +} From 37c633ecc692adc01fc1db0dc6538d834db7667d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 09:38:53 +0200 Subject: [PATCH 17/24] Port factor for translation --- src/uucore/src/lib/mods/clap_localization.rs | 25 ++++++++------------ tests/by-util/test_factor.rs | 13 ++++++++++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 673f15b22..8ef624dd1 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -125,11 +125,15 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: _ => 1, }; - // UnknownArgument gets special handling for suggestions, but should still show simple help + // For UnknownArgument, we need to preserve clap's built-in tips (like using -- for values) + // while still allowing localization of the main error message + let rendered_str = err.render().to_string(); + let _lines: Vec<&str> = rendered_str.lines().collect(); + if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); - // Get the uncolored words from common strings with fallbacks + // Get localized error word with fallback let error_word = { let translated = translate!("common-error"); if translated == "common-error" { @@ -138,18 +142,9 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: translated } }; - let tip_word = { - let translated = translate!("common-tip"); - if translated == "common-tip" { - "tip".to_string() - } else { - translated - } - }; let colored_arg = maybe_colorize(&arg_str, Color::Yellow); let colored_error_word = maybe_colorize(&error_word, Color::Red); - let colored_tip_word = maybe_colorize(&tip_word, Color::Green); // Print main error message with fallback let error_msg = { @@ -173,6 +168,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Show suggestion if available let suggestion = err.get(ContextKind::SuggestedArg); if let Some(suggested_arg) = suggestion { + let tip_word = translate!("common-tip"); + let colored_tip_word = maybe_colorize(&tip_word, Color::Green); let colored_suggestion = maybe_colorize(&suggested_arg.to_string(), Color::Green); let suggestion_msg = { @@ -203,9 +200,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: } } - // Show usage information for unknown arguments but use simple --help format - // Try to get usage information from localization with fallback - let usage_key = format!("{}-usage", util_name); + // Show usage information for unknown arguments + let usage_key = format!("{util_name}-usage"); let usage_text = translate!(&usage_key); let formatted_usage = crate::format_usage(&usage_text); let usage_label = { @@ -218,7 +214,6 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: }; eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); - // Use simple --help format for GNU test compatibility eprintln!("For more information, try '--help'."); std::process::exit(exit_code); diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 2324da2a0..f06e7a616 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -28,6 +28,19 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); } +#[test] +fn test_invalid_negative_arg_shows_tip() { + // Test that factor shows a tip when given an invalid negative argument + // This replicates the GNU test issue where "-1" was interpreted as an invalid option + new_ucmd!() + .arg("-1") + .fails() + .code_is(1) + .stderr_contains("unexpected argument '-1' found") + .stderr_contains("tip: to pass '-1' as a value, use '-- -1'") + .stderr_contains("Usage: factor"); +} + #[test] fn test_valid_arg_exponents() { new_ucmd!().arg("-h").succeeds(); From 096cab0461cbcb366eea9f1e0442870786d2557b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 11:19:20 +0200 Subject: [PATCH 18/24] clap: improve translation support --- src/bin/coreutils.rs | 22 +- src/uucore/src/lib/mods/clap_localization.rs | 74 +- src/uucore/src/lib/mods/locale.rs | 880 +++++++++---------- 3 files changed, 442 insertions(+), 534 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index c6d2283bd..64a79a3fd 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -81,18 +81,16 @@ fn find_prefixed_util<'a>( } fn setup_localization_or_exit(util_name: &str) { - locale::setup_localization_with_common(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) - }, - ); + 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)] diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 8ef624dd1..11784cf53 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -89,9 +89,8 @@ pub fn display_usage_and_help(util_name: &str) { } pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { - // Try to ensure localization is initialized for this utility - // If it's already initialized, that's fine - we'll use the existing one - let _ = crate::locale::setup_localization_with_common(util_name); + // Ensure localization is initialized for this utility (always with common strings) + let _ = crate::locale::setup_localization(util_name); // Check if colors are enabled by examining clap's rendered output let rendered_str = err.render().to_string(); @@ -115,7 +114,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: } ErrorKind::UnknownArgument => { // Force localization initialization - ignore any previous failures - crate::locale::setup_localization_with_common(util_name).ok(); + crate::locale::setup_localization(util_name).ok(); // Choose exit code based on utility name let exit_code = match util_name { @@ -134,34 +133,17 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let arg_str = invalid_arg.to_string(); // Get localized error word with fallback - let error_word = { - let translated = translate!("common-error"); - if translated == "common-error" { - "error".to_string() - } else { - translated - } - }; + let error_word = translate!("common-error"); let colored_arg = maybe_colorize(&arg_str, Color::Yellow); let colored_error_word = maybe_colorize(&error_word, Color::Red); // Print main error message with fallback - let error_msg = { - let translated = translate!( - "clap-error-unexpected-argument", - "arg" => colored_arg.clone(), - "error_word" => colored_error_word.clone() - ); - if translated.starts_with("clap-error-unexpected-argument") { - format!( - "{}: unexpected argument '{}' found", - colored_error_word, colored_arg - ) - } else { - translated - } - }; + let error_msg = translate!( + "clap-error-unexpected-argument", + "arg" => colored_arg.clone(), + "error_word" => colored_error_word.clone() + ); eprintln!("{error_msg}"); eprintln!(); @@ -172,21 +154,11 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let colored_tip_word = maybe_colorize(&tip_word, Color::Green); let colored_suggestion = maybe_colorize(&suggested_arg.to_string(), Color::Green); - let suggestion_msg = { - let translated = translate!( - "clap-error-similar-argument", - "tip_word" => colored_tip_word.clone(), - "suggestion" => colored_suggestion.clone() - ); - if translated.starts_with("clap-error-similar-argument") { - format!( - " {}: a similar argument exists: '{}'", - colored_tip_word, colored_suggestion - ) - } else { - format!(" {}", translated) - } - }; + let suggestion_msg = translate!( + "clap-error-similar-argument", + "tip_word" => colored_tip_word.clone(), + "suggestion" => colored_suggestion.clone() + ); eprintln!("{suggestion_msg}"); eprintln!(); } else { @@ -204,14 +176,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let usage_key = format!("{util_name}-usage"); let usage_text = translate!(&usage_key); let formatted_usage = crate::format_usage(&usage_text); - let usage_label = { - let translated = translate!("common-usage"); - if translated == "common-usage" { - "Usage".to_string() - } else { - translated - } - }; + let usage_label = translate!("common-usage"); eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); eprintln!("For more information, try '--help'."); @@ -219,14 +184,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: std::process::exit(exit_code); } else { // Generic fallback case - let error_word = { - let translated = translate!("common-error"); - if translated == "common-error" { - "error".to_string() - } else { - translated - } - }; + let error_word = translate!("common-error"); let colored_error_word = maybe_colorize(&error_word, Color::Red); eprintln!("{colored_error_word}: unexpected argument"); std::process::exit(exit_code); diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 8e1a09a23..c8e735403 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -107,46 +107,6 @@ thread_local! { static LOCALIZER: OnceLock = const { OnceLock::new() }; } -/// Initialize localization with a specific locale and config -fn init_localization( - locale: &LanguageIdentifier, - locales_dir: &Path, - util_name: &str, -) -> Result<(), LocalizationError> { - let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) - .expect("Default locale should always be valid"); - - // Try to load English from embedded resources first, then fall back to filesystem. - // This ensures consistent behavior and faster loading since embedded resources - // are immediately available. The filesystem fallback allows for development - // and testing scenarios where locale files might be present in the filesystem. - let english_bundle = - create_english_bundle_from_embedded(&default_locale, util_name).or_else(|_| { - // Try filesystem as fallback (useful for development/testing) - create_bundle(&default_locale, locales_dir) - })?; - - let loc = if locale == &default_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| { - lock.set(loc) - .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) - })?; - Ok(()) -} - /// Helper function to find the uucore locales directory from a utility's locales directory fn find_uucore_locales_dir(utility_locales_dir: &Path) -> Option { // Normalize the path to get absolute path @@ -167,7 +127,7 @@ fn find_uucore_locales_dir(utility_locales_dir: &Path) -> Option { } /// Create a bundle that combines common and utility-specific strings -fn create_bundle_with_common( +fn create_bundle( locale: &LanguageIdentifier, locales_dir: &Path, util_name: &str, @@ -209,7 +169,7 @@ fn create_bundle_with_common( } /// Initialize localization with common strings in addition to utility-specific strings -fn init_localization_with_common( +fn init_localization( locale: &LanguageIdentifier, locales_dir: &Path, util_name: &str, @@ -218,18 +178,17 @@ fn init_localization_with_common( .expect("Default locale should always be valid"); // Try to create a bundle that combines common and utility-specific strings - let english_bundle = create_bundle_with_common(&default_locale, locales_dir, util_name) - .or_else(|_| { - // Fallback to embedded utility-specific strings only - create_english_bundle_from_embedded(&default_locale, util_name) - })?; + let english_bundle = create_bundle(&default_locale, locales_dir, util_name).or_else(|_| { + // Fallback to embedded utility-specific and common strings + create_english_bundle_from_embedded(&default_locale, util_name) + })?; let loc = if locale == &default_locale { // If requesting English, just use English as primary (no fallback needed) Localizer::new(english_bundle) } else { // Try to load the requested locale with common strings - if let Ok(primary_bundle) = create_bundle_with_common(locale, locales_dir, util_name) { + if let Ok(primary_bundle) = create_bundle(locale, locales_dir, util_name) { // Successfully loaded requested locale, load English as fallback Localizer::new(primary_bundle).with_fallback(english_bundle) } else { @@ -245,53 +204,29 @@ fn init_localization_with_common( Ok(()) } -/// Create a bundle for a specific locale -fn create_bundle( - locale: &LanguageIdentifier, - locales_dir: &Path, -) -> Result, LocalizationError> { - let locale_path = locales_dir.join(format!("{locale}.ftl")); - - let ftl_file = fs::read_to_string(&locale_path).map_err(|e| LocalizationError::Io { - source: e, - path: locale_path.clone(), - })?; - - 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() +/// Helper function to parse FluentResource from content string +fn parse_fluent_resource(content: &str) -> Result { + FluentResource::try_new(content.to_string()).map_err( + |(_partial_resource, errs): (FluentResource, Vec)| { + if let Some(first_err) = errs.into_iter().next() { + let snippet = first_err + .slice + .clone() + .and_then(|range| content.get(range)) + .unwrap_or("") + .to_string(); + LocalizationError::ParseResource { + error: first_err, + snippet, + } } else { - String::new() - }; - LocalizationError::ParseResource { - error: first_err, - snippet, + LocalizationError::LocalesDirNotFound("Parse error without details".to_string()) } }, - )?; - - 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 {locale}: {errs:?}", - )) - })?; - - Ok(bundle) + ) } -/// Create a bundle from embedded English locale files +/// Create a bundle from embedded English locale files with common uucore strings fn create_english_bundle_from_embedded( locale: &LanguageIdentifier, util_name: &str, @@ -304,41 +239,31 @@ fn create_english_bundle_from_embedded( } let embedded_locales = get_embedded_locales(); - let locale_key = format!("{util_name}/en-US.ftl"); - - let ftl_content = embedded_locales.get(locale_key.as_str()).ok_or_else(|| { - LocalizationError::LocalesDirNotFound(format!("No embedded locale found for {util_name}")) - })?; - - let resource = FluentResource::try_new(ftl_content.to_string()).map_err( - |(_partial_resource, errs): (FluentResource, Vec)| { - if let Some(first_err) = errs.into_iter().next() { - let snippet = first_err - .slice - .clone() - .and_then(|range| ftl_content.get(range)) - .unwrap_or("") - .to_string(); - LocalizationError::ParseResource { - error: first_err, - snippet, - } - } else { - LocalizationError::LocalesDirNotFound("Parse error without details".to_string()) - } - }, - )?; - let mut bundle = FluentBundle::new(vec![locale.clone()]); bundle.set_use_isolating(false); - bundle.add_resource(resource).map_err(|errs| { - LocalizationError::Bundle(format!( - "Failed to add embedded resource to bundle for {locale}: {errs:?}", - )) - })?; + // First, try to load common uucore strings + let uucore_key = "uucore/en-US.ftl"; + if let Some(uucore_content) = embedded_locales.get(uucore_key) { + let uucore_resource = parse_fluent_resource(uucore_content)?; + bundle.add_resource_overriding(uucore_resource); + } - Ok(bundle) + // Then, try to load utility-specific strings + let locale_key = format!("{util_name}/en-US.ftl"); + if let Some(ftl_content) = embedded_locales.get(locale_key.as_str()) { + let resource = parse_fluent_resource(ftl_content)?; + bundle.add_resource_overriding(resource); + } + + // Return the bundle if we have either common strings or utility-specific strings + if bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about")) { + Ok(bundle) + } else { + Err(LocalizationError::LocalesDirNotFound(format!( + "No embedded locale found for {util_name} and no common strings found" + ))) + } } fn get_message_internal(id: &str, args: Option) -> String { @@ -426,6 +351,7 @@ fn detect_system_locale() -> Result { } /// Sets up localization using the system locale with English fallback. +/// Always loads common strings in addition to utility-specific strings. /// /// This function initializes the localization system based on the system's locale /// preferences (via the LANG environment variable) or falls back to English @@ -466,42 +392,14 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> { LanguageIdentifier::from_str(DEFAULT_LOCALE).expect("Default locale should always be valid") }); - // Try to find the locales directory. If found, use init_localization which - // will prioritize embedded resources but can also load from filesystem. - // If no locales directory exists, directly use embedded English resources. - match get_locales_dir(p) { - Ok(locales_dir) => init_localization(&locale, &locales_dir, p), - Err(_) => { - // No locales directory found, use embedded English directly - let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) - .expect("Default locale should always be valid"); - let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?; - let localizer = Localizer::new(english_bundle); - - LOCALIZER.with(|lock| { - lock.set(localizer) - .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) - })?; - Ok(()) - } - } -} - -/// Enhanced version of setup_localization that also loads common/clap error strings -/// This function loads both utility-specific strings and common strings for clap error handling -pub fn setup_localization_with_common(p: &str) -> Result<(), LocalizationError> { - let locale = detect_system_locale().unwrap_or_else(|_| { - LanguageIdentifier::from_str(DEFAULT_LOCALE).expect("Default locale should always be valid") - }); - // Load common strings along with utility-specific strings match get_locales_dir(p) { Ok(locales_dir) => { // Load both utility-specific and common strings - init_localization_with_common(&locale, &locales_dir, p) + init_localization(&locale, &locales_dir, p) } Err(_) => { - // No locales directory found, use embedded English directly + // No locales directory found, use embedded English with common strings directly let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) .expect("Default locale should always be valid"); let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?; @@ -664,6 +562,62 @@ mod tests { use std::path::PathBuf; use tempfile::TempDir; + /// Test-specific helper function to create a bundle from test directory only + #[cfg(test)] + fn create_test_bundle( + locale: &LanguageIdentifier, + test_locales_dir: &Path, + ) -> Result, LocalizationError> { + let mut bundle = FluentBundle::new(vec![locale.clone()]); + bundle.set_use_isolating(false); + + // Only load from the test directory - no common strings or utility-specific paths + let locale_path = test_locales_dir.join(format!("{locale}.ftl")); + if let Ok(ftl_content) = fs::read_to_string(&locale_path) { + let resource = parse_fluent_resource(&ftl_content)?; + bundle.add_resource_overriding(resource); + return Ok(bundle); + } + + Err(LocalizationError::LocalesDirNotFound(format!( + "No localization strings found for {locale} in {}", + test_locales_dir.display() + ))) + } + + /// Test-specific initialization function for test directories + #[cfg(test)] + fn init_test_localization( + locale: &LanguageIdentifier, + test_locales_dir: &Path, + ) -> Result<(), LocalizationError> { + let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) + .expect("Default locale should always be valid"); + + // Create English bundle from test directory + let english_bundle = create_test_bundle(&default_locale, test_locales_dir)?; + + let loc = if locale == &default_locale { + // If requesting English, just use English as primary + Localizer::new(english_bundle) + } else { + // Try to load the requested locale from test directory + if let Ok(primary_bundle) = create_test_bundle(locale, test_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| { + lock.set(loc) + .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) + })?; + Ok(()) + } + /// 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"); @@ -729,31 +683,12 @@ invalid-syntax = This is { $missing 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::Bundle("some 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()); + let result = create_test_bundle(&locale, temp_dir.path()); assert!(result.is_ok()); let bundle = result.unwrap(); @@ -765,13 +700,13 @@ invalid-syntax = This is { $missing let temp_dir = TempDir::new().unwrap(); let locale = LanguageIdentifier::from_str("de-DE").unwrap(); - let result = create_bundle(&locale, temp_dir.path()); + let result = create_test_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")); + if let Err(LocalizationError::LocalesDirNotFound(_)) = result { + // Expected - no localization strings found } else { - panic!("Expected IO error"); + panic!("Expected LocalesDirNotFound error"); } } @@ -780,24 +715,29 @@ invalid-syntax = This is { $missing 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()); + let result = create_test_bundle(&locale, temp_dir.path()); - if let Err(LocalizationError::ParseResource { - error: _parser_err, - snippet: _, - }) = result - { - // Expected ParseResource variant - } else { - panic!("Expected ParseResource error"); + // The result should be an error due to invalid syntax + match result { + Err(LocalizationError::ParseResource { + error: _parser_err, + snippet: _, + }) => { + // Expected ParseResource variant - test passes + } + Ok(_) => { + panic!("Expected ParseResource error, but bundle was created successfully"); + } + Err(other) => { + panic!("Expected ParseResource error, but got: {other:?}"); + } } } #[test] fn test_localizer_format_primary_bundle() { let temp_dir = create_test_locales_dir(); - let en_bundle = create_bundle( + let en_bundle = create_test_bundle( &LanguageIdentifier::from_str("en-US").unwrap(), temp_dir.path(), ) @@ -812,7 +752,7 @@ invalid-syntax = This is { $missing fn test_localizer_format_with_args() { use fluent::FluentArgs; let temp_dir = create_test_locales_dir(); - let en_bundle = create_bundle( + let en_bundle = create_test_bundle( &LanguageIdentifier::from_str("en-US").unwrap(), temp_dir.path(), ) @@ -829,12 +769,12 @@ invalid-syntax = This is { $missing #[test] fn test_localizer_fallback_to_english() { let temp_dir = create_test_locales_dir(); - let fr_bundle = create_bundle( + let fr_bundle = create_test_bundle( &LanguageIdentifier::from_str("fr-FR").unwrap(), temp_dir.path(), ) .unwrap(); - let en_bundle = create_bundle( + let en_bundle = create_test_bundle( &LanguageIdentifier::from_str("en-US").unwrap(), temp_dir.path(), ) @@ -854,7 +794,7 @@ invalid-syntax = This is { $missing #[test] fn test_localizer_format_message_not_found() { let temp_dir = create_test_locales_dir(); - let en_bundle = create_bundle( + let en_bundle = create_test_bundle( &LanguageIdentifier::from_str("en-US").unwrap(), temp_dir.path(), ) @@ -872,10 +812,7 @@ invalid-syntax = This is { $missing let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("en-US").unwrap(); - let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util"); - if let Err(e) = &result { - eprintln!("Init localization failed: {e}"); - } + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Test that we can get messages @@ -892,7 +829,7 @@ invalid-syntax = This is { $missing let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("fr-FR").unwrap(); - let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util"); + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Test French message @@ -913,7 +850,7 @@ invalid-syntax = This is { $missing 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(), "nonexistent_test_util"); + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Should use English as primary since German failed to load @@ -931,11 +868,11 @@ invalid-syntax = This is { $missing let locale = LanguageIdentifier::from_str("en-US").unwrap(); // Initialize once - let result1 = init_localization(&locale, temp_dir.path(), "test"); + let result1 = init_test_localization(&locale, temp_dir.path()); assert!(result1.is_ok()); // Try to initialize again - should fail - let result2 = init_localization(&locale, temp_dir.path(), "test"); + let result2 = init_test_localization(&locale, temp_dir.path()); assert!(result2.is_err()); match result2 { @@ -955,7 +892,7 @@ invalid-syntax = This is { $missing let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("fr-FR").unwrap(); - init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap(); + init_test_localization(&locale, temp_dir.path()).unwrap(); let message = get_message("greeting"); assert_eq!(message, "Bonjour, le monde!"); @@ -964,16 +901,6 @@ invalid-syntax = This is { $missing .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() { use fluent::FluentArgs; @@ -981,7 +908,7 @@ invalid-syntax = This is { $missing let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("en-US").unwrap(); - init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap(); + init_test_localization(&locale, temp_dir.path()).unwrap(); let mut args = FluentArgs::new(); args.set("name".to_string(), "Bob".to_string()); @@ -1000,7 +927,7 @@ invalid-syntax = This is { $missing let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("en-US").unwrap(); - init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap(); + init_test_localization(&locale, temp_dir.path()).unwrap(); // Test singular let mut args1 = FluentArgs::new(); @@ -1018,6 +945,269 @@ invalid-syntax = This is { $missing .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_test_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_test_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() { + use fluent::FluentArgs; + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ja-JP").unwrap(); + + let result = init_test_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 = FluentArgs::new(); + args.set("name".to_string(), "田中".to_string()); + let welcome = get_message_with_args("welcome", args); + assert_eq!(welcome, "ようこそ、田中さん!"); + + // Test Japanese count (no pluralization) + let mut count_args = FluentArgs::new(); + count_args.set("count".to_string(), "5".to_string()); + let count_message = get_message_with_args("count-items", count_args); + assert_eq!(count_message, "5個のアイテムがあります"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_arabic_localization() { + use fluent::FluentArgs; + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); + + let result = init_test_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 = FluentArgs::new(); + args.set("name", "أحمد".to_string()); + let welcome = get_message_with_args("welcome", args); + assert_eq!(welcome, "أهلاً وسهلاً، أحمد!"); + + // Test Arabic pluralization (zero case) + let mut args_zero = FluentArgs::new(); + args_zero.set("count", 0); + let message_zero = get_message_with_args("count-items", args_zero); + assert_eq!(message_zero, "لديك لا عناصر"); + + // Test Arabic pluralization (one case) + let mut args_one = FluentArgs::new(); + args_one.set("count", 1); + let message_one = get_message_with_args("count-items", args_one); + assert_eq!(message_one, "لديك عنصر واحد"); + + // Test Arabic pluralization (two case) + let mut args_two = FluentArgs::new(); + args_two.set("count", 2); + let message_two = get_message_with_args("count-items", args_two); + assert_eq!(message_two, "لديك عنصران"); + + // Test Arabic pluralization (few case - 3-10) + let mut args_few = FluentArgs::new(); + args_few.set("count", 5); + let message_few = get_message_with_args("count-items", args_few); + assert_eq!(message_few, "لديك 5 عناصر"); + + // Test Arabic pluralization (other case - 11+) + let mut args_many = FluentArgs::new(); + args_many.set("count", 15); + let message_many = get_message_with_args("count-items", args_many); + assert_eq!(message_many, "لديك 15 عنصر"); + }) + .join() + .unwrap(); + } + + #[test] + fn test_arabic_localization_with_macro() { + std::thread::spawn(|| { + use self::translate; + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); + + let result = init_test_localization(&locale, temp_dir.path()); + assert!(result.is_ok()); + + // Test Arabic greeting (RTL text) + let message = translate!("greeting"); + assert_eq!(message, "أهلاً بالعالم!"); + + // Test Arabic with arguments + let welcome = translate!("welcome", "name" => "أحمد"); + assert_eq!(welcome, "أهلاً وسهلاً، أحمد!"); + + // Test Arabic pluralization (zero case) + let message_zero = translate!("count-items", "count" => 0); + assert_eq!(message_zero, "لديك لا عناصر"); + + // Test Arabic pluralization (one case) + let message_one = translate!("count-items", "count" => 1); + assert_eq!(message_one, "لديك عنصر واحد"); + + // Test Arabic pluralization (two case) + let message_two = translate!("count-items", "count" => 2); + assert_eq!(message_two, "لديك عنصران"); + + // Test Arabic pluralization (few case - 3-10) + let message_few = translate!("count-items", "count" => 5); + assert_eq!(message_few, "لديك 5 عناصر"); + + // Test Arabic pluralization (other case - 11+) + let message_many = translate!("count-items", "count" => 15); + assert_eq!(message_many, "لديك 15 عنصر"); + }) + .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_test_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_disabled() { + use fluent::FluentArgs; + std::thread::spawn(|| { + let temp_dir = create_test_locales_dir(); + let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); + + init_test_localization(&locale, temp_dir.path()).unwrap(); + + // Test that Latin script names are NOT isolated in RTL context + // since we disabled Unicode directional isolation + let mut args = FluentArgs::new(); + args.set("name".to_string(), "John Smith".to_string()); + let message = get_message_with_args("welcome", args); + + // 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(); + } + + #[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_test_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 `{snippet}` but did not include the invalid text" + ); + } else { + panic!("Expected LocalizationError::ParseResource with snippet"); + } + } + + #[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::Bundle("some error".to_string()); + assert_eq!(error.code(), 1); + } + + #[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_detect_system_locale_from_lang_env() { // Test locale parsing logic directly instead of relying on environment variables @@ -1066,20 +1256,19 @@ invalid-syntax = This is { $missing #[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"); + env::set_var("LANG", "en-US.UTF-8"); // Use English since we have embedded resources for "test" } - let result = setup_localization(temp_dir.path().to_str().unwrap()); + let result = setup_localization("test"); assert!(result.is_ok()); - // Test that French is loaded - let message = get_message("greeting"); - assert_eq!(message, "Bonjour, le monde!"); + // Test that we can get messages (should use embedded English for "test" utility) + let message = get_message("test-about"); + // Since we're using embedded resources, we should get the expected message + assert!(!message.is_empty()); // Restore original LANG value if let Some(val) = original_lang { @@ -1099,20 +1288,18 @@ invalid-syntax = This is { $missing #[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 + env::set_var("LANG", "de-DE.UTF-8"); // German file doesn't exist, should fallback + } - let result = setup_localization(temp_dir.path().to_str().unwrap()); + let result = setup_localization("test"); assert!(result.is_ok()); - // Should fall back to English - let message = get_message("greeting"); - assert_eq!(message, "Hello, world!"); + // Should fall back to English embedded resources + let message = get_message("test-about"); + assert!(!message.is_empty()); // Should get something, not just the key // Restore original LANG value if let Some(val) = original_lang { @@ -1153,218 +1340,6 @@ invalid-syntax = This is { $missing .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, "nonexistent_test_util").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, "nonexistent_test_util").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() { - use fluent::FluentArgs; - 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(), "nonexistent_test_util"); - assert!(result.is_ok()); - - // Test Japanese greeting - let message = get_message("greeting"); - assert_eq!(message, "こんにちは、世界!"); - - // Test Japanese with arguments - let mut args = FluentArgs::new(); - args.set("name".to_string(), "田中".to_string()); - let welcome = get_message_with_args("welcome", args); - assert_eq!(welcome, "ようこそ、田中さん!"); - - // Test Japanese count (no pluralization) - let mut count_args = FluentArgs::new(); - count_args.set("count".to_string(), "5".to_string()); - let count_message = get_message_with_args("count-items", count_args); - assert_eq!(count_message, "5個のアイテムがあります"); - }) - .join() - .unwrap(); - } - - #[test] - fn test_arabic_localization() { - use fluent::FluentArgs; - 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(), "nonexistent_test_util"); - assert!(result.is_ok()); - - // Test Arabic greeting (RTL text) - let message = get_message("greeting"); - assert_eq!(message, "أهلاً بالعالم!"); - - // Test Arabic with arguments - let mut args = FluentArgs::new(); - args.set("name", "أحمد".to_string()); - let welcome = get_message_with_args("welcome", args); - - assert_eq!(welcome, "أهلاً وسهلاً، أحمد!"); - - // Test Arabic pluralization (zero case) - let mut args_zero = FluentArgs::new(); - args_zero.set("count", 0); - let message_zero = get_message_with_args("count-items", args_zero); - assert_eq!(message_zero, "لديك لا عناصر"); - - // Test Arabic pluralization (one case) - let mut args_one = FluentArgs::new(); - args_one.set("count", 1); - let message_one = get_message_with_args("count-items", args_one); - assert_eq!(message_one, "لديك عنصر واحد"); - - // Test Arabic pluralization (two case) - let mut args_two = FluentArgs::new(); - args_two.set("count", 2); - let message_two = get_message_with_args("count-items", args_two); - assert_eq!(message_two, "لديك عنصران"); - - // Test Arabic pluralization (few case - 3-10) - let mut args_few = FluentArgs::new(); - args_few.set("count", 5); - let message_few = get_message_with_args("count-items", args_few); - assert_eq!(message_few, "لديك 5 عناصر"); - - // Test Arabic pluralization (other case - 11+) - let mut args_many = FluentArgs::new(); - args_many.set("count", 15); - let message_many = get_message_with_args("count-items", args_many); - assert_eq!(message_many, "لديك 15 عنصر"); - }) - .join() - .unwrap(); - } - - #[test] - fn test_arabic_localization_with_macro() { - std::thread::spawn(|| { - use self::translate; - let temp_dir = create_test_locales_dir(); - let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); - - let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util"); - assert!(result.is_ok()); - - // Test Arabic greeting (RTL text) - let message = translate!("greeting"); - assert_eq!(message, "أهلاً بالعالم!"); - - // Test Arabic with arguments - let welcome = translate!("welcome", "name" => "أحمد"); - assert_eq!(welcome, "أهلاً وسهلاً، أحمد!"); - - // Test Arabic pluralization (zero case) - let message_zero = translate!("count-items", "count" => 0); - assert_eq!(message_zero, "لديك لا عناصر"); - - // Test Arabic pluralization (one case) - let message_one = translate!("count-items", "count" => 1); - assert_eq!(message_one, "لديك عنصر واحد"); - - // Test Arabic pluralization (two case) - let message_two = translate!("count-items", "count" => 2); - assert_eq!(message_two, "لديك عنصران"); - - // Test Arabic pluralization (few case - 3-10) - let message_few = translate!("count-items", "count" => 5); - assert_eq!(message_few, "لديك 5 عناصر"); - - // Test Arabic pluralization (other case - 11+) - let message_many = translate!("count-items", "count" => 15); - assert_eq!(message_many, "لديك 15 عنصر"); - }) - .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(), "nonexistent_test_util"); - 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_disabled() { - use fluent::FluentArgs; - std::thread::spawn(|| { - let temp_dir = create_test_locales_dir(); - let locale = LanguageIdentifier::from_str("ar-SA").unwrap(); - - init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap(); - - // Test that Latin script names are NOT isolated in RTL context - // since we disabled Unicode directional isolation - let mut args = FluentArgs::new(); - args.set("name".to_string(), "John Smith".to_string()); - let message = get_message_with_args("welcome", args); - - // 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(); - } - #[test] fn test_error_display() { let io_error = LocalizationError::Io { @@ -1380,29 +1355,6 @@ invalid-syntax = This is { $missing 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 `{snippet}` but did not include the invalid text" - ); - } else { - panic!("Expected LocalizationError::ParseResource with snippet"); - } - } - #[test] fn test_clap_localization_fallbacks() { std::thread::spawn(|| { @@ -1416,11 +1368,11 @@ invalid-syntax = This is { $missing let tip_msg = get_message("common-tip"); assert_eq!(tip_msg, "common-tip"); // Should return key when not initialized - // Now initialize with setup_localization_with_common - let result = setup_localization_with_common("comm"); + // Now initialize with setup_localization + let result = setup_localization("comm"); if result.is_err() { // If setup fails (e.g., no embedded locales for comm), try with a known utility - let _ = setup_localization_with_common("test"); + let _ = setup_localization("test"); } // Test that common strings are available after initialization From e8dccfc9833b0b43d7344d0c98b2a0dd7435e669 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 13:37:09 +0200 Subject: [PATCH 19/24] allow translation of "For more information, try '--help'." --- src/uucore/locales/en-US.ftl | 1 + src/uucore/locales/fr-FR.ftl | 1 + src/uucore/src/lib/mods/clap_localization.rs | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl index 358eda0d5..676f9ad3b 100644 --- a/src/uucore/locales/en-US.ftl +++ b/src/uucore/locales/en-US.ftl @@ -13,6 +13,7 @@ clap-error-unexpected-argument = { $error_word }: unexpected argument '{ $arg }' clap-error-similar-argument = { $tip_word }: a similar argument exists: '{ $suggestion }' clap-error-pass-as-value = { $tip_word }: to pass '{ $arg }' as a value, use '{ $tip_command }' clap-error-help-suggestion = For more information, try '{ $command } --help'. +common-help-suggestion = For more information, try '--help'. # Common help text patterns help-flag-help = Print help information diff --git a/src/uucore/locales/fr-FR.ftl b/src/uucore/locales/fr-FR.ftl index cbeab358d..8bd69e0f6 100644 --- a/src/uucore/locales/fr-FR.ftl +++ b/src/uucore/locales/fr-FR.ftl @@ -13,6 +13,7 @@ clap-error-unexpected-argument = { $error_word } : argument inattendu '{ $arg }' clap-error-similar-argument = { $tip_word } : un argument similaire existe : '{ $suggestion }' clap-error-pass-as-value = { $tip_word } : pour passer '{ $arg }' comme valeur, utilisez '{ $tip_command }' clap-error-help-suggestion = Pour plus d'informations, essayez '{ $command } --help'. +common-help-suggestion = Pour plus d'informations, essayez '--help'. # Modèles de texte d'aide communs help-flag-help = Afficher les informations d'aide diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 11784cf53..152c17ddb 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -179,7 +179,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let usage_label = translate!("common-usage"); eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); - eprintln!("For more information, try '--help'."); + eprintln!("{}", translate!("common-help-suggestion")); std::process::exit(exit_code); } else { @@ -199,7 +199,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: eprintln!("{}", main_error_line); eprintln!(); // Use the execution phrase for the help suggestion to match test expectations - eprintln!("For more information, try '--help'."); + eprintln!("{}", translate!("common-help-suggestion")); } else { // Fallback to original rendering if we can't parse eprint!("{}", err.render()); @@ -233,7 +233,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // For other errors, just show help suggestion eprintln!(); - eprintln!("For more information, try '--help'."); + eprintln!("{}", translate!("common-help-suggestion")); std::process::exit(exit_code); } From 023c570833fc4431765366e80b9dd7c0dc2402cd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 11 Aug 2025 08:35:27 +0200 Subject: [PATCH 20/24] clap: translate Usage too --- src/uu/arch/src/arch.rs | 1 + src/uu/base32/src/base_common.rs | 1 + src/uu/basename/src/basename.rs | 1 + src/uu/cat/src/cat.rs | 1 + src/uu/chcon/src/chcon.rs | 1 + src/uu/chgrp/src/chgrp.rs | 1 + src/uu/chmod/src/chmod.rs | 1 + src/uu/chown/src/chown.rs | 1 + src/uu/chroot/src/chroot.rs | 1 + src/uu/cksum/src/cksum.rs | 1 + src/uu/comm/src/comm.rs | 1 + src/uu/cp/src/cp.rs | 1 + src/uu/csplit/src/csplit.rs | 1 + src/uu/cut/src/cut.rs | 1 + src/uu/date/src/date.rs | 1 + src/uu/dd/src/dd.rs | 1 + src/uu/df/src/df.rs | 1 + src/uu/dircolors/src/dircolors.rs | 1 + src/uu/dirname/src/dirname.rs | 1 + src/uu/du/src/du.rs | 1 + src/uu/echo/src/echo.rs | 1 + src/uu/env/src/env.rs | 1 + src/uu/expand/src/expand.rs | 1 + src/uu/expr/src/expr.rs | 1 + src/uu/factor/src/factor.rs | 1 + src/uu/false/src/false.rs | 1 + src/uu/fmt/src/fmt.rs | 1 + src/uu/fold/src/fold.rs | 1 + src/uu/groups/src/groups.rs | 1 + src/uu/hashsum/src/hashsum.rs | 1 + src/uu/head/src/head.rs | 1 + src/uu/hostid/src/hostid.rs | 1 + src/uu/hostname/src/hostname.rs | 1 + src/uu/id/src/id.rs | 1 + src/uu/install/src/install.rs | 1 + src/uu/join/src/join.rs | 1 + src/uu/kill/src/kill.rs | 1 + src/uu/link/src/link.rs | 1 + src/uu/ln/src/ln.rs | 1 + src/uu/logname/src/logname.rs | 1 + src/uu/ls/src/ls.rs | 14 +- src/uu/mkdir/src/mkdir.rs | 1 + src/uu/mkfifo/src/mkfifo.rs | 1 + src/uu/mknod/src/mknod.rs | 1 + src/uu/mktemp/src/mktemp.rs | 1 + src/uu/more/src/more.rs | 1 + src/uu/mv/src/mv.rs | 1 + src/uu/nice/src/nice.rs | 1 + src/uu/nl/src/nl.rs | 1 + src/uu/nohup/src/nohup.rs | 1 + src/uu/nproc/src/nproc.rs | 1 + src/uu/numfmt/src/numfmt.rs | 1 + src/uu/od/src/od.rs | 1 + src/uu/paste/src/paste.rs | 1 + src/uu/pathchk/src/pathchk.rs | 1 + src/uu/pinky/src/pinky.rs | 1 + src/uu/pr/src/pr.rs | 1 + src/uu/printenv/src/printenv.rs | 1 + src/uu/printf/src/printf.rs | 1 + src/uu/ptx/src/ptx.rs | 1 + src/uu/pwd/src/pwd.rs | 1 + src/uu/readlink/src/readlink.rs | 1 + src/uu/realpath/src/realpath.rs | 1 + src/uu/rm/src/rm.rs | 1 + src/uu/rmdir/src/rmdir.rs | 1 + src/uu/runcon/src/runcon.rs | 1 + src/uu/seq/src/seq.rs | 1 + src/uu/shred/src/shred.rs | 1 + src/uu/shuf/src/shuf.rs | 1 + src/uu/sleep/src/sleep.rs | 1 + src/uu/sort/src/sort.rs | 13 +- src/uu/split/src/split.rs | 1 + src/uu/stat/src/stat.rs | 1 + src/uu/stdbuf/src/stdbuf.rs | 1 + src/uu/stty/src/stty.rs | 1 + src/uu/sum/src/sum.rs | 1 + src/uu/sync/src/sync.rs | 1 + src/uu/tac/src/tac.rs | 1 + src/uu/tail/src/args.rs | 1 + src/uu/tee/src/tee.rs | 1 + src/uu/test/src/test.rs | 4 +- src/uu/timeout/src/timeout.rs | 1 + src/uu/touch/src/touch.rs | 1 + src/uu/tr/src/tr.rs | 1 + src/uu/true/src/true.rs | 1 + src/uu/truncate/src/truncate.rs | 1 + src/uu/tsort/src/tsort.rs | 1 + src/uu/tty/src/tty.rs | 1 + src/uu/uname/src/uname.rs | 1 + src/uu/unexpand/src/unexpand.rs | 1 + src/uu/uniq/src/uniq.rs | 1 + src/uu/unlink/src/unlink.rs | 1 + src/uu/uptime/src/uptime.rs | 1 + src/uu/users/src/users.rs | 1 + src/uu/wc/src/wc.rs | 1 + src/uu/who/src/who.rs | 1 + src/uu/whoami/src/whoami.rs | 1 + src/uu/yes/src/yes.rs | 1 + src/uucore/src/lib/lib.rs | 35 +++++ src/uucore/src/lib/mods/clap_localization.rs | 48 ++++--- tests/by-util/test_sort.rs | 129 +++++++++++++++++++ 101 files changed, 298 insertions(+), 40 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 3b7b4dc10..8fe80b602 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -24,6 +24,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("arch-about")) .after_help(translate!("arch-after-help")) .infer_long_args(true) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 619eb9b4a..db1e97016 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -107,6 +107,7 @@ pub fn parse_base_cmd_args( pub fn base_app(about: &'static str, usage: &str) -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(about) .override_usage(format_usage(usage)) .infer_long_args(true) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 557d981d7..61ac69289 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -82,6 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("basename-about")) .override_usage(format_usage(&translate!("basename-usage"))) .infer_long_args(true) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index f69364310..f03c7c3b9 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -287,6 +287,7 @@ pub fn uu_app() -> Command { .version(uucore::crate_version!()) .override_usage(format_usage(&translate!("cat-usage"))) .about(translate!("cat-about")) + .help_template(uucore::localized_help_template(uucore::util_name())) .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 263d73475..25c2d099e 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -157,6 +157,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("chcon-about")) .override_usage(format_usage(&translate!("chcon-usage"))) .infer_long_args(true) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 9df217fd0..aca69d103 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -99,6 +99,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("chgrp-about")) .override_usage(format_usage(&translate!("chgrp-usage"))) .infer_long_args(true) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 01fa0422c..92ea6024e 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -178,6 +178,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("chmod-about")) .override_usage(format_usage(&translate!("chmod-usage"))) .args_override_self(true) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index bd5872d51..ceff36b59 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -77,6 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("chown-about")) .override_usage(format_usage(&translate!("chown-usage"))) .infer_long_args(true) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 3d8ea589b..52db47e36 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -236,6 +236,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("chroot-about")) .override_usage(format_usage(&translate!("chroot-usage"))) .infer_long_args(true) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index dd36cfefc..01e3da55f 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -344,6 +344,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("cksum-about")) .override_usage(format_usage(&translate!("cksum-usage"))) .infer_long_args(true) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 8ba393b5b..b77c549d7 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -316,6 +316,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("comm-about")) .override_usage(format_usage(&translate!("comm-usage"))) .infer_long_args(true) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 01c08324e..41cded5f0 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -521,6 +521,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) .about(translate!("cp-about")) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("cp-usage"))) .after_help(format!( "{}\n\n{}", diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 4a13ad40f..fc18b97da 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -630,6 +630,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("csplit-about")) .override_usage(format_usage(&translate!("csplit-usage"))) .args_override_self(true) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 1bc8cb0d6..57f0e61d1 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -595,6 +595,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("cut-usage"))) .about(translate!("cut-about")) .after_help(translate!("cut-after-help")) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 6a33aa74f..1685844d9 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -265,6 +265,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("date-about")) .override_usage(format_usage(&translate!("date-usage"))) .infer_long_args(true) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 0de57fe70..0ddeefeb4 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -1443,6 +1443,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("dd-about")) .override_usage(format_usage(&translate!("dd-usage"))) .after_help(translate!("dd-after-help")) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 7db47355d..3a7a10f92 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -463,6 +463,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("df-about")) .override_usage(format_usage(&translate!("df-usage"))) .after_help(translate!("df-after-help")) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 0a58ce37a..87a459f29 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -239,6 +239,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("dircolors-about")) .after_help(translate!("dircolors-after-help")) .override_usage(format_usage(&translate!("dircolors-usage"))) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 5f301edd7..aac8e57f3 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -64,6 +64,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .about(translate!("dirname-about")) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("dirname-usage"))) .args_override_self(true) .infer_long_args(true) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 12d94edf1..64a7662bb 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -805,6 +805,7 @@ fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("du-about")) .after_help(translate!("du-after-help")) .override_usage(format_usage(&translate!("du-usage"))) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 6c61ec7d8..4bbff02e9 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -193,6 +193,7 @@ pub fn uu_app() -> Command { .about(translate!("echo-about")) .after_help(translate!("echo-after-help")) .override_usage(format_usage(&translate!("echo-usage"))) + .help_template(uucore::localized_help_template(uucore::util_name())) .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 9e42b1694..52e3bfa22 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -225,6 +225,7 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { pub fn uu_app() -> Command { Command::new(crate_name!()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("env-about")) .override_usage(format_usage(&translate!("env-usage"))) .after_help(translate!("env-after-help")) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 8d768522d..cd528316e 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -251,6 +251,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("expand-about")) .after_help(LONG_HELP) .override_usage(format_usage(&translate!("expand-usage"))) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index ba980357c..75ce8620d 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -73,6 +73,7 @@ impl UError for ExprError { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("expr-about")) .override_usage(format_usage(&translate!("expr-usage"))) .after_help(translate!("expr-after-help")) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 1e7a176b6..8516b1eb6 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -122,6 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("factor-about")) .override_usage(format_usage(&translate!("factor-usage"))) .infer_long_args(true) diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index a0a3f944f..108349031 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -47,6 +47,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("false-about")) // We provide our own help and version options, to ensure maximum compatibility with GNU. .disable_help_flag(true) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index ed7ab7ff7..46f9d547f 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -353,6 +353,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("fmt-about")) .override_usage(format_usage(&translate!("fmt-usage"))) .infer_long_args(true) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 34bc6b656..22fc6df23 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -62,6 +62,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("fold-usage"))) .about(translate!("fold-about")) .infer_long_args(true) diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 156555798..17624ffde 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -82,6 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("groups-about")) .override_usage(format_usage(&translate!("groups-usage"))) .infer_long_args(true) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 199a09bc7..0fc23e35f 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -312,6 +312,7 @@ mod options { pub fn uu_app_common() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("hashsum-about")) .override_usage(format_usage(&translate!("hashsum-usage"))) .infer_long_args(true) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index c3f1c2c0c..818062ba9 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -72,6 +72,7 @@ type HeadResult = Result; pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("head-about")) .override_usage(format_usage(&translate!("head-usage"))) .infer_long_args(true) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index efbe75475..8a4f5efd8 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -22,6 +22,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("hostid-about")) .override_usage(format_usage(&translate!("hostid-usage"))) .infer_long_args(true) diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 734cfdbed..acefbf732 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -77,6 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("hostname-about")) .override_usage(format_usage(&translate!("hostname-usage"))) .infer_long_args(true) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 86fd86a17..0852d9927 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -350,6 +350,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("id-about")) .override_usage(format_usage(&translate!("id-usage"))) .infer_long_args(true) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index bb3663779..66e80b90d 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -184,6 +184,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("install-about")) .override_usage(format_usage(&translate!("install-usage"))) .infer_long_args(true) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index a55c9afd5..dd1323506 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -855,6 +855,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("join-about")) .override_usage(format_usage(&translate!("join-usage"))) .infer_long_args(true) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index aef9b45e3..e749e982b 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -100,6 +100,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("kill-about")) .override_usage(format_usage(&translate!("kill-usage"))) .infer_long_args(true) diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 8f18bf86b..773d52d20 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -37,6 +37,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("link-about")) .override_usage(format_usage(&translate!("link-usage"))) .infer_long_args(true) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index d9a7afbd7..8b21bd093 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -144,6 +144,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("ln-about")) .override_usage(format_usage(&translate!("ln-usage"))) .infer_long_args(true) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index bb34f4b74..1cf1872a8 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -37,6 +37,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(uucore::util_name()) .about(translate!("logname-about")) .infer_long_args(true) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 5e9477c29..b25924c3f 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -57,7 +57,7 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{dev_t, major, minor}; use uucore::{ display::Quotable, - error::{UError, UResult, USimpleError, set_exit_code}, + error::{UError, UResult, set_exit_code}, format::human::{SizeFormat, human_readable}, format_usage, fs::FileInformation, @@ -1104,18 +1104,9 @@ impl Config { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = match uu_app().try_get_matches_from(args) { - // clap successfully parsed the arguments: Ok(matches) => matches, - // --help, --version, etc.: - Err(e) if e.exit_code() == 0 => { - return Err(e.into()); - } - // Errors in argument *values* cause exit code 1: - Err(e) if e.kind() == clap::error::ErrorKind::InvalidValue => { - return Err(USimpleError::new(1, e.to_string())); - } - // All other argument parsing errors cause exit code 2: Err(e) => { + // Use localization handler for all clap errors uucore::clap_localization::handle_clap_error_with_exit_code(e, "ls", 2); } }; @@ -1134,6 +1125,7 @@ pub fn uu_app() -> Command { .version(uucore::crate_version!()) .override_usage(format_usage(&translate!("ls-usage"))) .about(translate!("ls-about")) + .help_template(uucore::localized_help_template(uucore::util_name())) .infer_long_args(true) .disable_help_flag(true) .args_override_self(true) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index c82309087..fb82d963c 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -112,6 +112,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("mkdir-about")) .override_usage(format_usage(&translate!("mkdir-usage"))) .infer_long_args(true) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index ae3466da6..5cae85cc8 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -84,6 +84,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("mkfifo-usage"))) .about(translate!("mkfifo-about")) .infer_long_args(true) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index b67e48e0c..d922c3b82 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -171,6 +171,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("mknod-usage"))) .after_help(translate!("mknod-after-help")) .about(translate!("mknod-about")) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index b1dbacf71..23d22d361 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -397,6 +397,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("mktemp-about")) .override_usage(format_usage(&translate!("mktemp-usage"))) .infer_long_args(true) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 473a7f859..8aa6b7729 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -213,6 +213,7 @@ pub fn uu_app() -> Command { .about(translate!("more-about")) .override_usage(format_usage(&translate!("more-usage"))) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .infer_long_args(true) .arg( Arg::new(options::SILENT) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index b3ae7114e..b89290149 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -226,6 +226,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) .about(translate!("mv-about")) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("mv-usage"))) .after_help(format!( "{}\n\n{}", diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 776332797..5e9b555e8 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -185,6 +185,7 @@ pub fn uu_app() -> Command { .trailing_var_arg(true) .infer_long_args(true) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .arg( Arg::new(options::ADJUSTMENT) .short('n') diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index f759b5926..890de1e86 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -230,6 +230,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .about(translate!("nl-about")) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("nl-usage"))) .after_help(translate!("nl-after-help")) .infer_long_args(true) diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 92dc510b5..f20ec29af 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -86,6 +86,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("nohup-about")) .after_help(translate!("nohup-after-help")) .override_usage(format_usage(&translate!("nohup-usage"))) diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 7137ebda7..dd53e81b9 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -93,6 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("nproc-about")) .override_usage(format_usage(&translate!("nproc-usage"))) .infer_long_args(true) diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 008f51558..e5cec7692 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -280,6 +280,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("numfmt-about")) .after_help(translate!("numfmt-after-help")) .override_usage(format_usage(&translate!("numfmt-usage"))) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 936e32c88..e63a29a00 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -252,6 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("od-about")) .override_usage(format_usage(&translate!("od-usage"))) .after_help(translate!("od-after-help")) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 807e4debf..82a03f93b 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -42,6 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("paste-about")) .override_usage(format_usage(&translate!("paste-usage"))) .infer_long_args(true) diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 9016a4878..c46ed39ae 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -82,6 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("pathchk-about")) .override_usage(format_usage(&translate!("pathchk-usage"))) .infer_long_args(true) diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index e494ba555..c2d303e26 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -36,6 +36,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(about) .override_usage(format_usage(&translate!("pinky-usage"))) .infer_long_args(true) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index fef4ba5cc..75fea4be1 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -159,6 +159,7 @@ enum PrError { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("pr-about")) .after_help(translate!("pr-after-help")) .override_usage(format_usage(&translate!("pr-usage"))) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index ea93904a1..3ae77a77e 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -57,6 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("printenv-about")) .override_usage(format_usage(&translate!("printenv-usage"))) .infer_long_args(true) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index f5a7bc67c..e48d253f5 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -85,6 +85,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .allow_hyphen_values(true) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("printf-about")) .after_help(translate!("printf-after-help")) .override_usage(format_usage(&translate!("printf-usage"))) diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 728c17d4c..b8352e06f 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -771,6 +771,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .about(translate!("ptx-about")) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("ptx-usage"))) .infer_long_args(true) .arg( diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 130b11096..fccf8c7b3 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -142,6 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("pwd-about")) .override_usage(format_usage(&translate!("pwd-usage"))) .infer_long_args(true) diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index b9aca641c..f634c970a 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -108,6 +108,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("readlink-about")) .override_usage(format_usage(&translate!("readlink-usage"))) .infer_long_args(true) diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index d5d77cc2c..1b4ea6028 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -86,6 +86,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("realpath-about")) .override_usage(format_usage(&translate!("realpath-usage"))) .infer_long_args(true) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 7030d0627..9e3fa9b39 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -227,6 +227,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) .about(translate!("rm-about")) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("rm-usage"))) .after_help(translate!("rm-after-help")) .infer_long_args(true) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 25bc666b6..d2c2ac105 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -171,6 +171,7 @@ struct Opts { pub fn uu_app() -> Command { Command::new(util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(util_name())) .about(translate!("rmdir-about")) .override_usage(format_usage(&translate!("rmdir-usage"))) .infer_long_args(true) diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index 3dfa5cc0c..f6fee9158 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -87,6 +87,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("runcon-about")) .after_help(translate!("runcon-after-help")) .override_usage(format_usage(&translate!("runcon-usage"))) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 6e343f534..5ab029b1a 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -221,6 +221,7 @@ pub fn uu_app() -> Command { .trailing_var_arg(true) .infer_long_args(true) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("seq-about")) .override_usage(format_usage(&translate!("seq-usage"))) .arg( diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index adc0db2bd..c158678a4 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -316,6 +316,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("shred-about")) .after_help(translate!("shred-after-help")) .override_usage(format_usage(&translate!("shred-usage"))) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 3e80e745e..45c1e4ece 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -146,6 +146,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .about(translate!("shuf-about")) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("shuf-usage"))) .infer_long_args(true) .arg( diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 87141e571..4cbaa6e6f 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -40,6 +40,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("sleep-about")) .after_help(translate!("sleep-after-help")) .override_usage(format_usage(&translate!("sleep-usage"))) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 29346315b..f9224f865 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -43,7 +43,7 @@ use std::str::Utf8Error; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{FromIo, strip_errno}; -use uucore::error::{UError, UResult, USimpleError, UUsageError, set_exit_code}; +use uucore::error::{UError, UResult, USimpleError, UUsageError}; use uucore::extendedbigdecimal::ExtendedBigDecimal; use uucore::format_usage; use uucore::line_ending::LineEnding; @@ -1051,14 +1051,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // This logic is similar to the code in clap, but we return 2 as the exit code in case of real failure // (clap returns 1). use uucore::clap_localization::handle_clap_error_with_exit_code; - if e.kind() == clap::error::ErrorKind::UnknownArgument { - handle_clap_error_with_exit_code(e, uucore::util_name(), 2); - } - e.print().unwrap(); - if e.use_stderr() { - set_exit_code(2); - } - return Ok(()); + // Always use the localization handler for proper error and help handling + handle_clap_error_with_exit_code(e, uucore::util_name(), 2); } }; @@ -1349,6 +1343,7 @@ pub fn uu_app() -> Command { .about(translate!("sort-about")) .after_help(translate!("sort-after-help")) .override_usage(format_usage(&translate!("sort-usage"))) + .help_template(uucore::localized_help_template(uucore::util_name())) .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 2bcb5a3a0..70c475782 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -228,6 +228,7 @@ fn handle_preceding_options( pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("split-about")) .after_help(translate!("split-after-help")) .override_usage(format_usage(&translate!("split-usage"))) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 0142f3dd5..4d5dde949 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -1235,6 +1235,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("stat-about")) .override_usage(format_usage(&translate!("stat-usage"))) .infer_long_args(true) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index e4ccd55e0..c2c6beab8 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -234,6 +234,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("stdbuf-about")) .after_help(translate!("stdbuf-after-help")) .override_usage(format_usage(&translate!("stdbuf-usage"))) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 0391b32a5..69400c855 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -1007,6 +1007,7 @@ fn get_sane_control_char(cc_index: S) -> u8 { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("stty-usage"))) .about(translate!("stty-about")) .infer_long_args(true) diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index c45ffc944..2366a59d3 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -138,6 +138,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("sum-usage"))) .about(translate!("sum-about")) .infer_long_args(true) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 4efaebd5c..d58463591 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -227,6 +227,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("sync-about")) .override_usage(format_usage(&translate!("sync-usage"))) .infer_long_args(true) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index f24320183..799e65571 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -57,6 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("tac-usage"))) .about(translate!("tac-about")) .infer_long_args(true) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 7e2a20bcb..ef53b3943 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -461,6 +461,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("tail-about")) .override_usage(format_usage(&translate!("tail-usage"))) .infer_long_args(true) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 29dfef9d5..5c6a120e7 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -95,6 +95,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("tee-about")) .override_usage(format_usage(&translate!("tee-usage"))) .after_help(translate!("tee-after-help")) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index c1a680093..c553e1f8b 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -15,6 +15,7 @@ use std::ffi::{OsStr, OsString}; use std::fs; #[cfg(unix)] use std::os::unix::fs::MetadataExt; +use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; @@ -35,6 +36,7 @@ pub fn uu_app() -> Command { // since we don't recognize -h and -v as help/version flags. Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("test-about")) .override_usage(format_usage(&translate!("test-usage"))) .after_help(translate!("test-after-help")) @@ -49,7 +51,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { if binary_name.ends_with('[') { // If invoked as [ we should recognize --help and --version (but not -h or -v) if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") { - uu_app().get_matches_from(std::iter::once(program).chain(args.into_iter())); + uu_app().get_matches_from_localized(std::iter::once(program).chain(args.into_iter())); return Ok(()); } // If invoked via name '[', matching ']' must be in the last arg diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 755f593bf..dded6bad6 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -121,6 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new("timeout") .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("timeout-about")) .override_usage(format_usage(&translate!("timeout-usage"))) .arg( diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index d61802b3c..451b5b560 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -255,6 +255,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("touch-about")) .override_usage(format_usage(&translate!("touch-usage"))) .infer_long_args(true) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index c254252fb..709cf227b 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -171,6 +171,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("tr-about")) .override_usage(format_usage(&translate!("tr-usage"))) .after_help(translate!("tr-after-help")) diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 788d23067..e19b26408 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -42,6 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("true-about")) // We provide our own help and version options, to ensure maximum compatibility with GNU. .disable_help_flag(true) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index b11796cc0..0ad207b7a 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -118,6 +118,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("truncate-about")) .override_usage(format_usage(&translate!("truncate-usage"))) .infer_long_args(true) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index a83478d20..33822a474 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -77,6 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("tsort-usage"))) .about(translate!("tsort-about")) .infer_long_args(true) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index b893fa8b4..1ac6ed362 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -58,6 +58,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("tty-about")) .override_usage(format_usage(&translate!("tty-usage"))) .infer_long_args(true) diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 84dd4ca7c..d23d8b7fc 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -142,6 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("uname-about")) .override_usage(format_usage(&translate!("uname-usage"))) .infer_long_args(true) diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index f0b8924b2..e4a3a9964 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -155,6 +155,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .override_usage(format_usage(&translate!("unexpand-usage"))) .about(translate!("unexpand-about")) .infer_long_args(true) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 1a2a3c7d9..cb9f9d198 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -592,6 +592,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("uniq-about")) .override_usage(format_usage(&translate!("uniq-usage"))) .infer_long_args(true) diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 4fc5492c6..14602cf38 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -30,6 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("unlink-about")) .override_usage(format_usage(&translate!("unlink-usage"))) .infer_long_args(true) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 22a715306..6e028fbd6 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -72,6 +72,7 @@ pub fn uu_app() -> Command { let cmd = Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(about) .override_usage(format_usage(&translate!("uptime-usage"))) .infer_long_args(true) diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 9439a3efa..ca63e527e 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -90,6 +90,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(about) .override_usage(format_usage(&translate!("users-usage"))) .infer_long_args(true) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 8b3070989..fa111e4e4 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -388,6 +388,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("wc-about")) .override_usage(format_usage(&translate!("wc-usage"))) .infer_long_args(true) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 1a139f20b..2a91b4cf6 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -47,6 +47,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(about) .override_usage(format_usage(&translate!("who-usage"))) .infer_long_args(true) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 40398accf..f65dfd078 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -28,6 +28,7 @@ pub fn whoami() -> UResult { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("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 5affbb6e3..028f6ba7e 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -41,6 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(uucore::crate_version!()) + .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("yes-about")) .override_usage(format_usage(&translate!("yes-usage"))) .arg( diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 8c8dbb74e..6b1419e62 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -228,6 +228,41 @@ pub fn format_usage(s: &str) -> String { s.replace("{}", crate::execution_phrase()) } +/// Creates a localized help template for clap commands. +/// +/// This function returns a help template that uses the localized +/// "Usage:" label from the translation files. This ensures consistent +/// localization across all utilities. +/// +/// Note: We avoid using clap's `{usage-heading}` placeholder because it is +/// hardcoded to "Usage:" and cannot be localized. Instead, we manually +/// construct the usage line with the localized label. +/// +/// # Parameters +/// - `util_name`: The name of the utility (for localization setup) +/// +/// # Example +/// ```no_run +/// use clap::Command; +/// use uucore::localized_help_template; +/// +/// let app = Command::new("myutil") +/// .help_template(localized_help_template("myutil")); +/// ``` +pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr { + // Ensure localization is initialized for this utility + let _ = crate::locale::setup_localization(util_name); + + let usage_label = crate::locale::translate!("common-usage"); + + // Create a template that avoids clap's hardcoded {usage-heading} + let template = format!( + "{{before-help}}{{about-with-newline}}\n{usage_label}: {{usage}}\n\n{{all-args}}{{after-help}}" + ); + + clap::builder::StyledStr::from(template) +} + /// Used to check if the utility is the second argument. /// Used to check if we were called as a multicall binary (`coreutils `) pub fn get_utility_is_second_arg() -> bool { diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 152c17ddb..824b606e5 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -17,7 +17,7 @@ use std::ffi::OsString; /// Based on clap's own design patterns and error categorization fn should_show_simple_help_for_clap_error(kind: ErrorKind) -> bool { match kind { - // Most validation errors should show simple help + // Show simple help ErrorKind::InvalidValue | ErrorKind::InvalidSubcommand | ErrorKind::ValueValidation @@ -73,21 +73,6 @@ fn colorize(text: &str, color: Color) -> String { format!("\x1b[{}m{text}\x1b[0m", color.code()) } -/// Display usage information and help suggestion for errors that require it -/// This consolidates the shared logic between clap errors and UUsageError -pub fn display_usage_and_help(util_name: &str) { - eprintln!(); - // Try to get usage information from localization - let usage_key = format!("{}-usage", util_name); - let usage_text = translate!(&usage_key); - let formatted_usage = crate::format_usage(&usage_text); - let usage_label = translate!("common-usage"); - eprintln!("{}: {}", usage_label, formatted_usage); - eprintln!(); - let help_msg = translate!("clap-error-help-suggestion", "command" => crate::execution_phrase()); - eprintln!("{help_msg}"); -} - pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { // Ensure localization is initialized for this utility (always with common strings) let _ = crate::locale::setup_localization(util_name); @@ -106,8 +91,25 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: }; match err.kind() { - ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { - // For help and version, use clap's built-in formatting and exit with 0 + ErrorKind::DisplayHelp => { + // For help messages, we use the localized help template + // The template should already have the localized usage label, + // but we also replace any remaining "Usage:" instances for fallback + + // Ensure localization is initialized + let _ = crate::locale::setup_localization(util_name); + + let help_text = err.render().to_string(); + + // Replace any remaining "Usage:" with localized version as fallback + let usage_label = translate!("common-usage"); + let localized_help = help_text.replace("Usage:", &format!("{usage_label}:")); + + print!("{}", localized_help); + std::process::exit(0); + } + ErrorKind::DisplayVersion => { + // For version, use clap's built-in formatting and exit with 0 // Output to stdout as expected by tests print!("{}", err.render()); std::process::exit(0); @@ -204,7 +206,15 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Fallback to original rendering if we can't parse eprint!("{}", err.render()); } - std::process::exit(exit_code); + + // InvalidValue errors should exit with code 1 for all utilities + let actual_exit_code = if matches!(kind, ErrorKind::InvalidValue) { + 1 + } else { + exit_code + }; + + std::process::exit(actual_exit_code); } _ => { // For MissingRequiredArgument, use the full clap error as it includes proper usage diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 15872affc..73046a7e5 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1606,4 +1606,133 @@ fn test_argument_suggestion() { } } } + +#[test] +fn test_clap_localization_unknown_argument() { + let test_cases = vec![ + ( + "en_US.UTF-8", + vec![ + "error: unexpected argument '--unknown-option' found", + "Usage:", + "For more information, try '--help'.", + ], + ), + ( + "fr_FR.UTF-8", + vec![ + "erreur : argument inattendu '--unknown-option' trouvé", + "Utilisation:", + "Pour plus d'informations, essayez '--help'.", + ], + ), + ]; + + for (locale, expected_strings) in test_cases { + let result = new_ucmd!() + .env("LC_ALL", locale) + .arg("--unknown-option") + .env("LANG", locale) + .fails(); + + result.code_is(2); // sort uses exit code 2 for invalid options + let stderr = result.stderr_str(); + for expected in expected_strings { + assert!(stderr.contains(expected)); + } + } +} + +#[test] +fn test_clap_localization_help_message() { + // Test help message in English + let result_en = new_ucmd!() + .env("LC_ALL", "en_US.UTF-8") + .arg("--help") + .env("LANG", "en_US.UTF-8") + .succeeds(); + + let stdout_en = result_en.stdout_str(); + assert!(stdout_en.contains("Usage:")); + assert!(stdout_en.contains("Options:")); + + // Test help message in French + let result_fr = new_ucmd!() + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--help") + .env("LANG", "fr_FR.UTF-8") + .succeeds(); + + let stdout_fr = result_fr.stdout_str(); + assert!(stdout_fr.contains("Utilisation:")); + assert!(stdout_fr.contains("Options:")); +} + +#[test] +fn test_clap_localization_missing_required_argument() { + // Test missing required argument + let result_en = new_ucmd!().env("LC_ALL", "en_US.UTF-8").arg("-k").fails(); + + let stderr_en = result_en.stderr_str(); + assert!(stderr_en.contains("error:")); + assert!(stderr_en.contains("-k")); + + // Test in French + let result_fr = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("-k") + .fails(); + + let stderr_fr = result_fr.stderr_str(); + // The main error message should contain French "erreur" + assert!(stderr_fr.contains("error:") || stderr_fr.contains("erreur")); +} + +#[test] +fn test_clap_localization_invalid_value() { + let test_cases = vec![ + ("en_US.UTF-8", "sort: failed to parse key 'invalid'"), + ("fr_FR.UTF-8", "sort: échec d'analyse de la clé 'invalid'"), + ]; + + for (locale, expected_message) in test_cases { + let result = new_ucmd!() + .env("LC_ALL", locale) + .arg("-k") + .env("LANG", locale) + .arg("invalid") + .fails(); + + let stderr = result.stderr_str(); + assert!(stderr.contains(expected_message)); + } +} + +#[test] +fn test_clap_localization_tip_for_value_with_dash() { + // Test tip for passing values that look like options + let result_en = new_ucmd!() + let test_cases = vec![ + ("en_US.UTF-8", vec!["tip:", "-- --file-with-dash"]), + ("fr_FR.UTF-8", vec!["tip:", "-- --file-with-dash"]), // TODO: fix French translation + ]; + assert!(stderr_en.contains("tip:") || stderr_en.contains("conseil:")); + for (locale, expected_strings) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .arg("--output") + .arg("--file-with-dash") + .fails(); + + let stderr = result.stderr_str(); + for expected in expected_strings { + assert!(stderr.contains(expected)); + } + let result_fr = new_ucmd!() + } + } + // The tip should be preserved from clap + assert!(stderr_fr.contains("tip:") || stderr_fr.contains("conseil:")); /* spell-checker: enable */ From 6a9e32f3cab0d9fb5bc1447ed3690451bf73706b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 11 Aug 2025 22:03:59 +0200 Subject: [PATCH 21/24] clap: also support translations for invalid values --- src/uucore/locales/en-US.ftl | 3 + src/uucore/locales/fr-FR.ftl | 3 + src/uucore/src/lib/mods/clap_localization.rs | 72 +++++++++++++++++++- tests/by-util/test_ls.rs | 36 ++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl index 676f9ad3b..da3dbaf3c 100644 --- a/src/uucore/locales/en-US.ftl +++ b/src/uucore/locales/en-US.ftl @@ -12,6 +12,9 @@ common-version = version clap-error-unexpected-argument = { $error_word }: unexpected argument '{ $arg }' found clap-error-similar-argument = { $tip_word }: a similar argument exists: '{ $suggestion }' clap-error-pass-as-value = { $tip_word }: to pass '{ $arg }' as a value, use '{ $tip_command }' +clap-error-invalid-value = { $error_word }: invalid value '{ $value }' for '{ $option }' +clap-error-value-required = { $error_word }: a value is required for '{ $option }' but none was supplied +clap-error-possible-values = possible values clap-error-help-suggestion = For more information, try '{ $command } --help'. common-help-suggestion = For more information, try '--help'. diff --git a/src/uucore/locales/fr-FR.ftl b/src/uucore/locales/fr-FR.ftl index 8bd69e0f6..b6c5a7bcf 100644 --- a/src/uucore/locales/fr-FR.ftl +++ b/src/uucore/locales/fr-FR.ftl @@ -12,6 +12,9 @@ common-version = version clap-error-unexpected-argument = { $error_word } : argument inattendu '{ $arg }' trouvé clap-error-similar-argument = { $tip_word } : un argument similaire existe : '{ $suggestion }' clap-error-pass-as-value = { $tip_word } : pour passer '{ $arg }' comme valeur, utilisez '{ $tip_command }' +clap-error-invalid-value = { $error_word } : valeur invalide '{ $value }' pour '{ $option }' +clap-error-value-required = { $error_word } : une valeur est requise pour '{ $option }' mais aucune n'a été fournie +clap-error-possible-values = valeurs possibles clap-error-help-suggestion = Pour plus d'informations, essayez '{ $command } --help'. common-help-suggestion = Pour plus d'informations, essayez '--help'. diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 824b606e5..8f3bbb5a4 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -6,11 +6,17 @@ //! Helper clap functions to localize error handling and options //! +//! This module provides utilities for handling clap errors with localization support. +//! It uses clap's error context API to extract structured information from errors +//! instead of parsing error strings, providing a more robust solution. +//! use crate::locale::translate; use clap::error::{ContextKind, ErrorKind}; use clap::{ArgMatches, Command, Error}; + +use std::error::Error as StdError; use std::ffi::OsString; /// Determines if a clap error should show simple help instead of full usage @@ -194,7 +200,71 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: } // Check if this is a simple validation error that should show simple help kind if should_show_simple_help_for_clap_error(kind) => { - // For simple validation errors, use the same simple format as other errors + // Special handling for InvalidValue and ValueValidation to provide localized error + if matches!(kind, ErrorKind::InvalidValue | ErrorKind::ValueValidation) { + // Force localization initialization + crate::locale::setup_localization(util_name).ok(); + + // Extract value and option from error context using clap's context API + // This is much more robust than parsing the error string + let invalid_arg = err.get(ContextKind::InvalidArg); + let invalid_value = err.get(ContextKind::InvalidValue); + + if let (Some(arg), Some(value)) = (invalid_arg, invalid_value) { + let option = arg.to_string(); + let value = value.to_string(); + + // Get localized error word + let error_word = translate!("common-error"); + let colored_error_word = maybe_colorize(&error_word, Color::Red); + + // Apply color to value and option if colors are enabled + let colored_value = maybe_colorize(&value, Color::Yellow); + let colored_option = maybe_colorize(&option, Color::Green); + + // Print localized error message + let error_msg = translate!( + "clap-error-invalid-value", + "error_word" => colored_error_word, + "value" => colored_value, + "option" => colored_option + ); + eprintln!("{error_msg}"); + + // For ValueValidation errors, include the validation error details + if matches!(kind, ErrorKind::ValueValidation) { + if let Some(source) = err.source() { + eprintln!(" {}", source); + } + } + + // Show possible values if available (for InvalidValue errors) + if matches!(kind, ErrorKind::InvalidValue) { + if let Some(valid_values) = err.get(ContextKind::ValidValue) { + eprintln!(); + let possible_values_label = translate!("clap-error-possible-values"); + eprintln!(" [{}: {}]", possible_values_label, valid_values); + } + } + + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + std::process::exit(1); + } else { + // Fallback if we can't extract context - use clap's default formatting + let lines: Vec<&str> = rendered_str.lines().collect(); + if let Some(main_error_line) = lines.first() { + eprintln!("{}", main_error_line); + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + } else { + eprint!("{}", err.render()); + } + std::process::exit(1); + } + } + + // For other simple validation errors, use the same simple format as other errors let lines: Vec<&str> = rendered_str.lines().collect(); if let Some(main_error_line) = lines.first() { // Keep the "error: " prefix for test compatibility diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index beb1262a2..72b4b770a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -83,6 +83,42 @@ fn test_invalid_value_returns_1() { } } +/* spellchecker: disable */ +#[test] +fn test_localized_possible_values() { + let test_cases = vec![ + ( + "en_US.UTF-8", + vec![ + "error: invalid value 'invalid_test_value' for '--color", + "[possible values:", + ], + ), + ( + "fr_FR.UTF-8", + vec![ + "erreur : valeur invalide 'invalid_test_value' pour '--color", + "[valeurs possibles:", + ], + ), + ]; + + for (locale, expected_strings) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .arg("--color=invalid_test_value") + .fails(); + + result.code_is(1); + let stderr = result.stderr_str(); + for expected in expected_strings { + assert!(stderr.contains(expected)); + } + } +} +/* spellchecker: enable */ + #[test] fn test_invalid_value_returns_2() { // Invalid values to flags *sometimes* result in error code 2: From a712fc305da0ef6b2111d087efd6f31174fb1767 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 11 Aug 2025 23:26:13 +0200 Subject: [PATCH 22/24] fix some last clap errors mgmt --- src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/clap_localization.rs | 57 +++++++++++++------- tests/by-util/test_base32.rs | 5 +- tests/by-util/test_base64.rs | 3 +- tests/by-util/test_du.rs | 4 +- tests/by-util/test_sort.rs | 13 +---- tests/by-util/test_split.rs | 5 +- 7 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6b1419e62..af0e039d0 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -5,7 +5,7 @@ //! library ~ (core/bundler file) // #![deny(missing_docs)] //TODO: enable this // -// spell-checker:ignore sigaction SIGBUS SIGSEGV extendedbigdecimal +// spell-checker:ignore sigaction SIGBUS SIGSEGV extendedbigdecimal myutil // * feature-gated external crates (re-shared as public internal modules) #[cfg(feature = "libc")] diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 8f3bbb5a4..c2b665830 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -214,30 +214,49 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: let option = arg.to_string(); let value = value.to_string(); - // Get localized error word - let error_word = translate!("common-error"); - let colored_error_word = maybe_colorize(&error_word, Color::Red); + // Check if this is actually a missing value (empty string) + if value.is_empty() { + // This is the case where no value was provided for an option that requires one + let error_word = translate!("common-error"); + eprintln!( + "{}", + translate!("clap-error-value-required", "error_word" => error_word, "option" => option) + ); + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + std::process::exit(1); + } else { + // Get localized error word and prepare message components outside conditionals + let error_word = translate!("common-error"); + let colored_error_word = maybe_colorize(&error_word, Color::Red); + let colored_value = maybe_colorize(&value, Color::Yellow); + let colored_option = maybe_colorize(&option, Color::Green); - // Apply color to value and option if colors are enabled - let colored_value = maybe_colorize(&value, Color::Yellow); - let colored_option = maybe_colorize(&option, Color::Green); + let error_msg = translate!( + "clap-error-invalid-value", + "error_word" => colored_error_word, + "value" => colored_value, + "option" => colored_option + ); - // Print localized error message - let error_msg = translate!( - "clap-error-invalid-value", - "error_word" => colored_error_word, - "value" => colored_value, - "option" => colored_option - ); - eprintln!("{error_msg}"); - - // For ValueValidation errors, include the validation error details - if matches!(kind, ErrorKind::ValueValidation) { - if let Some(source) = err.source() { - eprintln!(" {}", source); + // For ValueValidation errors, include the validation error in the message + if matches!(kind, ErrorKind::ValueValidation) { + if let Some(source) = err.source() { + // Print error with validation detail on same line + eprintln!("{error_msg}: {}", source); + } else { + // Print localized error message + eprintln!("{error_msg}"); + } + } else { + // Print localized error message + eprintln!("{error_msg}"); } } + // For ValueValidation errors, include the validation error details + // Note: We don't print these separately anymore as they're part of the main message + // Show possible values if available (for InvalidValue errors) if matches!(kind, ErrorKind::InvalidValue) { if let Some(valid_values) = err.get(ContextKind::ValidValue) { diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 8bdf19ed0..252256668 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -112,13 +112,12 @@ fn test_wrap() { #[test] fn test_wrap_no_arg() { - let expected_stderr = "a value is required for '--wrap ' but none was supplied"; - for wrap_param in ["-w", "--wrap"] { new_ucmd!() .arg(wrap_param) .fails() - .stderr_contains(expected_stderr) + .stderr_contains("error: a value is required for '--wrap ' but none was supplied") + .stderr_contains("For more information, try '--help'.") .no_stdout(); } } diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 2feb6ceff..ad0d1c2b1 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -147,7 +147,8 @@ fn test_wrap_no_arg() { new_ucmd!() .arg(wrap_param) .fails() - .stderr_contains("a value is required for '--wrap ' but none was supplied") + .stderr_contains("error: a value is required for '--wrap ' but none was supplied") + .stderr_contains("For more information, try '--help'.") .no_stdout(); } } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index ba64152e7..86c358724 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -862,7 +862,9 @@ fn test_du_threshold_error_handling() { new_ucmd!() .arg("--threshold") .fails() - .stderr_contains("a value is required for '--threshold ' but none was supplied") + .stderr_contains( + "error: a value is required for '--threshold ' but none was supplied", + ) .stderr_contains("For more information, try '--help'."); } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 73046a7e5..70e294d6c 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1674,19 +1674,8 @@ fn test_clap_localization_missing_required_argument() { let result_en = new_ucmd!().env("LC_ALL", "en_US.UTF-8").arg("-k").fails(); let stderr_en = result_en.stderr_str(); - assert!(stderr_en.contains("error:")); + assert!(stderr_en.contains(" a value is required for '--key ' but none was supplied")); assert!(stderr_en.contains("-k")); - - // Test in French - let result_fr = new_ucmd!() - .env("LANG", "fr_FR.UTF-8") - .env("LC_ALL", "fr_FR.UTF-8") - .arg("-k") - .fails(); - - let stderr_fr = result_fr.stderr_str(); - // The main error message should contain French "erreur" - assert!(stderr_fr.contains("error:") || stderr_fr.contains("erreur")); } #[test] diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index a91ffc8ff..34b24d84d 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1935,9 +1935,8 @@ fn test_split_separator_no_value() { .ignore_stdin_write_error() .pipe_in("a\n") .fails() - .stderr_contains( - "error: a value is required for '--separator ' but none was supplied", - ); + .stderr_contains("error: a value is required for '--separator ' but none was supplied") + .stderr_contains("For more information, try '--help'."); } #[test] From 968fc85efd475e3f80c4f62ccae34962acaf8422 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 13 Aug 2025 14:18:42 +0200 Subject: [PATCH 23/24] l10n: address review comments --- src/uucore/src/lib/features/perms.rs | 7 ++++--- src/uucore/src/lib/features/proc_info.rs | 2 ++ src/uucore/src/lib/mods/locale.rs | 3 +++ tests/by-util/test_dd.rs | 4 ++-- tests/by-util/test_mv.rs | 1 - tests/by-util/test_sort.rs | 18 +++++++----------- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 72fb0b27c..bddce9cf2 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -11,16 +11,17 @@ use crate::display::Quotable; use crate::error::{UResult, USimpleError, strip_errno}; pub use crate::features::entries; use crate::show_error; + use clap::{Arg, ArgMatches, Command}; + use libc::{gid_t, uid_t}; use options::traverse; use walkdir::WalkDir; -use std::io::Error as IOError; -use std::io::Result as IOResult; - use std::ffi::CString; use std::fs::Metadata; +use std::io::Error as IOError; +use std::io::Result as IOResult; use std::os::unix::fs::MetadataExt; use std::os::unix::ffi::OsStrExt; diff --git a/src/uucore/src/lib/features/proc_info.rs b/src/uucore/src/lib/features/proc_info.rs index 3f5334d10..8345e7e09 100644 --- a/src/uucore/src/lib/features/proc_info.rs +++ b/src/uucore/src/lib/features/proc_info.rs @@ -30,6 +30,7 @@ #![allow(dead_code)] use crate::features::tty::Teletype; + use std::hash::Hash; use std::{ collections::HashMap, @@ -38,6 +39,7 @@ use std::{ path::PathBuf, rc::Rc, }; + use walkdir::{DirEntry, WalkDir}; /// State or process diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index c8e735403..58a45dc0e 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -5,12 +5,15 @@ // spell-checker:disable use crate::error::UError; + use fluent::{FluentArgs, FluentBundle, FluentResource}; use fluent_syntax::parser::ParserError; + use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::OnceLock; + use thiserror::Error; use unic_langid::LanguageIdentifier; diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index e7e23b9ca..74701e962 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1617,7 +1617,7 @@ fn test_reading_partial_blocks_from_fifo() { .args(["dd", "ibs=3", "obs=3", &format!("if={fifoname}")]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .env("LANG", "C") + .env("LC_ALL", "C") .spawn() .unwrap(); @@ -1662,7 +1662,7 @@ fn test_reading_partial_blocks_from_fifo_unbuffered() { .args(["dd", "bs=3", "ibs=1", "obs=1", &format!("if={fifoname}")]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .env("LANG", "C") + .env("LC_ALL", "C") .spawn() .unwrap(); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 2f2f5d1de..23d7315fb 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2550,7 +2550,6 @@ fn test_mv_selinux_context() { #[test] fn test_mv_error_usage_display_missing_arg() { - new_ucmd!() .arg("--target-directory=.") .fails() diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 70e294d6c..5c1bb1570 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1630,9 +1630,9 @@ fn test_clap_localization_unknown_argument() { for (locale, expected_strings) in test_cases { let result = new_ucmd!() + .env("LANG", locale) .env("LC_ALL", locale) .arg("--unknown-option") - .env("LANG", locale) .fails(); result.code_is(2); // sort uses exit code 2 for invalid options @@ -1647,9 +1647,9 @@ fn test_clap_localization_unknown_argument() { fn test_clap_localization_help_message() { // Test help message in English let result_en = new_ucmd!() + .env("LANG", "en_US.UTF-8") .env("LC_ALL", "en_US.UTF-8") .arg("--help") - .env("LANG", "en_US.UTF-8") .succeeds(); let stdout_en = result_en.stdout_str(); @@ -1658,9 +1658,9 @@ fn test_clap_localization_help_message() { // Test help message in French let result_fr = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") .env("LC_ALL", "fr_FR.UTF-8") .arg("--help") - .env("LANG", "fr_FR.UTF-8") .succeeds(); let stdout_fr = result_fr.stdout_str(); @@ -1687,9 +1687,9 @@ fn test_clap_localization_invalid_value() { for (locale, expected_message) in test_cases { let result = new_ucmd!() + .env("LANG", locale) .env("LC_ALL", locale) .arg("-k") - .env("LANG", locale) .arg("invalid") .fails(); @@ -1700,13 +1700,11 @@ fn test_clap_localization_invalid_value() { #[test] fn test_clap_localization_tip_for_value_with_dash() { - // Test tip for passing values that look like options - let result_en = new_ucmd!() let test_cases = vec![ ("en_US.UTF-8", vec!["tip:", "-- --file-with-dash"]), ("fr_FR.UTF-8", vec!["tip:", "-- --file-with-dash"]), // TODO: fix French translation ]; - assert!(stderr_en.contains("tip:") || stderr_en.contains("conseil:")); + for (locale, expected_strings) in test_cases { let result = new_ucmd!() .env("LANG", locale) @@ -1719,9 +1717,7 @@ fn test_clap_localization_tip_for_value_with_dash() { for expected in expected_strings { assert!(stderr.contains(expected)); } - let result_fr = new_ucmd!() - } } - // The tip should be preserved from clap - assert!(stderr_fr.contains("tip:") || stderr_fr.contains("conseil:")); +} + /* spell-checker: enable */ From 26a8d1c7be4145538e8fde1641c56bb2613149b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 13:57:33 +0000 Subject: [PATCH 24/24] chore(deps): update actions/checkout action to v5 --- .github/workflows/l10n.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml index ccec22d54..d6909eb5c 100644 --- a/.github/workflows/l10n.yml +++ b/.github/workflows/l10n.yml @@ -136,7 +136,7 @@ jobs: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - uses: dtolnay/rust-toolchain@stable