From 206da5c28b998d6b432af4053363428093df178c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 18:23:52 +0200 Subject: [PATCH 01/19] 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 | 183 +++++++++++++++++++ src/uucore/src/lib/mods/locale.rs | 127 +++++++++++++ 6 files changed, 379 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 00000000000..358eda0d5fd --- /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 00000000000..cbeab358d24 --- /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 ab28d657b4e..8c8dbb74e09 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 7af54ff5a6a..e33bf031958 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 00000000000..3f93d453342 --- /dev/null +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -0,0 +1,183 @@ +// 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 7b86af32ce6..aec455cfe02 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 4a570c331d690e2cc1d51be41109174baa97a174 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 18:27:45 +0200 Subject: [PATCH 02/19] 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 | 8 ++++---- src/uu/basename/src/basename.rs | 3 ++- src/uu/basenc/src/basenc.rs | 1 - 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 | 17 +++++++++-------- 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 | 5 +++-- 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 ++- 83 files changed, 193 insertions(+), 104 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 64a79a3fd1e..c6d2283bd04 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 29ad9d273c9..cd398a72b39 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 b7d1124ac1e..b1c81714bca 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -9,16 +9,15 @@ 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; - pub const BASE_CMD_PARSE_ERROR: i32 = 1; /// Encoded output will be formatted in lines of this length (the last line can be shorter) @@ -100,7 +99,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 255ff611017..802eb446406 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/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index c6cd48c2acd..0d221dbeea0 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -13,7 +13,6 @@ use uucore::{ encoding::Format, error::{UResult, UUsageError}, }; - fn get_encodings() -> Vec<(&'static str, Format, String)> { vec![ ("base64", Format::Base64, translate!("basenc-help-base64")), diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index c7f975952f6..22cd38f38fb 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 ca65cf4a7c2..de1dc2f64ba 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 f2d2bd47a20..cb540aa7ab7 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 6dc28040ee5..959c23e4066 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 e55d181d6c5..81bcab418a7 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 e826ac3c470..3dee0fe0187 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 ba58479b2fe..8c98d37c586 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 4bf323507aa..cf9bfdbe4cd 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 6d5a418c287..1eadd6ce250 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 4d77dcfe77f..d231da8b1cd 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 6c8a1ee7af8..2d6d34b230a 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 b76d49f67ce..f6bd9ec4019 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 6d7a4a5a51e..73618d7e0e8 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 f39d257de56..b43cee53011 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 776a98536ac..6dec840da43 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 92128f5f1ce..a0a3f944f49 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 f6e5550559c..80c1af0e365 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 318e3875b47..c3e743d8562 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 498c72802d4..9d6947223a4 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 a6fc13e23d6..4204b979e9b 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 b7e4334c48e..bfd8a076afb 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 f5054389b53..c29b574895a 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 558c6aff1a6..74f2c48d9f9 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 41e92645044..688ac232a9d 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 4f434485ef0..dc249b5d0f8 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 ac1948c516c..338f90f6cfe 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 3dec9bef12c..6ba16d1012d 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 148489b2d63..b1dc4dc92eb 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 20528a700f5..327ef09b0f7 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 df53b16b85d..5d5ce9bdcaf 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 77d8fe15c6b..d34712339f8 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 f4c68586379..5e9477c2980 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 b2c3493372c..d1cf516478e 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 33d842d925d..8032a5fada8 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 5bd79ade9eb..53b7eb8d219 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 90ac7c875c5..b1dbacf71be 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 4bd2e80df69..829e4f53bc4 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 948e486c9f8..b8a9b33cdd3 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -10,7 +10,7 @@ mod error; mod hardlink; use clap::builder::ValueParser; -use clap::{Arg, ArgAction, ArgMatches, Command, 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 +51,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 +152,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 +161,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 1e5eb3d7285..414f5d73574 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 f7be033bd95..9d913d33e6b 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 f8dddd8c69f..8520164955f 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 7db53b6b6f9..3562e38c389 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 b6b9b59a8a4..6dc3d79e959 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 3b7a3c164f4..d5a318f0d7f 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 fb7cd155b59..c53839c47f3 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 59c226046bb..e80f02d0561 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 fbad8c93adc..3e7bcd3d984 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 063be33ff21..6d875c537ec 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 2c536bcb62c..08408b40b6f 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 78ec37a47eb..f2a26ae8289 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 39dad5f9efe..5fe5ae0aec0 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 8f61d9ab25b..c9f06ff5be5 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 f7971aa80e3..9595e25f627 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 34346c0d60c..bbbcae74407 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 a3479783563..73f4c067cc8 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 254875eaa47..d2e854b9bb8 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 1731c2af140..37d54ee99eb 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 74b6253fbdb..29346315b26 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 b8351c31efb..a2c85464fcd 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 6bc0aa0b3e9..f39e248423b 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 5de8f9e36c7..63b0a6e62f5 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 8359ec00281..061fc250961 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 95af36c8044..9e643aa7cf7 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 52d885bf4a0..4911a4b09b6 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 d7d4b670459..14c015bf793 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 fde4cdc1ba9..1f413e4e04f 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 5e5316dbced..f057287f51a 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -12,7 +12,8 @@ use operation::{ translate_input, }; use std::ffi::OsString; -use std::io::{Write, stdin, stdout}; +use std::io::{BufWriter, 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 646303bab2a..dab1ffc9cb2 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 d5c843fcdfc..b893fa8b4d6 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 092ec1a649a..c30c00bb443 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 47d6e04d06f..c5935b40ec1 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 41d0f6464bf..d8470e1968a 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 67761ca06a0..92c50d7cc0d 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 920f4602f64..140001c9efe 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 8e0bbd3b997..8d1e31dabe4 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 f9f322a5d25..70b46a59335 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 928e81edf12..3b0b11ead91 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 6b2bc6495d7..ec818a9558f 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 085f9d5c62f7ddcc296d828dd5eede35790cab65 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 10:51:29 +0200 Subject: [PATCH 03/19] 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 b4a151d810c..ccec22d54c3 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 a00f65c09ea30a82f178e63e1d990f8e113da725 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 22:22:31 +0200 Subject: [PATCH 04/19] add test (in sort) to verify that the clap error mgmt work (english and french) --- tests/by-util/test_sort.rs | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 657a3addd5f..963e39d35eb 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1568,3 +1568,46 @@ 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 result_en = new_ucmd!() + .env("LANG", "en_US.UTF-8") + .env("LC_ALL", "en_US.UTF-8") + .arg("--revrse") // Typo + .fails(); + + let stderr_en = result_en.stderr_str(); + assert!(stderr_en.contains("tip")); + assert!(stderr_en.contains("similar")); + assert!(stderr_en.contains("--reverse")); + + let result_fr = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--revrse") // Typo + .fails(); + + let stderr_fr = result_fr.stderr_str(); + assert!(stderr_fr.contains("conseil")); + assert!(stderr_fr.contains("similaire")); + assert!(stderr_fr.contains("--reverse")); +} +/* spell-checker: enable */ From d00ad102e5b8e4b70bd6144948de76330321ca53 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Aug 2025 22:55:18 +0200 Subject: [PATCH 05/19] 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 199830c2d1d..51dd4a30ce9 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 0326522d3b1698045a2a320d27a7bd06c6b3a3c6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 5 Aug 2025 10:44:12 +0200 Subject: [PATCH 06/19] l10n: also install uucore locales --- GNUmakefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 54aea865fd2..20dc731d3b0 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 3d69817817225317041e02b4e518bcc391f47156 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 18:49:04 +0200 Subject: [PATCH 07/19] 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 | 14 ++--- 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 | 61 ++++++++------------ 76 files changed, 105 insertions(+), 118 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index cd398a72b39..3b7b4dc108c 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 b1c81714bca..ec37886b3e7 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -99,7 +99,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 802eb446406..557d981d7e0 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 22cd38f38fb..f693643109c 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 de1dc2f64ba..263d73475dc 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 cb540aa7ab7..01fa0422ca2 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 959c23e4066..dd36cfefcba 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 81bcab418a7..8ba393b5bc8 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 3dee0fe0187..01c08324e47 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 8c98d37c586..4a13ad40f1c 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 cf9bfdbe4cd..1bc8cb0d6e2 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 1eadd6ce250..6a33aa74fec 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 d231da8b1cd..0de57fe702d 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 2d6d34b230a..7db47355d4d 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 f6bd9ec4019..0a58ce37ab6 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 73618d7e0e8..5f301edd7e7 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 b43cee53011..12d94edf156 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 6dec840da43..1e7a176b672 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 80c1af0e365..ed7ab7ff7dc 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 c3e743d8562..34bc6b65699 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 9d6947223a4..156555798cb 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 4204b979e9b..199a09bc762 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 bfd8a076afb..c3f1c2c0cbb 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 c29b574895a..efbe75475db 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 74f2c48d9f9..734cfdbed4c 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 688ac232a9d..86fd86a176a 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 338f90f6cfe..bb366377914 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 6ba16d1012d..a55c9afd555 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 b1dc4dc92eb..aef9b45e3f3 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 327ef09b0f7..8f18bf86b61 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 5d5ce9bdcaf..d9a7afbd707 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 d34712339f8..bb34f4b7462 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 d1cf516478e..c82309087bb 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 8032a5fada8..ae3466da64c 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 53b7eb8d219..b67e48e0cc8 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 829e4f53bc4..473a7f859d0 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 b8a9b33cdd3..8e9308eb261 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -152,7 +152,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) @@ -161,12 +161,12 @@ 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" - ), - )); + uu_app() + .error( + ErrorKind::TooFewValues, + translate!("mv-error-insufficient-arguments", "arg_files" => ARG_FILES), + ) + .exit(); } let overwrite_mode = determine_overwrite_mode(&matches); diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 414f5d73574..f759b592610 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 9d913d33e6b..7137ebda739 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 8520164955f..008f51558e3 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 3562e38c389..936e32c882f 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 6dc3d79e959..807e4debf93 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 d5a318f0d7f..9016a4878bb 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 c53839c47f3..d8dcbc9283a 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 e80f02d0561..3581cedbab3 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 3e7bcd3d984..fef4ba5cc9b 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 6d875c537ec..a7809b830e8 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 08408b40b6f..f5a7bc67c30 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 f2a26ae8289..728c17d4c2c 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 5fe5ae0aec0..130b11096f6 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 c9f06ff5be5..b9aca641ca7 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 9595e25f627..7030d06274a 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 bbbcae74407..25bc666b6fd 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 73f4c067cc8..adc0db2bd7c 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 d2e854b9bb8..3e80e745e01 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 37d54ee99eb..87141e5713a 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 a2c85464fcd..2bcb5a3a019 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 f39e248423b..0142f3dd5d5 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 63b0a6e62f5..0391b32a5e9 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 061fc250961..c45ffc94447 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 9e643aa7cf7..4efaebd5c46 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 4911a4b09b6..f2432018379 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 14c015bf793..29dfef9d55a 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 1f413e4e04f..d61802b3c27 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 f057287f51a..f697649bd98 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 dab1ffc9cb2..a83478d2013 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 c30c00bb443..84dd4ca7c26 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 c5935b40ec1..4fc5492c6e4 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 d8470e1968a..22a71530628 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 92c50d7cc0d..9439a3efaa9 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 140001c9efe..8b3070989ec 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 8d1e31dabe4..4a2954d274f 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 70b46a59335..7f7f5dc91d5 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 3b0b11ead91..40398accf72 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 ec818a9558f..5affbb6e3d1 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 3f93d453342..2ea5236e920 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -13,7 +13,7 @@ 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 { +fn colorize(text: &str, color_code: &str) -> String { format!("\x1b[{color_code}m{text}\x1b[0m") } @@ -44,6 +44,15 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: // Simple check - if the rendered output contains ANSI escape codes, colors are enabled let colors_enabled = rendered_str.contains("\x1b["); + // Helper closure to conditionally apply colors + let apply_color = |text: &str, color: &str| { + if colors_enabled { + colorize(text, color) + } else { + text.to_string() + } + }; + if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { let arg_str = invalid_arg.to_string(); @@ -51,16 +60,10 @@ 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()) - }; + // Apply colors using helper closure + let colored_arg = apply_color(&arg_str, colors::YELLOW); + let colored_error_word = apply_color(&error_word, colors::RED); + let colored_tip_word = apply_color(&tip_word, colors::GREEN); // Print main error message let error_msg = translate!( @@ -74,11 +77,7 @@ 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 = apply_color(&suggested_arg.to_string(), colors::GREEN); let suggestion_msg = translate!( "clap-error-similar-argument", "tip_word" => colored_tip_word, @@ -86,11 +85,7 @@ 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 = apply_color(&format!("-- {arg_str}"), colors::GREEN); let tip_msg = translate!( "clap-error-pass-as-value", "arg" => colored_arg, @@ -112,16 +107,8 @@ 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 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") - }; + // Generic fallback case - reuse colors_enabled and apply_color from above scope + let colored_error_word = apply_color(&translate!("common-error"), colors::RED); eprintln!("{colored_error_word}: unexpected argument"); std::process::exit(exit_code); } @@ -142,15 +129,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, @@ -163,7 +150,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, @@ -172,7 +159,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, From 45edcc4d3a0bae571d2cad497e9b03dab9ee3539 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 18:56:05 +0200 Subject: [PATCH 08/19] fix try --- src/uucore/src/lib/mods/clap_localization.rs | 170 ++++++++++++++----- tests/test_util_name.rs | 1 + 2 files changed, 131 insertions(+), 40 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 2ea5236e920..2d33a4b19af 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -12,16 +12,79 @@ use clap::error::{ContextKind, ErrorKind}; use clap::{ArgMatches, Command, Error}; use std::ffi::OsString; +/// 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 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_code: &str) -> String { - format!("\x1b[{color_code}m{text}\x1b[0m") +fn colorize(text: &str, color: Color) -> String { + format!("\x1b[{}m{text}\x1b[0m", color.code()) } -/// Color constants for consistent styling -pub mod colors { - pub const RED: &str = "31"; - pub const YELLOW: &str = "33"; - pub const GREEN: &str = "32"; +/// 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!("common-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) -> ! { @@ -29,6 +92,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 @@ -37,22 +113,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["); - - // Helper closure to conditionally apply colors - let apply_color = |text: &str, color: &str| { - if colors_enabled { - colorize(text, color) - } else { - text.to_string() - } - }; - + // 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(); @@ -60,10 +121,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 using helper closure - let colored_arg = apply_color(&arg_str, colors::YELLOW); - let colored_error_word = apply_color(&error_word, colors::RED); - let colored_tip_word = apply_color(&tip_word, colors::GREEN); + 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!( @@ -77,7 +137,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 = apply_color(&suggested_arg.to_string(), colors::GREEN); + let colored_suggestion = + maybe_colorize(&suggested_arg.to_string(), Color::Green); let suggestion_msg = translate!( "clap-error-similar-argument", "tip_word" => colored_tip_word, @@ -85,7 +146,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: ); eprintln!(" {suggestion_msg}"); } else { - let colored_tip_command = apply_color(&format!("-- {arg_str}"), colors::GREEN); + let colored_tip_command = + maybe_colorize(&format!("-- {arg_str}"), Color::Green); let tip_msg = translate!( "clap-error-pass-as-value", "arg" => colored_arg, @@ -95,28 +157,56 @@ 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 - reuse colors_enabled and apply_color from above scope - let colored_error_word = apply_color(&translate!("common-error"), colors::RED); + // Generic fallback case + 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); + + // 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 other errors, use the simple format but with original clap wording + 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); + } + + // Always use the expected test format for help + eprintln!("For more information, try '--help'"); + + std::process::exit(exit_code); } } } diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index a9fbb29d9f3..23dbccfe721 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 d2690fdf9e7cbd16624944ac7c7d488d6311311c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 22:48:16 +0200 Subject: [PATCH 09/19] fix syntax --- src/uucore/src/lib/mods/clap_localization.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 2d33a4b19af..0245904405e 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -184,9 +184,9 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: 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'"); + eprintln!("For more information, try '--help'."); } else { // Fallback to original rendering if we can't parse eprint!("{}", err.render()); @@ -204,7 +204,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: } // Always use the expected test format for help - eprintln!("For more information, try '--help'"); + eprintln!("For more information, try '--help'."); std::process::exit(exit_code); } From 72f56832085c61b4ed9fdafd80c023536c8650bc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 23:23:01 +0200 Subject: [PATCH 10/19] fix more --- src/uu/mv/src/mv.rs | 11 ++++----- src/uucore/src/lib/mods/clap_localization.rs | 22 ++++++++++++++--- tests/by-util/test_du.rs | 10 ++++++++ tests/by-util/test_mv.rs | 25 ++++++++++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 8e9308eb261..b716b2e2621 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -161,12 +161,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect(); if files.len() == 1 && !matches.contains_id(OPT_TARGET_DIRECTORY) { - uu_app() - .error( - ErrorKind::TooFewValues, - translate!("mv-error-insufficient-arguments", "arg_files" => ARG_FILES), - ) - .exit(); + 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/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 0245904405e..fe9f081d168 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -83,7 +83,7 @@ pub fn display_usage_and_help(util_name: &str) { let usage_label = translate!("common-usage"); eprintln!("{}: {}", usage_label, formatted_usage); eprintln!(); - let help_msg = translate!("common-help-suggestion", "command" => crate::execution_phrase()); + let help_msg = translate!("clap-error-help-suggestion", "command" => crate::execution_phrase()); eprintln!("{help_msg}"); } @@ -194,7 +194,22 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: std::process::exit(exit_code); } _ => { - // For other errors, use the simple format but with original clap wording + // 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(); @@ -203,7 +218,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: eprintln!("{}", first_line); } - // Always use the expected test format for help + // For other errors, just show help suggestion + eprintln!(); eprintln!("For more information, try '--help'."); std::process::exit(exit_code); diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 6c4191a2089..ba64152e7a3 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 8da94e86434..34aba9c91e4 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2547,3 +2547,28 @@ fn test_mv_selinux_context() { let _ = std::fs::remove_file(at.plus_as_string(src)); } } + +#[test] +fn test_mv_error_usage_display() { + let (at, _ucmd) = at_and_ucmd!(); + at.touch("file1"); + + // Test case 1: No files provided with --target option (MissingRequiredArgument) + 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 case 2: Only one file provided (TooFewValues from custom mv logic) + 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'."); +} From afd6390b480cacd6860a344d2464737aa7086d71 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 23:43:47 +0200 Subject: [PATCH 11/19] clap: override the usage section --- src/uucore/src/lib/mods/clap_localization.rs | 80 ++++++++++++-------- tests/by-util/test_comm.rs | 18 +++++ 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index fe9f081d168..4216c7ce624 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -113,57 +113,73 @@ 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}"); - } 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}"); + 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!(); } // 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 @@ -172,7 +188,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 0177216c77c..b0619af004c 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -572,3 +572,21 @@ 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!()); + let at = &scene.fixtures; + at.write("a", "1\n3\n3\n3\n"); + at.write("b", "2\n2\n3\n3\n3\n"); + + // 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 0caf5fc40b72650263bba65aa6d3683ac680a549 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 9 Aug 2025 23:45:01 +0200 Subject: [PATCH 12/19] 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 4216c7ce624..5751d2e8540 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -115,7 +115,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(); @@ -123,11 +123,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); @@ -142,7 +150,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 } @@ -150,7 +161,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 = @@ -162,7 +173,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) } @@ -178,7 +192,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!(); @@ -190,7 +208,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 aec455cfe02..8e1a09a23fb 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 7fdc75eb33001a4e8ec321bfdb22335dff235b87 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 09:31:22 +0200 Subject: [PATCH 13/19] 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 a7809b830e8..ea93904a171 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 5751d2e8540..89065019ee0 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -116,6 +116,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 4f01e526d19..0bfc71f07e0 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 15315f820eadb055e4cc0ccba92024caeff36722 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 09:38:53 +0200 Subject: [PATCH 14/19] Port factor for translation --- src/uucore/src/lib/mods/clap_localization.rs | 36 +++++++++++--------- tests/by-util/test_factor.rs | 13 +++++++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 89065019ee0..0aa3bbb4638 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -124,11 +124,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" { @@ -137,18 +141,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 = { @@ -169,9 +164,20 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: eprintln!("{error_msg}"); eprintln!(); - // Show suggestion if available + // Look for clap's built-in tips in the original error rendering + // These usually start with " tip:" and contain useful information + for line in &lines { + if line.trim().starts_with("tip:") { + eprintln!("{}", line); + eprintln!(); + } + } + + // Show suggestion if available (different from tips) 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 = { @@ -193,9 +199,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: eprintln!(); } - // 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 = { @@ -208,7 +213,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 2324da2a0ed..f06e7a61634 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 f534bb9ac4d6e480ff4261a582731f8370f83feb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 11:19:20 +0200 Subject: [PATCH 15/19] 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 | 737 +++++++++---------- 3 files changed, 386 insertions(+), 447 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index c6d2283bd04..64a79a3fd1e 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 0aa3bbb4638..581330a4190 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -88,9 +88,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(); @@ -114,7 +113,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 { @@ -133,34 +132,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!(); @@ -180,21 +162,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!(); } @@ -203,14 +175,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'."); @@ -218,14 +183,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 8e1a09a23fb..7fc33d6cbd3 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,7 @@ 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() - } else { - String::new() - }; - LocalizationError::ParseResource { - error: first_err, - snippet, - } - }, - )?; - - 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 +217,67 @@ 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 mut bundle = FluentBundle::new(vec![locale.clone()]); + bundle.set_use_isolating(false); - 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, + // 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 = FluentResource::try_new(uucore_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| uucore_content.get(range)) + .unwrap_or("") + .to_string(); + LocalizationError::ParseResource { + error: first_err, + snippet, + } + } else { + LocalizationError::LocalesDirNotFound("Parse error without details".to_string()) } - } 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_overriding(uucore_resource); + } - bundle.add_resource(resource).map_err(|errs| { - LocalizationError::Bundle(format!( - "Failed to add embedded resource to bundle for {locale}: {errs:?}", - )) - })?; + // 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 = 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()) + } + }, + )?; - Ok(bundle) + 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 +365,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 +406,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 +576,79 @@ 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) { + match FluentResource::try_new(ftl_content.clone()) { + Ok(resource) => { + bundle.add_resource_overriding(resource); + return Ok(bundle); + } + Err((_, errs)) => { + 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(); + return Err(LocalizationError::ParseResource { + error: first_err, + snippet, + }); + } + } + } + } + + 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 +714,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 +731,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 +746,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 +783,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 +800,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 +825,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 +843,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 +860,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 +881,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 +899,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 +923,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 +932,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 +939,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 +958,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,141 +976,6 @@ invalid-syntax = This is { $missing .unwrap(); } - #[test] - fn test_detect_system_locale_from_lang_env() { - // Test locale parsing logic directly instead of relying on environment variables - // which can have race conditions in multi-threaded test environments - - // Test parsing logic with UTF-8 encoding - let locale_with_encoding = "fr-FR.UTF-8"; - let parsed = locale_with_encoding.split('.').next().unwrap(); - let lang_id = LanguageIdentifier::from_str(parsed).unwrap(); - assert_eq!(lang_id.to_string(), "fr-FR"); - - // Test parsing logic without encoding - let locale_without_encoding = "es-ES"; - let lang_id = LanguageIdentifier::from_str(locale_without_encoding).unwrap(); - assert_eq!(lang_id.to_string(), "es-ES"); - - // Test that DEFAULT_LOCALE is valid - let default_lang_id = LanguageIdentifier::from_str(DEFAULT_LOCALE).unwrap(); - assert_eq!(default_lang_id.to_string(), "en-US"); - } - - #[test] - fn test_detect_system_locale_no_lang_env() { - // Save current LANG value - let original_lang = env::var("LANG").ok(); - - // Remove LANG environment variable - unsafe { - env::remove_var("LANG"); - } - - let result = detect_system_locale(); - assert!(result.is_ok()); - assert_eq!(result.unwrap().to_string(), "en-US"); - - // Restore original LANG value - if let Some(val) = original_lang { - unsafe { - env::set_var("LANG", val); - } - } else { - {} // Was already unset - } - } - - #[test] - fn test_setup_localization_success() { - std::thread::spawn(|| { - let temp_dir = create_test_locales_dir(); - - // Save current LANG value - let original_lang = env::var("LANG").ok(); - unsafe { - env::set_var("LANG", "fr-FR.UTF-8"); - } - - let result = setup_localization(temp_dir.path().to_str().unwrap()); - assert!(result.is_ok()); - - // Test that French is loaded - let message = get_message("greeting"); - assert_eq!(message, "Bonjour, le monde!"); - - // Restore original LANG value - if let Some(val) = original_lang { - unsafe { - env::set_var("LANG", val); - } - } else { - unsafe { - env::remove_var("LANG"); - } - } - }) - .join() - .unwrap(); - } - - #[test] - fn test_setup_localization_falls_back_to_english() { - std::thread::spawn(|| { - let temp_dir = create_test_locales_dir(); - - // Save current LANG value - let original_lang = env::var("LANG").ok(); - unsafe { - env::set_var("LANG", "de-DE.UTF-8"); - } // German file doesn't exist - - let result = setup_localization(temp_dir.path().to_str().unwrap()); - assert!(result.is_ok()); - - // Should fall back to English - let message = get_message("greeting"); - assert_eq!(message, "Hello, world!"); - - // Restore original LANG value - if let Some(val) = original_lang { - unsafe { - env::set_var("LANG", val); - } - } else { - unsafe { - env::remove_var("LANG"); - } - } - }) - .join() - .unwrap(); - } - - #[test] - fn test_setup_localization_fallback_to_embedded() { - std::thread::spawn(|| { - // Force English locale for this test - unsafe { - std::env::set_var("LANG", "en-US"); - } - - // Test with a utility name that has embedded locales - // This should fall back to embedded English when filesystem files aren't found - let result = setup_localization("test"); - if let Err(e) = &result { - eprintln!("Setup localization failed: {e}"); - } - assert!(result.is_ok()); - - // Verify we can get messages (using embedded English) - let message = get_message("test-about"); - assert_eq!(message, "Check file types and compare values."); // Should use embedded English - }) - .join() - .unwrap(); - } - #[test] fn test_thread_local_isolation() { use std::thread; @@ -1163,7 +986,7 @@ invalid-syntax = This is { $missing 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(); + init_test_localization(&locale, &temp_path_main).unwrap(); let main_message = get_message("greeting"); assert_eq!(main_message, "Bonjour, le monde!"); }); @@ -1178,7 +1001,7 @@ invalid-syntax = This is { $missing // 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(); + init_test_localization(&en_locale, &temp_path).unwrap(); let thread_message_after_init = get_message("greeting"); assert_eq!(thread_message_after_init, "Hello, world!"); }); @@ -1201,7 +1024,7 @@ invalid-syntax = This is { $missing 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"); + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Test Japanese greeting @@ -1231,7 +1054,7 @@ invalid-syntax = This is { $missing 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"); + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Test Arabic greeting (RTL text) @@ -1242,7 +1065,6 @@ invalid-syntax = This is { $missing 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) @@ -1286,7 +1108,7 @@ invalid-syntax = This is { $missing 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"); + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Test Arabic greeting (RTL text) @@ -1327,7 +1149,7 @@ invalid-syntax = This is { $missing 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"); + let result = init_test_localization(&locale, temp_dir.path()); assert!(result.is_ok()); // Test Arabic message exists @@ -1349,7 +1171,7 @@ invalid-syntax = This is { $missing 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(); + 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 @@ -1365,27 +1187,12 @@ invalid-syntax = This is { $missing .unwrap(); } - #[test] - fn test_error_display() { - let io_error = LocalizationError::Io { - source: std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"), - path: PathBuf::from("/test/path.ftl"), - }; - let error_string = format!("{io_error}"); - assert!(error_string.contains("I/O error loading")); - assert!(error_string.contains("/test/path.ftl")); - - let bundle_error = LocalizationError::Bundle("Bundle creation failed".to_string()); - let bundle_string = format!("{bundle_error}"); - assert!(bundle_string.contains("Bundle error: Bundle creation failed")); - } - #[test] fn test_parse_resource_error_includes_snippet() { let temp_dir = create_test_locales_dir(); let locale = LanguageIdentifier::from_str("es-ES").unwrap(); - let result = create_bundle(&locale, temp_dir.path()); + let result = create_test_bundle(&locale, temp_dir.path()); assert!(result.is_err()); if let Err(LocalizationError::ParseResource { @@ -1403,6 +1210,182 @@ invalid-syntax = This is { $missing } } + #[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 + // which can have race conditions in multi-threaded test environments + + // Test parsing logic with UTF-8 encoding + let locale_with_encoding = "fr-FR.UTF-8"; + let parsed = locale_with_encoding.split('.').next().unwrap(); + let lang_id = LanguageIdentifier::from_str(parsed).unwrap(); + assert_eq!(lang_id.to_string(), "fr-FR"); + + // Test parsing logic without encoding + let locale_without_encoding = "es-ES"; + let lang_id = LanguageIdentifier::from_str(locale_without_encoding).unwrap(); + assert_eq!(lang_id.to_string(), "es-ES"); + + // Test that DEFAULT_LOCALE is valid + let default_lang_id = LanguageIdentifier::from_str(DEFAULT_LOCALE).unwrap(); + assert_eq!(default_lang_id.to_string(), "en-US"); + } + + #[test] + fn test_detect_system_locale_no_lang_env() { + // Save current LANG value + let original_lang = env::var("LANG").ok(); + + // Remove LANG environment variable + unsafe { + env::remove_var("LANG"); + } + + let result = detect_system_locale(); + assert!(result.is_ok()); + assert_eq!(result.unwrap().to_string(), "en-US"); + + // Restore original LANG value + if let Some(val) = original_lang { + unsafe { + env::set_var("LANG", val); + } + } else { + {} // Was already unset + } + } + + #[test] + fn test_setup_localization_success() { + std::thread::spawn(|| { + // Save current LANG value + let original_lang = env::var("LANG").ok(); + unsafe { + env::set_var("LANG", "en-US.UTF-8"); // Use English since we have embedded resources for "test" + } + + let result = setup_localization("test"); + assert!(result.is_ok()); + + // 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 { + unsafe { + env::set_var("LANG", val); + } + } else { + unsafe { + env::remove_var("LANG"); + } + } + }) + .join() + .unwrap(); + } + + #[test] + fn test_setup_localization_falls_back_to_english() { + std::thread::spawn(|| { + // 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, should fallback + } + + let result = setup_localization("test"); + assert!(result.is_ok()); + + // 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 { + unsafe { + env::set_var("LANG", val); + } + } else { + unsafe { + env::remove_var("LANG"); + } + } + }) + .join() + .unwrap(); + } + + #[test] + fn test_setup_localization_fallback_to_embedded() { + std::thread::spawn(|| { + // Force English locale for this test + unsafe { + std::env::set_var("LANG", "en-US"); + } + + // Test with a utility name that has embedded locales + // This should fall back to embedded English when filesystem files aren't found + let result = setup_localization("test"); + if let Err(e) = &result { + eprintln!("Setup localization failed: {e}"); + } + assert!(result.is_ok()); + + // Verify we can get messages (using embedded English) + let message = get_message("test-about"); + assert_eq!(message, "Check file types and compare values."); // Should use embedded English + }) + .join() + .unwrap(); + } + + #[test] + fn test_error_display() { + let io_error = LocalizationError::Io { + source: std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"), + path: PathBuf::from("/test/path.ftl"), + }; + let error_string = format!("{io_error}"); + assert!(error_string.contains("I/O error loading")); + assert!(error_string.contains("/test/path.ftl")); + + let bundle_error = LocalizationError::Bundle("Bundle creation failed".to_string()); + let bundle_string = format!("{bundle_error}"); + assert!(bundle_string.contains("Bundle error: Bundle creation failed")); + } + #[test] fn test_clap_localization_fallbacks() { std::thread::spawn(|| { @@ -1416,11 +1399,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 1023ce1dbf4fa34872cf6c775834795a236ce4fc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 10 Aug 2025 13:37:09 +0200 Subject: [PATCH 16/19] 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 358eda0d5fd..676f9ad3b3b 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 cbeab358d24..8bd69e0f6c2 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 581330a4190..d5d3aa04286 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -178,7 +178,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 { @@ -198,7 +198,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()); @@ -232,7 +232,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 3b9a506825034277b3c58c8a225140797168d097 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 11 Aug 2025 08:35:27 +0200 Subject: [PATCH 17/19] 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 | 4 + 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 | 17 +-- 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 | 142 +++++++++++++++++++ 101 files changed, 317 insertions(+), 40 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 3b7b4dc108c..8fe80b602aa 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 ec37886b3e7..43ca48a7612 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -106,6 +106,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 557d981d7e0..61ac6928995 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 f693643109c..f03c7c3b981 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 263d73475dc..25c2d099e5f 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 9df217fd0de..aca69d10372 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 01fa0422ca2..92ea6024e86 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 bd5872d5129..ceff36b59fe 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 3d8ea589b37..52db47e36c8 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 dd36cfefcba..01e3da55f0c 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 8ba393b5bc8..b77c549d75a 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 01c08324e47..41cded5f01d 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 4a13ad40f1c..fc18b97da75 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 1bc8cb0d6e2..57f0e61d18c 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 6a33aa74fec..1685844d9c2 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 0de57fe702d..0ddeefeb4e7 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 7db47355d4d..3a7a10f92de 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 0a58ce37ab6..87a459f2952 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 5f301edd7e7..aac8e57f3c4 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 12d94edf156..64a7662bb12 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 6c61ec7d8d8..6c03e340e7a 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -180,6 +180,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { + // Ensure localization is initialized + let _ = uucore::locale::setup_localization("echo"); + // Note: echo is different from the other utils in that it should **not** // have `infer_long_args(true)`, because, for example, `--ver` should be // printed as `--ver` and not show the version text. @@ -193,6 +196,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 9e42b1694bd..52e3bfa22ca 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 8d768522d4f..cd528316e7f 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 ba980357cf0..75ce8620d6f 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 1e7a176b672..8516b1eb626 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 a0a3f944f49..1083490313a 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 ed7ab7ff7dc..46f9d547f18 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 34bc6b65699..22fc6df2311 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 156555798cb..17624ffdefa 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 199a09bc762..0fc23e35f2d 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 c3f1c2c0cbb..818062ba939 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 efbe75475db..8a4f5efd87a 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 734cfdbed4c..acefbf73278 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 86fd86a176a..0852d99276f 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 bb366377914..66e80b90d62 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 a55c9afd555..dd132350661 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 aef9b45e3f3..e749e982b0b 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 8f18bf86b61..773d52d203d 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 d9a7afbd707..8b21bd09372 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 bb34f4b7462..1cf1872a8ff 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 5e9477c2980..d9674074e82 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); } }; @@ -1130,10 +1121,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { + // Ensure localization is initialized + let _ = uucore::locale::setup_localization("ls"); + Command::new(uucore::util_name()) .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 c82309087bb..fb82d963ce1 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 ae3466da64c..5cae85cc87b 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 b67e48e0cc8..d922c3b82e0 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 b1dbacf71be..23d22d361e0 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 473a7f859d0..8aa6b772920 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 b716b2e2621..0f65b5232a9 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -225,6 +225,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 7763327972e..5e9b555e8ef 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 f759b592610..890de1e868f 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 92dc510b5be..f20ec29afcf 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 7137ebda739..dd53e81b9eb 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 008f51558e3..e5cec7692f7 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 936e32c882f..e63a29a0051 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 807e4debf93..82a03f93b8d 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 9016a4878bb..c46ed39ae79 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 e494ba555bb..c2d303e2670 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 fef4ba5cc9b..75fea4be18e 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 ea93904a171..3ae77a77ed5 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 f5a7bc67c30..e48d253f59e 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 728c17d4c2c..b8352e06fa0 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 130b11096f6..fccf8c7b32f 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 b9aca641ca7..f634c970a03 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 d5d77cc2ccb..1b4ea602842 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 7030d06274a..9e3fa9b3991 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 25bc666b6fd..d2c2ac10590 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 3dfa5cc0c9e..f6fee91582c 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 6e343f5349a..5ab029b1a31 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 adc0db2bd7c..c158678a414 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 3e80e745e01..45c1e4ece75 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 87141e5713a..4cbaa6e6fe5 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 29346315b26..f9224f86586 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 2bcb5a3a019..70c47578289 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 0142f3dd5d5..4d5dde94950 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 e4ccd55e0a4..c2c6beab888 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 0391b32a5e9..69400c8553e 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 c45ffc94447..2366a59d376 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 4efaebd5c46..d5846359138 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 f2432018379..799e6557131 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 7e2a20bcb3a..ef53b394309 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 29dfef9d55a..5c6a120e7af 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 c1a680093a8..c553e1f8b26 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 755f593bf92..dded6bad63c 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 d61802b3c27..451b5b5604c 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 f697649bd98..9dcfe475529 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 788d23067e0..e19b26408e3 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 b11796cc074..0ad207b7ad8 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 a83478d2013..33822a47410 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 b893fa8b4d6..1ac6ed362ac 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 84dd4ca7c26..d23d8b7fc8b 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 f0b8924b2dd..e4a3a9964f2 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 1a2a3c7d963..cb9f9d198aa 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 4fc5492c6e4..14602cf3882 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 22a71530628..6e028fbd6ee 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 9439a3efaa9..ca63e527eae 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 8b3070989ec..fa111e4e46c 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 1a139f20b8d..2a91b4cf6a9 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 40398accf72..f65dfd0788e 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 5affbb6e3d1..028f6ba7e41 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 8c8dbb74e09..6b1419e629e 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 d5d3aa04286..466405bfffc 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -16,7 +16,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 @@ -72,21 +72,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); @@ -105,8 +90,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); @@ -203,7 +205,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 963e39d35eb..07ce267f26d 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1610,4 +1610,146 @@ fn test_argument_suggestion() { assert!(stderr_fr.contains("similaire")); assert!(stderr_fr.contains("--reverse")); } + +#[test] +fn test_clap_localization_unknown_argument() { + // Test unknown argument error in English + let result_en = new_ucmd!() + .env("LANG", "en_US.UTF-8") + .env("LC_ALL", "en_US.UTF-8") + .arg("--unknown-option") + .fails(); + + result_en.code_is(2); // sort uses exit code 2 for invalid options + let stderr_en = result_en.stderr_str(); + assert!(stderr_en.contains("error: unexpected argument '--unknown-option' found")); + assert!(stderr_en.contains("Usage:")); + assert!(stderr_en.contains("For more information, try '--help'.")); + + // Test unknown argument error in French + let result_fr = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--unknown-option") + .fails(); + + result_fr.code_is(2); + let stderr_fr = result_fr.stderr_str(); + assert!(stderr_fr.contains("erreur : argument inattendu '--unknown-option' trouvé")); + assert!(stderr_fr.contains("Utilisation:")); + assert!(stderr_fr.contains("Pour plus d'informations, essayez '--help'.")); +} + +#[test] +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") + .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("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--help") + .succeeds(); + + let stdout_fr = result_fr.stdout_str(); + assert!(stdout_fr.contains("Utilisation:")); + assert!(stdout_fr.contains("Options:")); +} + +#[test] +fn test_clap_localization_version() { + // Test version output (should exit with 0) + let result = new_ucmd!().arg("--version").succeeds(); + + let stdout = result.stdout_str(); + assert!(stdout.contains("sort")); +} + +#[test] +fn test_clap_localization_missing_required_argument() { + // Test missing required argument + let result_en = new_ucmd!() + .env("LANG", "en_US.UTF-8") + .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() { + // Test invalid value error + let result_en = new_ucmd!() + .env("LANG", "en_US.UTF-8") + .env("LC_ALL", "en_US.UTF-8") + .arg("-k") + .arg("invalid") + .fails(); + + let stderr_en = result_en.stderr_str(); + assert!(stderr_en.contains("sort: failed to parse key 'invalid'")); + + // Test in French + let result_fr = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("-k") + .arg("invalid") + .fails(); + + let stderr_fr = result_fr.stderr_str(); + assert!(stderr_fr.contains("sort: échec d'analyse de la clé 'invalid'")); +} + +#[test] +fn test_clap_localization_tip_for_value_with_dash() { + // Test tip for passing values that look like options + let result_en = new_ucmd!() + .env("LANG", "en_US.UTF-8") + .env("LC_ALL", "en_US.UTF-8") + .arg("--output") + .arg("--file-with-dash") + .fails(); + + let stderr_en = result_en.stderr_str(); + assert!(stderr_en.contains("tip:") || stderr_en.contains("conseil:")); + assert!(stderr_en.contains("-- --file-with-dash")); + + // Test in French + let result_fr = new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--output") + .arg("--file-with-dash") + .fails(); + + let stderr_fr = result_fr.stderr_str(); + // The tip should be preserved from clap + assert!(stderr_fr.contains("tip:") || stderr_fr.contains("conseil:")); + assert!(stderr_fr.contains("-- --file-with-dash")); +} + /* spell-checker: enable */ From ce941947b73d8a0b04f4f0205e93d9b24b954ec7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 11 Aug 2025 22:03:59 +0200 Subject: [PATCH 18/19] clap: also support translations for invalid values --- src/uucore/locales/en-US.ftl | 2 + src/uucore/locales/fr-FR.ftl | 2 + src/uucore/src/lib/mods/clap_localization.rs | 71 +++++++++++++++++++- tests/by-util/test_ls.rs | 28 ++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl index 676f9ad3b3b..f84fccc1d55 100644 --- a/src/uucore/locales/en-US.ftl +++ b/src/uucore/locales/en-US.ftl @@ -12,6 +12,8 @@ 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-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 8bd69e0f6c2..b9086062e7c 100644 --- a/src/uucore/locales/fr-FR.ftl +++ b/src/uucore/locales/fr-FR.ftl @@ -12,6 +12,8 @@ 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-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 466405bfffc..d40a54a6f62 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -6,10 +6,15 @@ //! 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 @@ -193,7 +198,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 beb1262a228..6adbfdbdfff 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -83,6 +83,34 @@ fn test_invalid_value_returns_1() { } } +/* spellchecker: disable */ +#[test] +fn test_localized_possible_values_english() { + // Test that "possible values" is properly localized in English + new_ucmd!() + .env("LANG", "en_US.UTF-8") + .env("LC_ALL", "en_US.UTF-8") + .arg("--color=invalid_test_value") + .fails() + .code_is(1) + .stderr_contains("error: invalid value 'invalid_test_value' for '--color") + .stderr_contains("[possible values:"); +} + +#[test] +fn test_localized_possible_values_french() { + // Test that "possible values" is properly localized in French + new_ucmd!() + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .arg("--color=invalid_test_value") + .fails() + .code_is(1) + .stderr_contains("erreur : valeur invalide 'invalid_test_value' pour '--color") + .stderr_contains("[valeurs possibles:"); +} +/* spellchecker: enable */ + #[test] fn test_invalid_value_returns_2() { // Invalid values to flags *sometimes* result in error code 2: From 026d378c0e4af9188f4a13da2ac0dc4ce4a6d5df Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 11 Aug 2025 23:26:13 +0200 Subject: [PATCH 19/19] fix some last clap errors mgmt --- src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/clap_localization.rs | 73 ++++++++++++++------ 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, 63 insertions(+), 42 deletions(-) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6b1419e629e..af0e039d063 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 d40a54a6f62..a8a783ad6c0 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -212,30 +212,61 @@ 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); - - // 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); + // 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 + eprintln!( + "error: a value is required for '{}' but none was supplied", + option + ); + eprintln!(); + eprintln!("For more information, try '--help'."); + std::process::exit(1); + } else { + // 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); + + // 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 + let error_msg = translate!( + "clap-error-invalid-value", + "error_word" => colored_error_word, + "value" => colored_value, + "option" => colored_option + ); + eprintln!("{error_msg}: {}", source); + } else { + // 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}"); + } + } else { + // 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 + // 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 8bdf19ed0d8..25225666868 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 2feb6ceff83..ad0d1c2b1ed 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 ba64152e7a3..86c358724ee 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 07ce267f26d..44c69486048 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1684,19 +1684,8 @@ fn test_clap_localization_missing_required_argument() { .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 a91ffc8ff1c..34b24d84dbd 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]