From 23f3551e1c9aba07f6fbe198cf20e03421b12c7e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Aug 2025 20:52:49 +0200 Subject: [PATCH] clap/locale: fix the colors for all programs (Closes: #8501) Co-authored-by: Daniel Hofstetter --- .vscode/cSpell.json | 3 +- .../workspace.wordlist.txt | 9 + Cargo.toml | 2 +- src/uu/arch/src/arch.rs | 3 +- src/uu/base32/src/base_common.rs | 9 +- src/uu/basename/src/basename.rs | 3 +- src/uu/basenc/src/basenc.rs | 5 +- src/uu/cat/src/cat.rs | 3 +- src/uu/chcon/src/chcon.rs | 31 +- src/uu/chgrp/src/chgrp.rs | 6 +- src/uu/chmod/src/chmod.rs | 8 +- src/uu/chroot/src/chroot.rs | 11 +- 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/dir/src/dir.rs | 2 +- src/uu/dircolors/src/dircolors.rs | 3 +- src/uu/dirname/src/dirname.rs | 6 +- src/uu/du/src/du.rs | 3 +- src/uu/env/src/env.rs | 29 +- src/uu/expand/src/expand.rs | 78 +- src/uu/factor/src/factor.rs | 3 +- 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 | 3 +- src/uu/hostid/src/hostid.rs | 3 +- src/uu/hostname/src/hostname.rs | 3 +- src/uu/id/src/id.rs | 6 +- 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 | 18 +- src/uu/logname/src/logname.rs | 3 +- src/uu/ls/src/ls.rs | 1301 ++++++++--------- src/uu/mkdir/src/mkdir.rs | 6 +- src/uu/mkfifo/src/mkfifo.rs | 3 +- src/uu/mknod/src/mknod.rs | 3 +- src/uu/mktemp/src/mktemp.rs | 2 +- src/uu/more/src/more.rs | 3 +- src/uu/mv/src/mv.rs | 5 +- src/uu/nice/src/nice.rs | 5 +- src/uu/nl/src/nl.rs | 4 +- src/uu/nohup/src/nohup.rs | 5 +- 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/pinky.rs | 6 +- src/uu/pinky/src/platform/openbsd.rs | 3 +- src/uu/pinky/src/platform/unix.rs | 6 +- src/uu/pr/src/pr.rs | 3 +- src/uu/printenv/src/printenv.rs | 11 +- 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/realpath/src/realpath.rs | 4 +- src/uu/rm/src/rm.rs | 3 +- src/uu/rmdir/src/rmdir.rs | 3 +- src/uu/runcon/src/runcon.rs | 17 +- src/uu/seq/src/seq.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 | 503 ++++--- src/uu/split/src/split.rs | 3 +- src/uu/stat/src/stat.rs | 6 +- src/uu/stdbuf/src/stdbuf.rs | 5 +- 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/test/src/test.rs | 6 +- src/uu/timeout/src/timeout.rs | 5 +- src/uu/touch/src/touch.rs | 3 +- src/uu/tr/src/tr.rs | 3 +- src/uu/truncate/src/truncate.rs | 19 +- src/uu/tsort/src/tsort.rs | 3 +- src/uu/tty/src/tty.rs | 26 +- src/uu/uname/src/uname.rs | 3 +- src/uu/unexpand/src/unexpand.rs | 2 +- src/uu/uniq/src/uniq.rs | 21 +- src/uu/unlink/src/unlink.rs | 3 +- src/uu/uptime/src/uptime.rs | 3 +- src/uu/users/src/users.rs | 6 +- src/uu/vdir/src/vdir.rs | 2 +- src/uu/wc/src/wc.rs | 3 +- src/uu/who/src/platform/openbsd.rs | 3 +- src/uu/who/src/platform/unix.rs | 6 +- src/uu/who/src/who.rs | 6 +- src/uu/whoami/src/whoami.rs | 3 +- src/uu/yes/src/yes.rs | 3 +- src/uucore/locales/en-US.ftl | 2 + src/uucore/locales/fr-FR.ftl | 2 + src/uucore/src/lib/features/perms.rs | 2 +- src/uucore/src/lib/lib.rs | 50 +- src/uucore/src/lib/mods/clap_localization.rs | 1060 +++++++------- tests/by-util/test_chmod.rs | 32 + tests/by-util/test_comm.rs | 9 +- tests/by-util/test_dir.rs | 5 + tests/by-util/test_env.rs | 17 + tests/by-util/test_runcon.rs | 9 +- tests/by-util/test_sort.rs | 183 +++ tests/by-util/test_uniq.rs | 43 +- tests/by-util/test_vdir.rs | 5 + tests/test_localization_and_colors.rs | 333 +++++ util/gnu-patches/error_msg_uniq.diff | 58 + util/gnu-patches/series | 1 + util/gnu-patches/tests_comm.pl.patch | 12 +- util/gnu-patches/tests_env_env-S.pl.patch | 4 +- util/gnu-patches/tests_tsort.patch | 6 +- 121 files changed, 2426 insertions(+), 1801 deletions(-) create mode 100644 tests/test_localization_and_colors.rs create mode 100644 util/gnu-patches/error_msg_uniq.diff diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index bd58ee1d230..c4ad338eb71 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -29,7 +29,8 @@ "**/*.svg", "src/uu/*/locales/*.ftl", "src/uucore/locales/*.ftl", - ".devcontainer/**" + ".devcontainer/**", + "util/gnu-patches/**", ], "enableGlobDot": true, diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 2c8bada0930..6fd3dadcea3 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -349,3 +349,12 @@ getcwd # * other weblate algs + +# translation tests +CLICOLOR +erreur +Utilisation +merror +merreur +verbo +inattendu diff --git a/Cargo.toml b/Cargo.toml index 1ad3c2c0eb0..7b3ce20be8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -301,7 +301,7 @@ chrono = { version = "0.4.41", default-features = false, features = [ "alloc", "clock", ] } -clap = { version = "4.5", features = ["wrap_help", "cargo"] } +clap = { version = "4.5", features = ["wrap_help", "cargo", "color"] } clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 8fe80b602aa..7d1867763f9 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -6,13 +6,12 @@ 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().get_matches_from_localized(args); + uucore::clap_localization::handle_clap_result(uu_app(), 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 5c5dd983d8a..ee2310ce1d2 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -10,7 +10,6 @@ use std::ffi::OsString; 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, EncodingWrapper, Format, SupportsFastDecodeAndEncode, Z85Wrapper, @@ -101,17 +100,17 @@ pub fn parse_base_cmd_args( usage: &str, ) -> UResult { let command = base_app(about, usage); - let matches = command.get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(command, args)?; Config::from(&matches) } pub fn base_app(about: &'static str, usage: &str) -> Command { - Command::new(uucore::util_name()) + 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(usage)) - .infer_long_args(true) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) // Format arguments. .arg( Arg::new(options::DECODE) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 61ac6928995..cf2f2168919 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -15,7 +15,6 @@ use uucore::error::{UResult, UUsageError}; use uucore::format_usage; use uucore::line_ending::LineEnding; -use uucore::LocalizedCommand; use uucore::translate; pub mod options { @@ -30,7 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // // Argument parsing // - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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..0a4ab8ab2a7 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -7,7 +7,6 @@ use clap::{Arg, ArgAction, Command}; use uu_base32::base_common::{self, BASE_CMD_PARSE_ERROR, Config}; -use uucore::error::UClapError; use uucore::translate; use uucore::{ encoding::Format, @@ -64,9 +63,7 @@ pub fn uu_app() -> Command { } fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> { - let matches = uu_app() - .try_get_matches_from(args.collect_lossy()) - .with_exit_code(1)?; + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args.collect_lossy())?; let encodings = get_encodings(); let format = encodings diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 51498aa9c8b..6d19c0572c6 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -23,7 +23,6 @@ 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"))] @@ -232,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 25c2d099e5f..6770088e115 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -7,12 +7,11 @@ #![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}; -use clap::{Arg, ArgAction, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command}; use selinux::{OpaqueSecurityContext, SecurityContext}; use std::borrow::Cow; @@ -58,9 +57,9 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let config = uu_app(); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; - let options = match parse_command_line(config, args) { + let options = match parse_command_line(&matches) { Ok(r) => r, Err(r) => { if let Error::CommandLine(r) = r { @@ -155,20 +154,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) - .disable_help_flag(true) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .args_override_self(true) - .arg( - Arg::new(options::HELP) - .long(options::HELP) - .help(translate!("chcon-help-help")) - .action(ArgAction::Help), - ) + .disable_help_flag(true) .arg( Arg::new(options::dereference::DEREFERENCE) .long(options::dereference::DEREFERENCE) @@ -183,6 +176,12 @@ pub fn uu_app() -> Command { .help(translate!("chcon-help-no-dereference")) .action(ArgAction::SetTrue), ) + .arg( + Arg::new("help") + .long("help") + .help(translate!("help")) + .action(ArgAction::Help), + ) .arg( Arg::new(options::preserve_root::PRESERVE_ROOT) .long(options::preserve_root::PRESERVE_ROOT) @@ -304,9 +303,7 @@ struct Options { files: Vec, } -fn parse_command_line(config: Command, args: impl uucore::Args) -> Result { - let matches = config.get_matches_from_localized(args); - +fn parse_command_line(matches: &ArgMatches) -> Result { let verbose = matches.get_flag(options::VERBOSE); let (recursive_mode, affect_symlink_referent) = if matches.get_flag(options::RECURSIVE) { diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 07859d07d05..f681da37eed 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -98,12 +98,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .disable_help_flag(true) .arg( Arg::new(options::HELP) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d8152d8bdc6..9a8850fa3ea 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -11,7 +11,6 @@ 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; @@ -111,9 +110,7 @@ fn extract_negative_modes(mut args: impl uucore::Args) -> (Option, Vec UResult<()> { let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name - let matches = uu_app() - .after_help(translate!("chmod-after-help")) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let changes = matches.get_flag(options::CHANGES); let quiet = matches.get_flag(options::QUIET); @@ -177,13 +174,14 @@ 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"))) + .help_template(uucore::localized_help_template(uucore::util_name())) .args_override_self(true) .infer_long_args(true) .no_binary_name(true) .disable_help_flag(true) + .after_help(translate!("chmod-after-help")) .arg( Arg::new(options::HELP) .long(options::HELP) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 52db47e36c8..369afd0af76 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -14,7 +14,7 @@ use std::os::unix::prelude::OsStrExt; use std::path::{Path, PathBuf}; use std::process; use uucore::entries::{Locate, Passwd, grp2gid, usr2uid}; -use uucore::error::{UClapError, UResult, UUsageError, set_exit_code}; +use uucore::error::{UResult, UUsageError, set_exit_code}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{format_usage, show}; @@ -155,7 +155,8 @@ impl Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; + let matches = + uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 125)?; let default_shell: &'static str = "/bin/sh"; let default_option: &'static str = "-i"; @@ -234,13 +235,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) - .trailing_var_arg(true) + .trailing_var_arg(true); + uucore::clap_localization::configure_localized_command(cmd) .arg( Arg::new(options::NEWROOT) .value_hint(clap::ValueHint::DirPath) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 4174a6bd118..7d595e625ac 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -20,7 +20,6 @@ use uucore::checksum::{ }; use uucore::translate; -use uucore::LocalizedCommand; use uucore::{ encoding, error::{FromIo, UResult, USimpleError}, @@ -235,7 +234,7 @@ fn handle_tag_text_binary_flags>( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 e383d4d6fa4..2eb872bfb1a 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -10,7 +10,6 @@ use std::ffi::OsString; use std::fs::{File, metadata}; use std::io::{self, BufRead, BufReader, Read, Stdin, stdin}; use std::path::Path; -use uucore::LocalizedCommand; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::format_usage; use uucore::fs::paths_refer_to_same_file; @@ -283,7 +282,7 @@ fn open_file(name: &OsString, line_ending: LineEnding) -> io::Result #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 0c3a6ca0c5e..57bd19e7be6 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -15,7 +15,6 @@ use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::os::unix::net::UnixListener; 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; @@ -782,7 +781,7 @@ pub fn uu_app() -> Command { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let options = Options::from_matches(&matches)?; diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index a3e10e2b061..a99768605c0 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -26,7 +26,6 @@ mod split_name; use crate::csplit_error::CsplitError; use crate::split_name::SplitName; -use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -606,7 +605,7 @@ where #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 aea44c98875..60992948673 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -18,7 +18,6 @@ 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}; @@ -483,7 +482,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }) .collect(); - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 ed2c2241f34..ea76a631e02 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -21,7 +21,6 @@ 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 @@ -114,7 +113,7 @@ impl From<&str> for Rfc3339Format { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 0ddeefeb4e7..2f4db984a29 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -55,7 +55,6 @@ use nix::{ errno::Errno, fcntl::{PosixFadviseAdvice, posix_fadvise}, }; -use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; #[cfg(unix)] @@ -1416,7 +1415,7 @@ fn is_fifo(filename: &str) -> bool { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 3a7a10f92de..c81d260e412 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -11,7 +11,6 @@ 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}; @@ -407,7 +406,7 @@ impl UError for DfError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; #[cfg(windows)] { diff --git a/src/uu/dir/src/dir.rs b/src/uu/dir/src/dir.rs index 83cff412d56..099ae8bf9af 100644 --- a/src/uu/dir/src/dir.rs +++ b/src/uu/dir/src/dir.rs @@ -14,7 +14,7 @@ use uucore::quoting_style::QuotingStyle; pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command = uu_app(); - let matches = command.get_matches_from(args); + let matches = uucore::clap_localization::handle_clap_result_with_exit_code(command, args, 2)?; let mut default_quoting_style = false; let mut default_format_style = false; diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index c00f5d210f0..857050bde7a 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -19,7 +19,6 @@ 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 { @@ -122,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().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 2782c7fb3a9..fe952bb7bde 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -6,7 +6,6 @@ use clap::{Arg, ArgAction, Command}; use std::ffi::OsString; use std::path::Path; -use uucore::LocalizedCommand; use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; use uucore::format_usage; @@ -21,9 +20,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(translate!("dirname-after-help")) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); @@ -69,6 +66,7 @@ pub fn uu_app() -> Command { .override_usage(format_usage(&translate!("dirname-usage"))) .args_override_self(true) .infer_long_args(true) + .after_help(translate!("dirname-after-help")) .arg( Arg::new(options::ZERO) .long(options::ZERO) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index d4e20054d47..dba96aa6343 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -27,7 +27,6 @@ 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; @@ -583,7 +582,7 @@ fn read_files_from(file_name: &OsStr) -> Result, std::io::Error> { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let summarize = matches.get_flag(options::SUMMARIZE); diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 52e3bfa22ca..c41f6318fd2 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -492,24 +492,27 @@ impl EnvAppData { let original_args: Vec = original_args.collect(); let args = self.process_all_string_arguments(&original_args)?; let app = uu_app(); - let matches = app - .try_get_matches_from(args) - .map_err(|e| -> Box { + let matches = match app.try_get_matches_from(args) { + Ok(matches) => matches, + Err(e) => { match e.kind() { clap::error::ErrorKind::DisplayHelp - | clap::error::ErrorKind::DisplayVersion => e.into(), + | clap::error::ErrorKind::DisplayVersion => return Err(e.into()), _ => { - // extent any real issue with parameter parsing by the ERROR_MSG_S_SHEBANG - let s = format!("{e}"); - if !s.is_empty() { - let s = s.trim_end(); - uucore::show_error!("{s}"); - } - uucore::show_error!("{}", translate!("env-error-use-s-shebang")); - ExitCode::new(125) + // Use ErrorFormatter directly to handle error with shebang message callback + let formatter = + uucore::clap_localization::ErrorFormatter::new(uucore::util_name()); + formatter.print_error_and_exit_with_callback(&e, 125, || { + eprintln!( + "{}: {}", + uucore::util_name(), + translate!("env-error-use-s-shebang") + ); + }); } } - })?; + } + }; Ok((original_args, matches)) } diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 5b0b8f4e7ea..0891a2de452 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -243,49 +243,51 @@ fn expand_shortcuts(args: Vec) -> Vec { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(expand_shortcuts(args.collect()))?; + let matches = + uucore::clap_localization::handle_clap_result(uu_app(), expand_shortcuts(args.collect()))?; expand(&Options::new(&matches)?) } 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"))) - .infer_long_args(true) - .args_override_self(true) - .arg( - Arg::new(options::INITIAL) - .long(options::INITIAL) - .short('i') - .help(translate!("expand-help-initial")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::TABS) - .long(options::TABS) - .short('t') - .value_name("N, LIST") - .action(ArgAction::Append) - .help(translate!("expand-help-tabs")), - ) - .arg( - Arg::new(options::NO_UTF8) - .long(options::NO_UTF8) - .short('U') - .help(translate!("expand-help-no-utf8")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::FILES) - .action(ArgAction::Append) - .hide(true) - .value_hint(clap::ValueHint::FilePath) - .value_parser(clap::value_parser!(OsString)), - ) + uucore::clap_localization::configure_localized_command( + Command::new(uucore::util_name()) + .version(uucore::crate_version!()) + .about(translate!("expand-about")) + .after_help(LONG_HELP) + .override_usage(format_usage(&translate!("expand-usage"))), + ) + .infer_long_args(true) + .args_override_self(true) + .arg( + Arg::new(options::INITIAL) + .long(options::INITIAL) + .short('i') + .help(translate!("expand-help-initial")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::TABS) + .long(options::TABS) + .short('t') + .value_name("N, LIST") + .action(ArgAction::Append) + .help(translate!("expand-help-tabs")), + ) + .arg( + Arg::new(options::NO_UTF8) + .long(options::NO_UTF8) + .short('U') + .help(translate!("expand-help-no-utf8")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::FILES) + .action(ArgAction::Append) + .hide(true) + .value_hint(clap::ValueHint::FilePath) + .value_parser(clap::value_parser!(OsString)), + ) } fn open(path: &OsString) -> UResult>> { diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 8516b1eb626..f3fe92786d3 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -12,7 +12,6 @@ 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; @@ -80,7 +79,7 @@ fn write_result( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 4919fb43577..882c0834a68 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -14,7 +14,6 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::translate; -use uucore::LocalizedCommand; use uucore::format_usage; use linebreak::break_lines; @@ -341,7 +340,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let matches = uu_app().get_matches_from_localized(&args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), &args)?; let files = extract_files(&matches)?; diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 22fc6df2311..b439f20a94d 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -9,7 +9,6 @@ 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; @@ -32,7 +31,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().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 17624ffdefa..772e23cf5f0 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -14,7 +14,6 @@ use uucore::{ }; use clap::{Arg, ArgAction, Command}; -use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -48,7 +47,7 @@ fn infallible_gid2grp(gid: &u32) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 0fc23e35f2d..4bdb1fe38ba 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -15,7 +15,6 @@ 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; @@ -182,7 +181,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.get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(command, 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 904c59198ee..bce39e4892e 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -14,7 +14,6 @@ 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; @@ -555,7 +554,7 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args: Vec<_> = arg_iterate(args)?.collect(); - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let options = HeadOptions::get_from(&matches).map_err(HeadError::MatchOption)?; uu_head(&options) } diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 8a4f5efd87a..8c139c83121 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -9,12 +9,11 @@ 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().get_matches_from_localized(args); + uucore::clap_localization::handle_clap_result(uu_app(), args)?; hostid(); Ok(()) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index acefbf73278..89caf001413 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -12,7 +12,6 @@ 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; @@ -61,7 +60,7 @@ mod wsa { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 0852d99276f..7c1e1c12c5a 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -44,7 +44,6 @@ 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}; @@ -120,9 +119,7 @@ struct State { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(translate!("id-after-help")) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let users: Vec = matches .get_many::(options::ARG_USERS) @@ -355,6 +352,7 @@ pub fn uu_app() -> Command { .override_usage(format_usage(&translate!("id-usage"))) .infer_long_args(true) .args_override_self(true) + .after_help(translate!("id-after-help")) .arg( Arg::new(options::OPT_AUDIT) .short('A') diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 903f3928d58..02b807ee8d6 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -17,7 +17,6 @@ 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; @@ -167,7 +166,7 @@ static ARG_FILES: &str = "files"; /// #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 4dc1dd146ec..58d83fc4083 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -16,7 +16,6 @@ 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; @@ -823,7 +822,7 @@ fn parse_settings(matches: &clap::ArgMatches) -> UResult { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let settings = parse_settings(&matches)?; diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index e749e982b0b..809d59b7d88 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -13,7 +13,6 @@ 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}; @@ -41,7 +40,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().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 773d52d203d..d19e84be498 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -8,7 +8,6 @@ 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; @@ -20,7 +19,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 0557dd4f319..ba38236fa45 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -23,7 +23,6 @@ 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}; @@ -89,15 +88,7 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let after_help = format!( - "{}\n\n{}", - translate!("ln-after-help"), - backup_control::BACKUP_CONTROL_LONG_HELP - ); - - let matches = uu_app() - .after_help(after_help) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; /* the list of files */ @@ -142,12 +133,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { + let after_help = format!( + "{}\n\n{}", + translate!("ln-after-help"), + backup_control::BACKUP_CONTROL_LONG_HELP + ); + 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) + .after_help(after_help) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) /*.arg( diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 1cf1872a8ff..3dd99549500 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -7,7 +7,6 @@ use clap::Command; use std::ffi::CStr; -use uucore::LocalizedCommand; use uucore::translate; use uucore::{error::UResult, show_error}; @@ -24,7 +23,7 @@ fn get_userlogin() -> Option { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _ = uu_app().get_matches_from_localized(args); + let _ = uucore::clap_localization::handle_clap_result(uu_app(), 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 b25924c3fd9..ba7f26f1aad 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1103,13 +1103,7 @@ impl Config { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = match uu_app().try_get_matches_from(args) { - Ok(matches) => matches, - Err(e) => { - // Use localization handler for all clap errors - uucore::clap_localization::handle_clap_error_with_exit_code(e, "ls", 2); - } - }; + let matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 2)?; let config = Config::from(&matches)?; @@ -1121,652 +1115,653 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(uucore::crate_version!()) - .override_usage(format_usage(&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) - .arg( - Arg::new(options::HELP) - .long(options::HELP) - .help(translate!("ls-help-print-help")) - .action(ArgAction::Help), - ) - // Format arguments - .arg( - Arg::new(options::FORMAT) - .long(options::FORMAT) - .help(translate!("ls-help-set-display-format")) - .value_parser(ShortcutValueParser::new([ - "long", - "verbose", - "single-column", - "columns", - "vertical", - "across", - "horizontal", - "commas", - ])) - .hide_possible_values(true) - .require_equals(true) - .overrides_with_all([ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - options::DIRED, - ]), - ) - .arg( - Arg::new(options::format::COLUMNS) - .short('C') - .help(translate!("ls-help-display-files-columns")) - .overrides_with_all([ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::format::LONG) - .short('l') - .long(options::format::LONG) - .help(translate!("ls-help-display-detailed-info")) - .overrides_with_all([ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::format::ACROSS) - .short('x') - .help(translate!("ls-help-list-entries-rows")) - .overrides_with_all([ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::format::TAB_SIZE) - .short('T') - .long(options::format::TAB_SIZE) - .env("TABSIZE") - .value_name("COLS") - .help(translate!("ls-help-assume-tab-stops")), - ) - .arg( - Arg::new(options::format::COMMAS) - .short('m') - .help(translate!("ls-help-list-entries-commas")) - .overrides_with_all([ - options::FORMAT, - options::format::COLUMNS, - options::format::LONG, - options::format::ACROSS, - options::format::COLUMNS, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::ZERO) - .long(options::ZERO) - .overrides_with(options::ZERO) - .help(translate!("ls-help-list-entries-nul")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::DIRED) - .long(options::DIRED) - .short('D') - .help(translate!("ls-help-generate-dired-output")) - .action(ArgAction::SetTrue) - .overrides_with(options::HYPERLINK), - ) - .arg( - Arg::new(options::HYPERLINK) - .long(options::HYPERLINK) - .help(translate!("ls-help-hyperlink-filenames")) - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("always").alias("yes").alias("force"), - PossibleValue::new("auto").alias("tty").alias("if-tty"), - PossibleValue::new("never").alias("no").alias("none"), - ])) - .require_equals(true) - .num_args(0..=1) - .default_missing_value("always") - .default_value("never") - .value_name("WHEN") - .overrides_with(options::DIRED), - ) - // The next four arguments do not override with the other format - // options, see the comment in Config::from for the reason. - // Ideally, they would use Arg::override_with, with their own name - // but that doesn't seem to work in all cases. Example: - // ls -1g1 - // even though `ls -11` and `ls -1 -g -1` work. - .arg( - Arg::new(options::format::ONE_LINE) - .short('1') - .help(translate!("ls-help-list-one-file-per-line")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::format::LONG_NO_GROUP) - .short('o') - .help(translate!("ls-help-long-format-no-group")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::format::LONG_NO_OWNER) - .short('g') - .help(translate!("ls-help-long-no-owner")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::format::LONG_NUMERIC_UID_GID) - .short('n') - .long(options::format::LONG_NUMERIC_UID_GID) - .help(translate!("ls-help-long-numeric-uid-gid")) - .action(ArgAction::SetTrue), - ) - // Quoting style - .arg( - Arg::new(QUOTING_STYLE) - .long(QUOTING_STYLE) - .help(translate!("ls-help-set-quoting-style")) - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("literal"), - PossibleValue::new("shell"), - PossibleValue::new("shell-escape"), - PossibleValue::new("shell-always"), - PossibleValue::new("shell-escape-always"), - PossibleValue::new("c").alias("c-maybe"), - PossibleValue::new("escape"), - ])) - .overrides_with_all([ - QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]), - ) - .arg( - Arg::new(options::quoting::LITERAL) - .short('N') - .long(options::quoting::LITERAL) - .alias("l") - .help(translate!("ls-help-literal-quoting-style")) - .overrides_with_all([ - QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::quoting::ESCAPE) - .short('b') - .long(options::quoting::ESCAPE) - .help(translate!("ls-help-escape-quoting-style")) - .overrides_with_all([ - QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::quoting::C) - .short('Q') - .long(options::quoting::C) - .help(translate!("ls-help-c-quoting-style")) - .overrides_with_all([ - QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]) - .action(ArgAction::SetTrue), - ) - // Control characters - .arg( - Arg::new(options::HIDE_CONTROL_CHARS) - .short('q') - .long(options::HIDE_CONTROL_CHARS) - .help(translate!("ls-help-replace-control-chars")) - .overrides_with_all([options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::SHOW_CONTROL_CHARS) - .long(options::SHOW_CONTROL_CHARS) - .help(translate!("ls-help-show-control-chars")) - .overrides_with_all([options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]) - .action(ArgAction::SetTrue), - ) - // Time arguments - .arg( - Arg::new(options::TIME) - .long(options::TIME) - .help(translate!("ls-help-show-time-field")) - .value_name("field") - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("atime").alias("access").alias("use"), - PossibleValue::new("ctime").alias("status"), - PossibleValue::new("mtime").alias("modification"), - PossibleValue::new("birth").alias("creation"), - ])) - .hide_possible_values(true) - .require_equals(true) - .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]), - ) - .arg( - Arg::new(options::time::CHANGE) - .short('c') - .help(translate!("ls-help-time-change")) - .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::time::ACCESS) - .short('u') - .help(translate!("ls-help-time-access")) - .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]) - .action(ArgAction::SetTrue), - ) - // Hide and ignore - .arg( - Arg::new(options::HIDE) - .long(options::HIDE) - .action(ArgAction::Append) - .value_name("PATTERN") - .help(translate!("ls-help-hide-pattern")), - ) - .arg( - Arg::new(options::IGNORE) - .short('I') - .long(options::IGNORE) - .action(ArgAction::Append) - .value_name("PATTERN") - .help(translate!("ls-help-ignore-pattern")), - ) - .arg( - Arg::new(options::IGNORE_BACKUPS) - .short('B') - .long(options::IGNORE_BACKUPS) - .help(translate!("ls-help-ignore-backups")) - .action(ArgAction::SetTrue), - ) - // Sort arguments - .arg( - Arg::new(options::SORT) - .long(options::SORT) - .help(translate!("ls-help-sort-by-field")) - .value_name("field") - .value_parser(ShortcutValueParser::new([ - "name", - "none", - "time", - "size", - "version", - "extension", - "width", - ])) - .require_equals(true) - .overrides_with_all([ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]), - ) - .arg( - Arg::new(options::sort::SIZE) - .short('S') - .help(translate!("ls-help-sort-by-size")) - .overrides_with_all([ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::sort::TIME) - .short('t') - .help(translate!("ls-help-sort-by-time")) - .overrides_with_all([ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::sort::VERSION) - .short('v') - .help(translate!("ls-help-sort-by-version")) - .overrides_with_all([ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::sort::EXTENSION) - .short('X') - .help(translate!("ls-help-sort-by-extension")) - .overrides_with_all([ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::sort::NONE) - .short('U') - .help(translate!("ls-help-sort-none")) - .overrides_with_all([ - options::SORT, - options::sort::SIZE, - options::sort::TIME, - options::sort::NONE, - options::sort::VERSION, - options::sort::EXTENSION, - ]) - .action(ArgAction::SetTrue), - ) - // Dereferencing - .arg( - Arg::new(options::dereference::ALL) - .short('L') - .long(options::dereference::ALL) - .help(translate!("ls-help-dereference-all")) - .overrides_with_all([ - options::dereference::ALL, - options::dereference::DIR_ARGS, - options::dereference::ARGS, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::dereference::DIR_ARGS) - .long(options::dereference::DIR_ARGS) - .help(translate!("ls-help-dereference-dir-args")) - .overrides_with_all([ - options::dereference::ALL, - options::dereference::DIR_ARGS, - options::dereference::ARGS, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::dereference::ARGS) - .short('H') - .long(options::dereference::ARGS) - .help(translate!("ls-help-dereference-args")) - .overrides_with_all([ - options::dereference::ALL, - options::dereference::DIR_ARGS, - options::dereference::ARGS, - ]) - .action(ArgAction::SetTrue), - ) - // Long format options - .arg( - Arg::new(options::NO_GROUP) - .long(options::NO_GROUP) - .short('G') - .help(translate!("ls-help-no-group")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::AUTHOR) - .long(options::AUTHOR) - .help(translate!("ls-help-author")) - .action(ArgAction::SetTrue), - ) - // Other Flags - .arg( - Arg::new(options::files::ALL) - .short('a') - .long(options::files::ALL) - // Overrides -A (as the order matters) - .overrides_with_all([options::files::ALL, options::files::ALMOST_ALL]) - .help(translate!("ls-help-all-files")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::files::ALMOST_ALL) - .short('A') - .long(options::files::ALMOST_ALL) - // Overrides -a (as the order matters) - .overrides_with_all([options::files::ALL, options::files::ALMOST_ALL]) - .help(translate!("ls-help-almost-all")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::DIRECTORY) - .short('d') - .long(options::DIRECTORY) - .help(translate!("ls-help-directory")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::size::HUMAN_READABLE) - .short('h') - .long(options::size::HUMAN_READABLE) - .help(translate!("ls-help-human-readable")) - .overrides_with_all([options::size::BLOCK_SIZE, options::size::SI]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::size::KIBIBYTES) - .short('k') - .long(options::size::KIBIBYTES) - .help(translate!("ls-help-kibibytes")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::size::SI) - .long(options::size::SI) - .help(translate!("ls-help-si")) - .overrides_with_all([options::size::BLOCK_SIZE, options::size::HUMAN_READABLE]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::size::BLOCK_SIZE) - .long(options::size::BLOCK_SIZE) - .require_equals(true) - .value_name("BLOCK_SIZE") - .help(translate!("ls-help-block-size")) - .overrides_with_all([options::size::SI, options::size::HUMAN_READABLE]), - ) - .arg( - Arg::new(options::INODE) - .short('i') - .long(options::INODE) - .help(translate!("ls-help-print-inode")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::REVERSE) - .short('r') - .long(options::REVERSE) - .help(translate!("ls-help-reverse-sort")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::RECURSIVE) - .short('R') - .long(options::RECURSIVE) - .help(translate!("ls-help-recursive")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::WIDTH) - .long(options::WIDTH) - .short('w') - .help(translate!("ls-help-terminal-width")) - .value_name("COLS"), - ) - .arg( - Arg::new(options::size::ALLOCATION_SIZE) - .short('s') - .long(options::size::ALLOCATION_SIZE) - .help(translate!("ls-help-allocation-size")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::COLOR) - .long(options::COLOR) - .help(translate!("ls-help-color-output")) - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("always").alias("yes").alias("force"), - PossibleValue::new("auto").alias("tty").alias("if-tty"), - PossibleValue::new("never").alias("no").alias("none"), - ])) - .require_equals(true) - .num_args(0..=1), - ) - .arg( - Arg::new(options::INDICATOR_STYLE) - .long(options::INDICATOR_STYLE) - .help(translate!("ls-help-indicator-style")) - .value_parser(ShortcutValueParser::new([ - "none", - "slash", - "file-type", - "classify", - ])) - .overrides_with_all([ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ]), - ) - .arg( - // The --classify flag can take an optional when argument to - // control its behavior from version 9 of GNU coreutils. - // There is currently an inconsistency where GNU coreutils allows only - // the long form of the flag to take the argument while we allow it - // for both the long and short form of the flag. - Arg::new(options::indicator_style::CLASSIFY) - .short('F') - .long(options::indicator_style::CLASSIFY) - .help(translate!("ls-help-classify")) - .value_name("when") - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("always").alias("yes").alias("force"), - PossibleValue::new("auto").alias("tty").alias("if-tty"), - PossibleValue::new("never").alias("no").alias("none"), - ])) - .default_missing_value("always") - .require_equals(true) - .num_args(0..=1) - .overrides_with_all([ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ]), - ) - .arg( - Arg::new(options::indicator_style::FILE_TYPE) - .long(options::indicator_style::FILE_TYPE) - .help(translate!("ls-help-file-type")) - .overrides_with_all([ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::indicator_style::SLASH) - .short('p') - .help(translate!("ls-help-slash-directories")) - .overrides_with_all([ - options::indicator_style::FILE_TYPE, - options::indicator_style::SLASH, - options::indicator_style::CLASSIFY, - options::INDICATOR_STYLE, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - //This still needs support for posix-* - Arg::new(options::TIME_STYLE) - .long(options::TIME_STYLE) - .help(translate!("ls-help-time-style")) - .value_name("TIME_STYLE") - .env("TIME_STYLE") - .value_parser(NonEmptyStringValueParser::new()) - .overrides_with_all([options::TIME_STYLE]), - ) - .arg( - Arg::new(options::FULL_TIME) - .long(options::FULL_TIME) - .overrides_with(options::FULL_TIME) - .help(translate!("ls-help-full-time")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::CONTEXT) - .short('Z') - .long(options::CONTEXT) - .help(translate!("ls-help-context")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::GROUP_DIRECTORIES_FIRST) - .long(options::GROUP_DIRECTORIES_FIRST) - .help(translate!("ls-help-group-directories-first")) - .action(ArgAction::SetTrue), - ) - // Positional arguments - .arg( - Arg::new(options::PATHS) - .action(ArgAction::Append) - .value_hint(clap::ValueHint::AnyPath) - .value_parser(ValueParser::os_string()), - ) - .after_help(translate!("ls-after-help")) + uucore::clap_localization::configure_localized_command( + Command::new(uucore::util_name()) + .version(uucore::crate_version!()) + .override_usage(format_usage(&translate!("ls-usage"))) + .about(translate!("ls-about")), + ) + .infer_long_args(true) + .disable_help_flag(true) + .args_override_self(true) + .arg( + Arg::new(options::HELP) + .long(options::HELP) + .help(translate!("ls-help-print-help")) + .action(ArgAction::Help), + ) + // Format arguments + .arg( + Arg::new(options::FORMAT) + .long(options::FORMAT) + .help(translate!("ls-help-set-display-format")) + .value_parser(ShortcutValueParser::new([ + "long", + "verbose", + "single-column", + "columns", + "vertical", + "across", + "horizontal", + "commas", + ])) + .hide_possible_values(true) + .require_equals(true) + .overrides_with_all([ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + options::DIRED, + ]), + ) + .arg( + Arg::new(options::format::COLUMNS) + .short('C') + .help(translate!("ls-help-display-files-columns")) + .overrides_with_all([ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::format::LONG) + .short('l') + .long(options::format::LONG) + .help(translate!("ls-help-display-detailed-info")) + .overrides_with_all([ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::format::ACROSS) + .short('x') + .help(translate!("ls-help-list-entries-rows")) + .overrides_with_all([ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::format::TAB_SIZE) + .short('T') + .long(options::format::TAB_SIZE) + .env("TABSIZE") + .value_name("COLS") + .help(translate!("ls-help-assume-tab-stops")), + ) + .arg( + Arg::new(options::format::COMMAS) + .short('m') + .help(translate!("ls-help-list-entries-commas")) + .overrides_with_all([ + options::FORMAT, + options::format::COLUMNS, + options::format::LONG, + options::format::ACROSS, + options::format::COLUMNS, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::ZERO) + .long(options::ZERO) + .overrides_with(options::ZERO) + .help(translate!("ls-help-list-entries-nul")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::DIRED) + .long(options::DIRED) + .short('D') + .help(translate!("ls-help-generate-dired-output")) + .action(ArgAction::SetTrue) + .overrides_with(options::HYPERLINK), + ) + .arg( + Arg::new(options::HYPERLINK) + .long(options::HYPERLINK) + .help(translate!("ls-help-hyperlink-filenames")) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("always").alias("yes").alias("force"), + PossibleValue::new("auto").alias("tty").alias("if-tty"), + PossibleValue::new("never").alias("no").alias("none"), + ])) + .require_equals(true) + .num_args(0..=1) + .default_missing_value("always") + .default_value("never") + .value_name("WHEN") + .overrides_with(options::DIRED), + ) + // The next four arguments do not override with the other format + // options, see the comment in Config::from for the reason. + // Ideally, they would use Arg::override_with, with their own name + // but that doesn't seem to work in all cases. Example: + // ls -1g1 + // even though `ls -11` and `ls -1 -g -1` work. + .arg( + Arg::new(options::format::ONE_LINE) + .short('1') + .help(translate!("ls-help-list-one-file-per-line")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::format::LONG_NO_GROUP) + .short('o') + .help(translate!("ls-help-long-format-no-group")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::format::LONG_NO_OWNER) + .short('g') + .help(translate!("ls-help-long-no-owner")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::format::LONG_NUMERIC_UID_GID) + .short('n') + .long(options::format::LONG_NUMERIC_UID_GID) + .help(translate!("ls-help-long-numeric-uid-gid")) + .action(ArgAction::SetTrue), + ) + // Quoting style + .arg( + Arg::new(QUOTING_STYLE) + .long(QUOTING_STYLE) + .help(translate!("ls-help-set-quoting-style")) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("literal"), + PossibleValue::new("shell"), + PossibleValue::new("shell-escape"), + PossibleValue::new("shell-always"), + PossibleValue::new("shell-escape-always"), + PossibleValue::new("c").alias("c-maybe"), + PossibleValue::new("escape"), + ])) + .overrides_with_all([ + QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]), + ) + .arg( + Arg::new(options::quoting::LITERAL) + .short('N') + .long(options::quoting::LITERAL) + .alias("l") + .help(translate!("ls-help-literal-quoting-style")) + .overrides_with_all([ + QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::quoting::ESCAPE) + .short('b') + .long(options::quoting::ESCAPE) + .help(translate!("ls-help-escape-quoting-style")) + .overrides_with_all([ + QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::quoting::C) + .short('Q') + .long(options::quoting::C) + .help(translate!("ls-help-c-quoting-style")) + .overrides_with_all([ + QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]) + .action(ArgAction::SetTrue), + ) + // Control characters + .arg( + Arg::new(options::HIDE_CONTROL_CHARS) + .short('q') + .long(options::HIDE_CONTROL_CHARS) + .help(translate!("ls-help-replace-control-chars")) + .overrides_with_all([options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::SHOW_CONTROL_CHARS) + .long(options::SHOW_CONTROL_CHARS) + .help(translate!("ls-help-show-control-chars")) + .overrides_with_all([options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]) + .action(ArgAction::SetTrue), + ) + // Time arguments + .arg( + Arg::new(options::TIME) + .long(options::TIME) + .help(translate!("ls-help-show-time-field")) + .value_name("field") + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("atime").alias("access").alias("use"), + PossibleValue::new("ctime").alias("status"), + PossibleValue::new("mtime").alias("modification"), + PossibleValue::new("birth").alias("creation"), + ])) + .hide_possible_values(true) + .require_equals(true) + .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]), + ) + .arg( + Arg::new(options::time::CHANGE) + .short('c') + .help(translate!("ls-help-time-change")) + .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::time::ACCESS) + .short('u') + .help(translate!("ls-help-time-access")) + .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]) + .action(ArgAction::SetTrue), + ) + // Hide and ignore + .arg( + Arg::new(options::HIDE) + .long(options::HIDE) + .action(ArgAction::Append) + .value_name("PATTERN") + .help(translate!("ls-help-hide-pattern")), + ) + .arg( + Arg::new(options::IGNORE) + .short('I') + .long(options::IGNORE) + .action(ArgAction::Append) + .value_name("PATTERN") + .help(translate!("ls-help-ignore-pattern")), + ) + .arg( + Arg::new(options::IGNORE_BACKUPS) + .short('B') + .long(options::IGNORE_BACKUPS) + .help(translate!("ls-help-ignore-backups")) + .action(ArgAction::SetTrue), + ) + // Sort arguments + .arg( + Arg::new(options::SORT) + .long(options::SORT) + .help(translate!("ls-help-sort-by-field")) + .value_name("field") + .value_parser(ShortcutValueParser::new([ + "name", + "none", + "time", + "size", + "version", + "extension", + "width", + ])) + .require_equals(true) + .overrides_with_all([ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]), + ) + .arg( + Arg::new(options::sort::SIZE) + .short('S') + .help(translate!("ls-help-sort-by-size")) + .overrides_with_all([ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::sort::TIME) + .short('t') + .help(translate!("ls-help-sort-by-time")) + .overrides_with_all([ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::sort::VERSION) + .short('v') + .help(translate!("ls-help-sort-by-version")) + .overrides_with_all([ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::sort::EXTENSION) + .short('X') + .help(translate!("ls-help-sort-by-extension")) + .overrides_with_all([ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::sort::NONE) + .short('U') + .help(translate!("ls-help-sort-none")) + .overrides_with_all([ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, + ]) + .action(ArgAction::SetTrue), + ) + // Dereferencing + .arg( + Arg::new(options::dereference::ALL) + .short('L') + .long(options::dereference::ALL) + .help(translate!("ls-help-dereference-all")) + .overrides_with_all([ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::dereference::DIR_ARGS) + .long(options::dereference::DIR_ARGS) + .help(translate!("ls-help-dereference-dir-args")) + .overrides_with_all([ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::dereference::ARGS) + .short('H') + .long(options::dereference::ARGS) + .help(translate!("ls-help-dereference-args")) + .overrides_with_all([ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + .action(ArgAction::SetTrue), + ) + // Long format options + .arg( + Arg::new(options::NO_GROUP) + .long(options::NO_GROUP) + .short('G') + .help(translate!("ls-help-no-group")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::AUTHOR) + .long(options::AUTHOR) + .help(translate!("ls-help-author")) + .action(ArgAction::SetTrue), + ) + // Other Flags + .arg( + Arg::new(options::files::ALL) + .short('a') + .long(options::files::ALL) + // Overrides -A (as the order matters) + .overrides_with_all([options::files::ALL, options::files::ALMOST_ALL]) + .help(translate!("ls-help-all-files")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::files::ALMOST_ALL) + .short('A') + .long(options::files::ALMOST_ALL) + // Overrides -a (as the order matters) + .overrides_with_all([options::files::ALL, options::files::ALMOST_ALL]) + .help(translate!("ls-help-almost-all")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::DIRECTORY) + .short('d') + .long(options::DIRECTORY) + .help(translate!("ls-help-directory")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::size::HUMAN_READABLE) + .short('h') + .long(options::size::HUMAN_READABLE) + .help(translate!("ls-help-human-readable")) + .overrides_with_all([options::size::BLOCK_SIZE, options::size::SI]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::size::KIBIBYTES) + .short('k') + .long(options::size::KIBIBYTES) + .help(translate!("ls-help-kibibytes")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::size::SI) + .long(options::size::SI) + .help(translate!("ls-help-si")) + .overrides_with_all([options::size::BLOCK_SIZE, options::size::HUMAN_READABLE]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::size::BLOCK_SIZE) + .long(options::size::BLOCK_SIZE) + .require_equals(true) + .value_name("BLOCK_SIZE") + .help(translate!("ls-help-block-size")) + .overrides_with_all([options::size::SI, options::size::HUMAN_READABLE]), + ) + .arg( + Arg::new(options::INODE) + .short('i') + .long(options::INODE) + .help(translate!("ls-help-print-inode")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::REVERSE) + .short('r') + .long(options::REVERSE) + .help(translate!("ls-help-reverse-sort")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::RECURSIVE) + .short('R') + .long(options::RECURSIVE) + .help(translate!("ls-help-recursive")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::WIDTH) + .long(options::WIDTH) + .short('w') + .help(translate!("ls-help-terminal-width")) + .value_name("COLS"), + ) + .arg( + Arg::new(options::size::ALLOCATION_SIZE) + .short('s') + .long(options::size::ALLOCATION_SIZE) + .help(translate!("ls-help-allocation-size")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::COLOR) + .long(options::COLOR) + .help(translate!("ls-help-color-output")) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("always").alias("yes").alias("force"), + PossibleValue::new("auto").alias("tty").alias("if-tty"), + PossibleValue::new("never").alias("no").alias("none"), + ])) + .require_equals(true) + .num_args(0..=1), + ) + .arg( + Arg::new(options::INDICATOR_STYLE) + .long(options::INDICATOR_STYLE) + .help(translate!("ls-help-indicator-style")) + .value_parser(ShortcutValueParser::new([ + "none", + "slash", + "file-type", + "classify", + ])) + .overrides_with_all([ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ]), + ) + .arg( + // The --classify flag can take an optional when argument to + // control its behavior from version 9 of GNU coreutils. + // There is currently an inconsistency where GNU coreutils allows only + // the long form of the flag to take the argument while we allow it + // for both the long and short form of the flag. + Arg::new(options::indicator_style::CLASSIFY) + .short('F') + .long(options::indicator_style::CLASSIFY) + .help(translate!("ls-help-classify")) + .value_name("when") + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("always").alias("yes").alias("force"), + PossibleValue::new("auto").alias("tty").alias("if-tty"), + PossibleValue::new("never").alias("no").alias("none"), + ])) + .default_missing_value("always") + .require_equals(true) + .num_args(0..=1) + .overrides_with_all([ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ]), + ) + .arg( + Arg::new(options::indicator_style::FILE_TYPE) + .long(options::indicator_style::FILE_TYPE) + .help(translate!("ls-help-file-type")) + .overrides_with_all([ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::indicator_style::SLASH) + .short('p') + .help(translate!("ls-help-slash-directories")) + .overrides_with_all([ + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, + options::INDICATOR_STYLE, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + //This still needs support for posix-* + Arg::new(options::TIME_STYLE) + .long(options::TIME_STYLE) + .help(translate!("ls-help-time-style")) + .value_name("TIME_STYLE") + .env("TIME_STYLE") + .value_parser(NonEmptyStringValueParser::new()) + .overrides_with_all([options::TIME_STYLE]), + ) + .arg( + Arg::new(options::FULL_TIME) + .long(options::FULL_TIME) + .overrides_with(options::FULL_TIME) + .help(translate!("ls-help-full-time")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::CONTEXT) + .short('Z') + .long(options::CONTEXT) + .help(translate!("ls-help-context")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::GROUP_DIRECTORIES_FIRST) + .long(options::GROUP_DIRECTORIES_FIRST) + .help(translate!("ls-help-group-directories-first")) + .action(ArgAction::SetTrue), + ) + // Positional arguments + .arg( + Arg::new(options::PATHS) + .action(ArgAction::Append) + .value_hint(clap::ValueHint::AnyPath) + .value_parser(ValueParser::os_string()), + ) + .after_help(translate!("ls-after-help")) } /// Represents a Path along with it's associated data. diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index fb82d963ce1..dc59b2b99c0 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -15,7 +15,6 @@ 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}; @@ -80,9 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Linux-specific options, not implemented // opts.optflag("Z", "context", "set SELinux security context" + // " of each created directory to CTX"), - let matches = uu_app() - .after_help(translate!("mkdir-after-help")) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let dirs = matches .get_many::(options::DIRS) @@ -116,6 +113,7 @@ pub fn uu_app() -> Command { .about(translate!("mkdir-about")) .override_usage(format_usage(&translate!("mkdir-usage"))) .infer_long_args(true) + .after_help(translate!("mkdir-after-help")) .arg( Arg::new(options::MODE) .short('m') diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 5cae85cc87b..572ea00b81e 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -12,7 +12,6 @@ use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::translate; -use uucore::LocalizedCommand; use uucore::{format_usage, show}; mod options { @@ -24,7 +23,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 d922c3b82e0..ca2640b68d4 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -10,7 +10,6 @@ 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; @@ -112,7 +111,7 @@ fn mknod(file_name: &str, config: Config) -> i32 { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 ceb8605bfb7..a0eaa32d451 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -343,7 +343,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { 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); + handle_clap_error_with_exit_code(e, 1); } if e.kind() == clap::error::ErrorKind::TooManyValues && e.context().any(|(kind, val)| { diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 262dc9940f4..9c172db9cb3 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -27,7 +27,6 @@ use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::format_usage; use uucore::{display::Quotable, show}; -use uucore::LocalizedCommand; use uucore::translate; #[derive(Debug)] @@ -153,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { print!("\r"); println!("{panic_info}"); })); - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 b737b88a04c..5d487b0e193 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -52,7 +52,6 @@ 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}; @@ -153,7 +152,7 @@ static OPT_SELINUX: &str = "selinux"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let files: Vec = matches .get_many::(ARG_FILES) @@ -166,7 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { 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); + uucore::clap_localization::handle_clap_error_with_exit_code(err, 1); } let overwrite_mode = determine_overwrite_mode(&matches); diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 5e9b555e8ef..8e47e9d078c 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -13,7 +13,7 @@ use std::ptr; use uucore::translate; use uucore::{ - error::{UClapError, UResult, USimpleError, UUsageError, set_exit_code}, + error::{UResult, USimpleError, UUsageError, set_exit_code}, format_usage, show_error, }; @@ -103,7 +103,8 @@ fn standardize_nice_args(mut args: impl uucore::Args) -> impl uucore::Args { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = standardize_nice_args(args); - let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; + let matches = + uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 125)?; nix::errno::Errno::clear(); let mut niceness = unsafe { libc::getpriority(PRIO_PROCESS, 0) }; diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 6d08535160f..56a390d9ca4 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -9,7 +9,7 @@ use std::fs::File; use std::io::{BufRead, BufReader, Read, stdin}; use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; -use uucore::{LocalizedCommand, format_usage, show_error, translate}; +use uucore::{format_usage, show_error, translate}; mod helper; @@ -186,7 +186,7 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let mut settings = Settings::default(); diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index f20ec29afcf..0c596c162cd 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -16,7 +16,7 @@ use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use thiserror::Error; use uucore::display::Quotable; -use uucore::error::{UClapError, UError, UResult, set_exit_code}; +use uucore::error::{UError, UResult, set_exit_code}; use uucore::translate; use uucore::{format_usage, show_error}; @@ -57,7 +57,8 @@ impl UError for NohupError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; + let matches = + uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 125)?; replace_fds()?; diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index dd53e81b9eb..b75c4c8db0e 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -7,7 +7,6 @@ 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; @@ -27,7 +26,7 @@ static OPT_IGNORE: &str = "ignore"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 e5cec7692f7..fc9f1c1e1a4 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -13,7 +13,6 @@ 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; @@ -255,7 +254,7 @@ fn parse_options(args: &ArgMatches) -> Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 e63a29a0051..2dac86d2e28 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -46,7 +46,6 @@ 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}; @@ -221,7 +220,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let clap_opts = uu_app(); - let clap_matches = clap_opts.get_matches_from_localized(&args); + let clap_matches = uucore::clap_localization::handle_clap_result(clap_opts, &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 540938ccda2..23b6d0757fe 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -12,7 +12,6 @@ use std::iter::Cycle; use std::path::Path; 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; @@ -27,7 +26,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 e73ac2d7c12..d0a35c33160 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -9,7 +9,6 @@ use clap::{Arg, ArgAction, Command}; use std::ffi::OsString; 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; @@ -36,7 +35,7 @@ const POSIX_NAME_MAX: usize = 14; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; // set working mode let is_posix = matches.get_flag(options::POSIX); diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index c2d303e2670..942695654db 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -34,12 +34,12 @@ pub fn uu_app() -> Command { #[cfg(target_env = "musl")] let about = translate!("pinky-about") + &translate!("pinky-about-musl-warning"); - Command::new(uucore::util_name()) + 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!("pinky-usage"))) - .infer_long_args(true) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .disable_help_flag(true) .arg( Arg::new(options::LONG_FORMAT) diff --git a/src/uu/pinky/src/platform/openbsd.rs b/src/uu/pinky/src/platform/openbsd.rs index d8dcbc9283a..304f79029f2 100644 --- a/src/uu/pinky/src/platform/openbsd.rs +++ b/src/uu/pinky/src/platform/openbsd.rs @@ -5,12 +5,11 @@ // 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().get_matches_from_localized(args); + let _matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 5832dbbf099..1a749e6b9e2 100644 --- a/src/uu/pinky/src/platform/unix.rs +++ b/src/uu/pinky/src/platform/unix.rs @@ -9,7 +9,6 @@ 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; @@ -33,9 +32,8 @@ fn get_long_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(get_long_usage()) - .get_matches_from_localized(args); + let matches = + uucore::clap_localization::handle_clap_result(uu_app().after_help(get_long_usage()), 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 75fea4be18e..66c97dc2d57 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -16,7 +16,6 @@ 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; @@ -318,7 +317,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let opt_args = recreate_arguments(&args); let command = uu_app(); - let matches = command.get_matches_from_mut_localized(opt_args); + let matches = uucore::clap_localization::handle_clap_result(command, 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 3ae77a77ed5..47801fd378e 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -14,10 +14,7 @@ static ARG_VARIABLES: &str = "variables"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - 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 matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 2)?; let variables: Vec = matches .get_many::(ARG_VARIABLES) @@ -55,12 +52,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .arg( Arg::new(OPT_NULL) .short('0') diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index e48d253f59e..d313c8acec5 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -6,7 +6,6 @@ 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; @@ -22,7 +21,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 a5696b96b6b..930d4773ec0 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -17,7 +17,6 @@ use std::path::Path; 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; @@ -731,7 +730,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 fccf8c7b32f..cefdee8986f 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -13,7 +13,6 @@ 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"; @@ -110,7 +109,7 @@ fn logical_path() -> io::Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 dd2c785b41b..d3d3b298473 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -10,7 +10,6 @@ use std::ffi::OsString; use std::fs; use std::io::{Write, stdout}; use std::path::{Path, PathBuf}; -use uucore::LocalizedCommand; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::line_ending::LineEnding; @@ -30,7 +29,7 @@ const ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 2f1efacc612..6b57acefb98 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -18,7 +18,7 @@ use uucore::fs::make_path_relative_to; use uucore::translate; use uucore::{ display::{Quotable, print_verbatim}, - error::{FromIo, UClapError, UResult}, + error::{FromIo, UResult}, format_usage, fs::{MissingHandling, ResolveMode, canonicalize}, line_ending::LineEnding, @@ -72,7 +72,7 @@ impl ValueParserFactory for NonEmptyOsStringParser { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args).with_exit_code(1)?; + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; /* the list of files */ diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 81dbd47263a..ac5a818a6f2 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -23,7 +23,6 @@ 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)] @@ -144,7 +143,7 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 d2c2ac10590..4f13afcbf83 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -15,7 +15,6 @@ 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"; @@ -26,7 +25,7 @@ static ARG_DIRS: &str = "dirs"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let opts = Opts { ignore: matches.get_flag(OPT_IGNORE_FAIL_NON_EMPTY), diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index f6fee91582c..f0738a5c034 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -6,7 +6,7 @@ #![cfg(target_os = "linux")] use clap::builder::ValueParser; -use uucore::error::{UClapError, UError, UResult}; +use uucore::error::{UError, UResult}; use uucore::translate; use clap::{Arg, ArgAction, Command}; @@ -37,12 +37,7 @@ pub mod options { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let config = uu_app(); - let options = match parse_command_line(config, args) { - Ok(r) => r, - Err(r) => { - return Err(r); - } - }; + let options = parse_command_line(config, args)?; match &options.mode { CommandLineMode::Print => print_current_context().map_err(|e| RunconError::new(e).into()), @@ -85,13 +80,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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"))) - .infer_long_args(true) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .arg( Arg::new(options::COMPUTE) .short('c') @@ -185,7 +180,7 @@ struct Options { } fn parse_command_line(config: Command, args: impl uucore::Args) -> UResult { - let matches = config.try_get_matches_from(args).with_exit_code(125)?; + let matches = uucore::clap_localization::handle_clap_result_with_exit_code(config, args, 125)?; let compute_transition_context = matches.get_flag(options::COMPUTE); diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 5ab029b1a31..a489e54b9a9 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -92,7 +92,8 @@ fn select_precision( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(split_short_args_with_value(args))?; + let matches = + uucore::clap_localization::handle_clap_result(uu_app(), split_short_args_with_value(args))?; let numbers_option = matches.get_many::(ARG_NUMBERS); diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index b7b882c2694..1fd3ef77892 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -15,7 +15,6 @@ 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; @@ -240,7 +239,7 @@ impl<'a> BytesWriter<'a> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 45c1e4ece75..e69ad1e1caf 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -17,7 +17,6 @@ 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; @@ -52,7 +51,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 4cbaa6e6fe5..dd29d92b36e 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -6,7 +6,6 @@ use clap::{Arg, ArgAction, Command}; use std::thread; use std::time::Duration; -use uucore::LocalizedCommand; use uucore::translate; use uucore::{ error::{UResult, USimpleError, UUsageError}, @@ -21,7 +20,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 92d3e9522e1..1d4948186fe 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1042,19 +1042,7 @@ const STDIN_FILE: &str = "-"; pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut settings = GlobalSettings::default(); - let matches = match uu_app().try_get_matches_from(args) { - Ok(t) => t, - Err(e) => { - // not all clap "Errors" are because of a failure to parse arguments. - // "--version" also causes an Error to be returned, but we should not print to stderr - // 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; - // Always use the localization handler for proper error and help handling - handle_clap_error_with_exit_code(e, uucore::util_name(), 2); - } - }; + let matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 2)?; // Prevent -o/--output to be specified multiple times if matches @@ -1338,250 +1326,251 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(uucore::crate_version!()) - .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) - .args_override_self(true) - .arg( - Arg::new(options::HELP) - .long(options::HELP) - .help(translate!("sort-help-help")) - .action(ArgAction::Help), - ) - .arg( - Arg::new(options::VERSION) - .long(options::VERSION) - .help(translate!("sort-help-version")) - .action(ArgAction::Version), - ) - .arg( - Arg::new(options::modes::SORT) - .long(options::modes::SORT) - .value_parser(ShortcutValueParser::new([ - "general-numeric", - "human-numeric", - "month", - "numeric", - "version", - "random", - ])) - .conflicts_with_all(options::modes::ALL_SORT_MODES), - ) - .arg(make_sort_mode_arg( - options::modes::HUMAN_NUMERIC, - 'h', - translate!("sort-help-human-numeric"), - )) - .arg(make_sort_mode_arg( - options::modes::MONTH, - 'M', - translate!("sort-help-month"), - )) - .arg(make_sort_mode_arg( - options::modes::NUMERIC, - 'n', - translate!("sort-help-numeric"), - )) - .arg(make_sort_mode_arg( - options::modes::GENERAL_NUMERIC, - 'g', - translate!("sort-help-general-numeric"), - )) - .arg(make_sort_mode_arg( - options::modes::VERSION, - 'V', - translate!("sort-help-version-sort"), - )) - .arg(make_sort_mode_arg( - options::modes::RANDOM, - 'R', - translate!("sort-help-random"), - )) - .arg( - Arg::new(options::DICTIONARY_ORDER) - .short('d') - .long(options::DICTIONARY_ORDER) - .help(translate!("sort-help-dictionary-order")) - .conflicts_with_all([ - options::modes::NUMERIC, - options::modes::GENERAL_NUMERIC, - options::modes::HUMAN_NUMERIC, - options::modes::MONTH, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::MERGE) - .short('m') - .long(options::MERGE) - .help(translate!("sort-help-merge")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::check::CHECK) - .short('c') - .long(options::check::CHECK) - .require_equals(true) - .num_args(0..) - .value_parser(ShortcutValueParser::new([ - options::check::SILENT, - options::check::QUIET, - options::check::DIAGNOSE_FIRST, - ])) - .conflicts_with_all([options::OUTPUT, options::check::CHECK_SILENT]) - .help(translate!("sort-help-check")), - ) - .arg( - Arg::new(options::check::CHECK_SILENT) - .short('C') - .long(options::check::CHECK_SILENT) - .conflicts_with_all([options::OUTPUT, options::check::CHECK]) - .help(translate!("sort-help-check-silent")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::IGNORE_CASE) - .short('f') - .long(options::IGNORE_CASE) - .help(translate!("sort-help-ignore-case")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::IGNORE_NONPRINTING) - .short('i') - .long(options::IGNORE_NONPRINTING) - .help(translate!("sort-help-ignore-nonprinting")) - .conflicts_with_all([ - options::modes::NUMERIC, - options::modes::GENERAL_NUMERIC, - options::modes::HUMAN_NUMERIC, - options::modes::MONTH, - ]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::IGNORE_LEADING_BLANKS) - .short('b') - .long(options::IGNORE_LEADING_BLANKS) - .help(translate!("sort-help-ignore-leading-blanks")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::OUTPUT) - .short('o') - .long(options::OUTPUT) - .help(translate!("sort-help-output")) - .value_parser(ValueParser::os_string()) - .value_name("FILENAME") - .value_hint(clap::ValueHint::FilePath) - .num_args(1) - .allow_hyphen_values(true) - // To detect multiple occurrences and raise an error - .action(ArgAction::Append), - ) - .arg( - Arg::new(options::REVERSE) - .short('r') - .long(options::REVERSE) - .help(translate!("sort-help-reverse")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::STABLE) - .short('s') - .long(options::STABLE) - .help(translate!("sort-help-stable")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::UNIQUE) - .short('u') - .long(options::UNIQUE) - .help(translate!("sort-help-unique")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::KEY) - .short('k') - .long(options::KEY) - .help(translate!("sort-help-key")) - .action(ArgAction::Append) - .num_args(1), - ) - .arg( - Arg::new(options::SEPARATOR) - .short('t') - .long(options::SEPARATOR) - .help(translate!("sort-help-separator")) - .value_parser(ValueParser::os_string()), - ) - .arg( - Arg::new(options::ZERO_TERMINATED) - .short('z') - .long(options::ZERO_TERMINATED) - .help(translate!("sort-help-zero-terminated")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::PARALLEL) - .long(options::PARALLEL) - .help(translate!("sort-help-parallel")) - .value_name("NUM_THREADS"), - ) - .arg( - Arg::new(options::BUF_SIZE) - .short('S') - .long(options::BUF_SIZE) - .help(translate!("sort-help-buf-size")) - .value_name("SIZE"), - ) - .arg( - Arg::new(options::TMP_DIR) - .short('T') - .long(options::TMP_DIR) - .help(translate!("sort-help-tmp-dir")) - .value_name("DIR") - .value_hint(clap::ValueHint::DirPath), - ) - .arg( - Arg::new(options::COMPRESS_PROG) - .long(options::COMPRESS_PROG) - .help(translate!("sort-help-compress-prog")) - .value_name("PROG") - .value_hint(clap::ValueHint::CommandName), - ) - .arg( - Arg::new(options::BATCH_SIZE) - .long(options::BATCH_SIZE) - .help(translate!("sort-help-batch-size")) - .value_name("N_MERGE"), - ) - .arg( - Arg::new(options::FILES0_FROM) - .long(options::FILES0_FROM) - .help(translate!("sort-help-files0-from")) - .value_name("NUL_FILE") - .value_parser(ValueParser::os_string()) - .value_hint(clap::ValueHint::FilePath), - ) - .arg( - Arg::new(options::DEBUG) - .long(options::DEBUG) - .help(translate!("sort-help-debug")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::FILES) - .action(ArgAction::Append) - .value_parser(ValueParser::os_string()) - .value_hint(clap::ValueHint::FilePath), - ) + uucore::clap_localization::configure_localized_command( + Command::new(uucore::util_name()) + .version(uucore::crate_version!()) + .about(translate!("sort-about")) + .after_help(translate!("sort-after-help")) + .override_usage(format_usage(&translate!("sort-usage"))), + ) + .infer_long_args(true) + .disable_help_flag(true) + .disable_version_flag(true) + .args_override_self(true) + .arg( + Arg::new(options::HELP) + .long(options::HELP) + .help(translate!("sort-help-help")) + .action(ArgAction::Help), + ) + .arg( + Arg::new(options::VERSION) + .long(options::VERSION) + .help(translate!("sort-help-version")) + .action(ArgAction::Version), + ) + .arg( + Arg::new(options::modes::SORT) + .long(options::modes::SORT) + .value_parser(ShortcutValueParser::new([ + "general-numeric", + "human-numeric", + "month", + "numeric", + "version", + "random", + ])) + .conflicts_with_all(options::modes::ALL_SORT_MODES), + ) + .arg(make_sort_mode_arg( + options::modes::HUMAN_NUMERIC, + 'h', + translate!("sort-help-human-numeric"), + )) + .arg(make_sort_mode_arg( + options::modes::MONTH, + 'M', + translate!("sort-help-month"), + )) + .arg(make_sort_mode_arg( + options::modes::NUMERIC, + 'n', + translate!("sort-help-numeric"), + )) + .arg(make_sort_mode_arg( + options::modes::GENERAL_NUMERIC, + 'g', + translate!("sort-help-general-numeric"), + )) + .arg(make_sort_mode_arg( + options::modes::VERSION, + 'V', + translate!("sort-help-version-sort"), + )) + .arg(make_sort_mode_arg( + options::modes::RANDOM, + 'R', + translate!("sort-help-random"), + )) + .arg( + Arg::new(options::DICTIONARY_ORDER) + .short('d') + .long(options::DICTIONARY_ORDER) + .help(translate!("sort-help-dictionary-order")) + .conflicts_with_all([ + options::modes::NUMERIC, + options::modes::GENERAL_NUMERIC, + options::modes::HUMAN_NUMERIC, + options::modes::MONTH, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::MERGE) + .short('m') + .long(options::MERGE) + .help(translate!("sort-help-merge")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::check::CHECK) + .short('c') + .long(options::check::CHECK) + .require_equals(true) + .num_args(0..) + .value_parser(ShortcutValueParser::new([ + options::check::SILENT, + options::check::QUIET, + options::check::DIAGNOSE_FIRST, + ])) + .conflicts_with_all([options::OUTPUT, options::check::CHECK_SILENT]) + .help(translate!("sort-help-check")), + ) + .arg( + Arg::new(options::check::CHECK_SILENT) + .short('C') + .long(options::check::CHECK_SILENT) + .conflicts_with_all([options::OUTPUT, options::check::CHECK]) + .help(translate!("sort-help-check-silent")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::IGNORE_CASE) + .short('f') + .long(options::IGNORE_CASE) + .help(translate!("sort-help-ignore-case")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::IGNORE_NONPRINTING) + .short('i') + .long(options::IGNORE_NONPRINTING) + .help(translate!("sort-help-ignore-nonprinting")) + .conflicts_with_all([ + options::modes::NUMERIC, + options::modes::GENERAL_NUMERIC, + options::modes::HUMAN_NUMERIC, + options::modes::MONTH, + ]) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::IGNORE_LEADING_BLANKS) + .short('b') + .long(options::IGNORE_LEADING_BLANKS) + .help(translate!("sort-help-ignore-leading-blanks")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::OUTPUT) + .short('o') + .long(options::OUTPUT) + .help(translate!("sort-help-output")) + .value_parser(ValueParser::os_string()) + .value_name("FILENAME") + .value_hint(clap::ValueHint::FilePath) + .num_args(1) + .allow_hyphen_values(true) + // To detect multiple occurrences and raise an error + .action(ArgAction::Append), + ) + .arg( + Arg::new(options::REVERSE) + .short('r') + .long(options::REVERSE) + .help(translate!("sort-help-reverse")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::STABLE) + .short('s') + .long(options::STABLE) + .help(translate!("sort-help-stable")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::UNIQUE) + .short('u') + .long(options::UNIQUE) + .help(translate!("sort-help-unique")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::KEY) + .short('k') + .long(options::KEY) + .help(translate!("sort-help-key")) + .action(ArgAction::Append) + .num_args(1), + ) + .arg( + Arg::new(options::SEPARATOR) + .short('t') + .long(options::SEPARATOR) + .help(translate!("sort-help-separator")) + .value_parser(ValueParser::os_string()), + ) + .arg( + Arg::new(options::ZERO_TERMINATED) + .short('z') + .long(options::ZERO_TERMINATED) + .help(translate!("sort-help-zero-terminated")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::PARALLEL) + .long(options::PARALLEL) + .help(translate!("sort-help-parallel")) + .value_name("NUM_THREADS"), + ) + .arg( + Arg::new(options::BUF_SIZE) + .short('S') + .long(options::BUF_SIZE) + .help(translate!("sort-help-buf-size")) + .value_name("SIZE"), + ) + .arg( + Arg::new(options::TMP_DIR) + .short('T') + .long(options::TMP_DIR) + .help(translate!("sort-help-tmp-dir")) + .value_name("DIR") + .value_hint(clap::ValueHint::DirPath), + ) + .arg( + Arg::new(options::COMPRESS_PROG) + .long(options::COMPRESS_PROG) + .help(translate!("sort-help-compress-prog")) + .value_name("PROG") + .value_hint(clap::ValueHint::CommandName), + ) + .arg( + Arg::new(options::BATCH_SIZE) + .long(options::BATCH_SIZE) + .help(translate!("sort-help-batch-size")) + .value_name("N_MERGE"), + ) + .arg( + Arg::new(options::FILES0_FROM) + .long(options::FILES0_FROM) + .help(translate!("sort-help-files0-from")) + .value_name("NUL_FILE") + .value_parser(ValueParser::os_string()) + .value_hint(clap::ValueHint::FilePath), + ) + .arg( + Arg::new(options::DEBUG) + .long(options::DEBUG) + .help(translate!("sort-help-debug")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::FILES) + .action(ArgAction::Append) + .value_parser(ValueParser::os_string()) + .value_hint(clap::ValueHint::FilePath), + ) } fn exec( diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 7d6cdbd94c5..768092bb0ac 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -26,7 +26,6 @@ use uucore::translate; use uucore::parser::parse_size::parse_size_u64; -use uucore::LocalizedCommand; use uucore::format_usage; use uucore::uio_error; @@ -52,7 +51,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().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 4d5dde94950..797e6971be7 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -27,7 +27,6 @@ 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)] @@ -1219,9 +1218,7 @@ impl Stater { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(translate!("stat-after-help")) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let stater = Stater::new(&matches)?; let exit_status = stater.exec(); @@ -1237,6 +1234,7 @@ pub fn uu_app() -> Command { .version(uucore::crate_version!()) .help_template(uucore::localized_help_template(uucore::util_name())) .about(translate!("stat-about")) + .after_help(translate!("stat-after-help")) .override_usage(format_usage(&translate!("stat-usage"))) .infer_long_args(true) .arg( diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index e8721d9814f..639c3ffff07 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -13,7 +13,7 @@ use std::process; use tempfile::TempDir; use tempfile::tempdir; use thiserror::Error; -use uucore::error::{FromIo, UClapError, UResult, USimpleError, UUsageError}; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::format_usage; use uucore::parser::parse_size::parse_size_u64; use uucore::translate; @@ -179,7 +179,8 @@ fn get_preload_env(_tmp_dir: &TempDir) -> UResult<(String, PathBuf)> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; + let matches = + uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 125)?; let options = ProgramOptions::try_from(&matches).map_err(|e| UUsageError::new(125, e.to_string()))?; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 69400c8553e..589d2adad42 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -29,7 +29,6 @@ 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; @@ -243,7 +242,7 @@ ioctl_write_ptr_bad!( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let opts = Options::from(&matches)?; diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 5832df77873..754964043d3 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -14,7 +14,6 @@ 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)> { @@ -99,7 +98,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 d5846359138..76162ffc45b 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -13,7 +13,6 @@ 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; @@ -174,7 +173,7 @@ mod platform { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 521e2c9dbca..507dd153199 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -21,7 +21,6 @@ use uucore::{format_usage, show}; use crate::error::TacError; -use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -33,7 +32,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 faf848ebda6..fc345a403cd 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -18,7 +18,6 @@ use uucore::{format_usage, show_error}; // spell-checker:ignore nopipe -use uucore::LocalizedCommand; #[cfg(unix)] use uucore::signals::{enable_pipe_errors, ignore_interrupts}; @@ -53,7 +52,7 @@ enum OutputErrorMode { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let append = matches.get_flag(options::APPEND); let ignore_interrupts = matches.get_flag(options::IGNORE_INTERRUPTS); diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index c553e1f8b26..34d59424194 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -15,7 +15,6 @@ 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; @@ -51,7 +50,10 @@ 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_localized(std::iter::once(program).chain(args.into_iter())); + uucore::clap_localization::handle_clap_result( + uu_app(), + 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 dded6bad63c..d0fba88ca71 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -14,7 +14,7 @@ use std::process::{self, Child, Stdio}; use std::sync::atomic::{self, AtomicBool}; use std::time::Duration; use uucore::display::Quotable; -use uucore::error::{UClapError, UResult, USimpleError, UUsageError}; +use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parser::parse_time; use uucore::process::ChildExt; use uucore::translate; @@ -104,7 +104,8 @@ impl Config { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; + let matches = + uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 125)?; let config = Config::from(&matches)?; timeout( diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 6994a5f70ff..98d87e3367b 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -20,7 +20,6 @@ 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; @@ -187,7 +186,7 @@ fn shr2(s: &str) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let mut filenames: Vec<&OsString> = matches .get_many::(ARG_FILES) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 0a374cd0ebd..d9349baa5bb 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -15,7 +15,6 @@ use operation::{ use simd::process_input; use std::ffi::OsString; use std::io::{stdin, stdout}; -use uucore::LocalizedCommand; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; @@ -43,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let delete_flag = matches.get_flag(options::DELETE); let complement_flag = matches.get_flag(options::COMPLEMENT); diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index a2750b72a5f..a45a4ac5941 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -84,16 +84,8 @@ pub mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(translate!("truncate-after-help")) - .try_get_matches_from(args) - .map_err(|e| { - e.print().expect("Error writing clap::Error"); - match e.kind() { - clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => 0, - _ => 1, - } - })?; + let app = uu_app(); + let matches = uucore::clap_localization::handle_clap_result(app, args)?; let files: Vec = matches .get_many::(options::ARG_FILES) @@ -117,12 +109,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) + .after_help(translate!("truncate-after-help")) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .arg( Arg::new(options::IO_BLOCKS) .short('o') diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 41a3cb79d6b..748e8e88d38 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -12,7 +12,6 @@ use uucore::display::Quotable; use uucore::error::{UError, UResult}; use uucore::{format_usage, show}; -use uucore::LocalizedCommand; use uucore::translate; mod options { @@ -45,7 +44,7 @@ impl UError for TsortError {} #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 1ac6ed362ac..984f34c60a4 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -18,10 +18,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - 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 matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 2)?; let silent = matches.get_flag(options::SILENT); @@ -56,18 +53,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) - .arg( - Arg::new(options::SILENT) - .long(options::SILENT) - .visible_alias("quiet") - .short('s') - .help(translate!("tty-help-silent")) - .action(ArgAction::SetTrue), - ) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd).arg( + Arg::new(options::SILENT) + .long(options::SILENT) + .visible_alias("quiet") + .short('s') + .help(translate!("tty-help-silent")) + .action(ArgAction::SetTrue), + ) } diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index d23d8b7fc8b..e6a9597aa74 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -7,7 +7,6 @@ use clap::{Arg, ArgAction, Command}; use platform_info::*; -use uucore::LocalizedCommand; use uucore::translate; use uucore::{ error::{UResult, USimpleError}, @@ -121,7 +120,7 @@ pub struct Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let options = Options { all: matches.get_flag(options::ALL), diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index e4a3a9964f2..9f306c999c4 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -147,7 +147,7 @@ fn expand_shortcuts(args: &[String]) -> Vec { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_ignore(); - let matches = uu_app().try_get_matches_from(expand_shortcuts(&args))?; + let matches = uucore::clap_localization::handle_clap_result(uu_app(), expand_shortcuts(&args))?; unexpand(&Options::new(&matches)?) } diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index cb9f9d198aa..506f1956596 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -547,9 +547,18 @@ fn map_clap_errors(clap_error: Error) -> Box { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (args, skip_fields_old, skip_chars_old) = handle_obsolete(args); - let matches = uu_app() - .try_get_matches_from(args) - .map_err(map_clap_errors)?; + let matches = match uu_app().try_get_matches_from(args) { + Ok(matches) => matches, + Err(clap_error) => { + if clap_error.exit_code() == 0 { + // Let caller handle help/version + return Err(map_clap_errors(clap_error)); + } + // Use ErrorFormatter directly to handle error + let formatter = uucore::clap_localization::ErrorFormatter::new(uucore::util_name()); + formatter.print_error_and_exit_with_callback(&clap_error, 1, || {}); + } + }; let files = matches.get_many::(ARG_FILES); @@ -590,13 +599,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let cmd = 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) - .after_help(translate!("uniq-after-help")) + .after_help(translate!("uniq-after-help")); + uucore::clap_localization::configure_localized_command(cmd) .arg( Arg::new(options::ALL_REPEATED) .short('D') diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 14602cf3882..0e03c3cd1ce 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -9,7 +9,6 @@ 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; @@ -19,7 +18,7 @@ static OPT_PATH: &str = "FILE"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 6e028fbd6ee..d6f38c3b835 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -17,7 +17,6 @@ use uucore::uptime::*; use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser}; -use uucore::LocalizedCommand; use uucore::format_usage; #[cfg(unix)] @@ -48,7 +47,7 @@ impl UError for UptimeError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 bd93e340287..3fb48a9b613 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -16,7 +16,6 @@ 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}; @@ -36,9 +35,7 @@ fn get_long_usage() -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(get_long_usage()) - .get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let maybe_file: Option<&Path> = matches.get_one::(ARG_FILE).map(AsRef::as_ref); @@ -94,6 +91,7 @@ pub fn uu_app() -> Command { .about(about) .override_usage(format_usage(&translate!("users-usage"))) .infer_long_args(true) + .after_help(get_long_usage()) .arg( Arg::new(ARG_FILE) .num_args(1) diff --git a/src/uu/vdir/src/vdir.rs b/src/uu/vdir/src/vdir.rs index 126dad6d63f..affc8d47e01 100644 --- a/src/uu/vdir/src/vdir.rs +++ b/src/uu/vdir/src/vdir.rs @@ -13,7 +13,7 @@ use uucore::quoting_style::QuotingStyle; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command = uu_app(); - let matches = command.get_matches_from(args); + let matches = uucore::clap_localization::handle_clap_result_with_exit_code(command, args, 2)?; let mut default_quoting_style = false; let mut default_format_style = false; diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index fa111e4e46c..44362e03fb4 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -26,7 +26,6 @@ use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; use uucore::translate; -use uucore::LocalizedCommand; use uucore::{ error::{FromIo, UError, UResult}, format_usage, @@ -377,7 +376,7 @@ impl UError for WcError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 4a2954d274f..f5871382b11 100644 --- a/src/uu/who/src/platform/openbsd.rs +++ b/src/uu/who/src/platform/openbsd.rs @@ -7,12 +7,11 @@ 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().get_matches_from_localized(args); + let _matches = uucore::clap_localization::handle_clap_result(uu_app(), 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 eeef62ee8af..8e72a83ba39 100644 --- a/src/uu/who/src/platform/unix.rs +++ b/src/uu/who/src/platform/unix.rs @@ -13,7 +13,6 @@ use uucore::error::{FromIo, UResult}; use uucore::libc::{S_IWGRP, STDIN_FILENO, ttyname}; use uucore::translate; -use uucore::LocalizedCommand; use uucore::utmpx::{self, UtmpxRecord, time}; use std::borrow::Cow; @@ -27,9 +26,8 @@ fn get_long_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app() - .after_help(get_long_usage()) - .get_matches_from_localized(args); + let matches = + uucore::clap_localization::handle_clap_result(uu_app().after_help(get_long_usage()), args)?; let files: Vec = matches .get_many::(options::FILE) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 2a91b4cf6a9..49b67e47d03 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -45,12 +45,12 @@ pub fn uu_app() -> Command { #[cfg(target_env = "musl")] let about = translate!("who-about") + &translate!("who-about-musl-warning"); - Command::new(uucore::util_name()) + 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!("who-usage"))) - .infer_long_args(true) + .infer_long_args(true); + uucore::clap_localization::configure_localized_command(cmd) .arg( Arg::new(options::ALL) .long(options::ALL) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index f65dfd0788e..f56bb80b1a5 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -5,7 +5,6 @@ use clap::Command; use std::ffi::OsString; -use uucore::LocalizedCommand; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; use uucore::translate; @@ -14,7 +13,7 @@ mod platform; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().get_matches_from_localized(args); + uucore::clap_localization::handle_clap_result(uu_app(), 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 028f6ba7e41..a5aaa18a867 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -9,7 +9,6 @@ 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)] @@ -22,7 +21,7 @@ const BUF_SIZE: usize = 16 * 1024; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from_localized(args); + let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; let mut buffer = Vec::with_capacity(BUF_SIZE); args_into_buffer(&mut buffer, matches.get_many::("STRING")).unwrap(); diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl index da3dbaf3cd0..5e0816b0260 100644 --- a/src/uucore/locales/en-US.ftl +++ b/src/uucore/locales/en-US.ftl @@ -10,10 +10,12 @@ common-version = version # Common clap error messages clap-error-unexpected-argument = { $error_word }: unexpected argument '{ $arg }' found +clap-error-unexpected-argument-simple = unexpected argument clap-error-similar-argument = { $tip_word }: a similar argument exists: '{ $suggestion }' clap-error-pass-as-value = { $tip_word }: to pass '{ $arg }' as a value, use '{ $tip_command }' clap-error-invalid-value = { $error_word }: invalid value '{ $value }' for '{ $option }' clap-error-value-required = { $error_word }: a value is required for '{ $option }' but none was supplied +clap-error-missing-required-arguments = { $error_word }: the following required arguments were not provided: 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 b6c5a7bcfb4..0734f56ba13 100644 --- a/src/uucore/locales/fr-FR.ftl +++ b/src/uucore/locales/fr-FR.ftl @@ -10,10 +10,12 @@ common-version = version # Messages d'erreur clap communs clap-error-unexpected-argument = { $error_word } : argument inattendu '{ $arg }' trouvé +clap-error-unexpected-argument-simple = argument inattendu clap-error-similar-argument = { $tip_word } : un argument similaire existe : '{ $suggestion }' clap-error-pass-as-value = { $tip_word } : pour passer '{ $arg }' comme valeur, utilisez '{ $tip_command }' clap-error-invalid-value = { $error_word } : valeur invalide '{ $value }' pour '{ $option }' clap-error-value-required = { $error_word } : une valeur est requise pour '{ $option }' mais aucune n'a été fournie +clap-error-missing-required-arguments = { $error_word } : les arguments requis suivants n'ont pas été fournis : 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/features/perms.rs b/src/uucore/src/lib/features/perms.rs index c03d0032ac9..512b1a7dbaa 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -601,7 +601,7 @@ pub fn chown_base( .num_args(1..) .value_parser(clap::value_parser!(std::ffi::OsString)), ); - let matches = command.try_get_matches_from(args)?; + let matches = crate::clap_localization::handle_clap_result(command, args)?; let files: Vec = matches .get_many::(options::ARG_FILES) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 7b16baff9ce..646749bd9b2 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -23,7 +23,6 @@ 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")] @@ -252,17 +251,56 @@ pub fn format_usage(s: &str) -> String { /// .help_template(localized_help_template("myutil")); /// ``` pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr { + use std::io::IsTerminal; + + // Determine if colors should be enabled - same logic as configure_localized_command + let colors_enabled = if std::env::var("NO_COLOR").is_ok() { + false + } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() { + true + } else { + IsTerminal::is_terminal(&std::io::stdout()) + && std::env::var("TERM").unwrap_or_default() != "dumb" + }; + + localized_help_template_with_colors(util_name, colors_enabled) +} + +/// Create a localized help template with explicit color control +/// This ensures color detection consistency between clap and our template +pub fn localized_help_template_with_colors( + util_name: &str, + colors_enabled: bool, +) -> clap::builder::StyledStr { + use std::fmt::Write; + // Ensure localization is initialized for this utility let _ = crate::locale::setup_localization(util_name); + // Get the localized "Usage" label 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}}" - ); + // Create a styled template + let mut template = clap::builder::StyledStr::new(); + + // Add the basic template parts + writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap(); + + // Add styled usage header (bold + underline like clap's default) + if colors_enabled { + write!( + template, + "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n" + ) + .unwrap(); + } else { + write!(template, "{usage_label}: {{usage}}\n\n").unwrap(); + } + + // Add the rest + write!(template, "{{all-args}}{{after-help}}").unwrap(); - clap::builder::StyledStr::from(template) + template } /// Used to check if the utility is the second argument. diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 91b7f078d0e..4ca46fe20a1 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (path) osrelease +// spell-checker:ignore (path) osrelease myutil //! Helper clap functions to localize error handling and options //! @@ -11,6 +11,7 @@ //! instead of parsing error strings, providing a more robust solution. //! +use crate::error::UResult; use crate::locale::translate; use clap::error::{ContextKind, ErrorKind}; @@ -19,43 +20,6 @@ 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 -/// Based on clap's own design patterns and error categorization -fn should_show_simple_help_for_clap_error(kind: ErrorKind) -> bool { - match kind { - // 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 { @@ -74,314 +38,527 @@ impl Color { } } -/// Apply color to text using ANSI escape codes -fn colorize(text: &str, color: Color) -> String { - format!("\x1b[{}m{text}\x1b[0m", color.code()) +/// Determine color choice based on environment variables +fn get_color_choice() -> clap::ColorChoice { + if std::env::var("NO_COLOR").is_ok() { + clap::ColorChoice::Never + } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() { + clap::ColorChoice::Always + } else { + clap::ColorChoice::Auto + } } -/// Handle DisplayHelp and DisplayVersion errors -fn handle_display_errors(err: Error) -> ! { - match err.kind() { - 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 +/// Generic helper to check if colors should be enabled for a given stream +fn should_use_color_for_stream(stream: &S) -> bool { + match get_color_choice() { + clap::ColorChoice::Always => true, + clap::ColorChoice::Never => false, + clap::ColorChoice::Auto => { + stream.is_terminal() && std::env::var("TERM").unwrap_or_default() != "dumb" + } + } +} - let help_text = err.render().to_string(); +/// Manages color output based on environment settings +struct ColorManager(bool); - // 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}:")); +impl ColorManager { + /// Create a new ColorManager based on environment variables + fn from_env() -> Self { + Self(should_use_color_for_stream(&std::io::stderr())) + } - print!("{localized_help}"); - } - ErrorKind::DisplayVersion => { - // For version, use clap's built-in formatting and exit with 0 - // Output to stdout as expected by tests - print!("{}", err.render()); + /// Apply color to text if colors are enabled + fn colorize(&self, text: &str, color: Color) -> String { + if self.0 { + format!("\x1b[{}m{text}\x1b[0m", color.code()) + } else { + text.to_string() } - _ => unreachable!("handle_display_errors called with non-display error"), } - std::process::exit(0); } -/// Handle UnknownArgument errors with localization and suggestions -fn handle_unknown_argument_error( - err: Error, - util_name: &str, - maybe_colorize: impl Fn(&str, Color) -> String, -) -> ! { - if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { - let arg_str = invalid_arg.to_string(); - // Get localized error word with fallback - let error_word = translate!("common-error"); +/// Unified error formatter that handles all error types consistently +pub struct ErrorFormatter<'a> { + color_mgr: ColorManager, + util_name: &'a str, +} - let colored_arg = maybe_colorize(&arg_str, Color::Yellow); - let colored_error_word = maybe_colorize(&error_word, Color::Red); +impl<'a> ErrorFormatter<'a> { + pub fn new(util_name: &'a str) -> Self { + Self { + color_mgr: ColorManager::from_env(), + util_name, + } + } - // Print main error message with fallback - let error_msg = translate!( - "clap-error-unexpected-argument", - "arg" => colored_arg.clone(), - "error_word" => colored_error_word.clone() - ); - eprintln!("{error_msg}"); - eprintln!(); + /// Print error and exit with the specified code + fn print_error_and_exit(&self, err: &Error, exit_code: i32) -> ! { + self.print_error_and_exit_with_callback(err, exit_code, || {}) + } - // Show suggestion if available - if let Some(suggested_arg) = err.get(ContextKind::SuggestedArg) { - 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 = translate!( - "clap-error-similar-argument", - "tip_word" => colored_tip_word.clone(), - "suggestion" => colored_suggestion.clone() - ); - eprintln!("{suggestion_msg}"); - eprintln!(); - } else { - // 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(); - - // Look for other clap tips (like "-- --file-with-dash") that aren't suggestions - // These usually start with " tip:" and contain useful information - for line in rendered_str.lines() { - if line.trim_start().starts_with("tip:") && !line.contains("similar argument") { - eprintln!("{line}"); - eprintln!(); - } + /// Print error with optional callback before exit + pub fn print_error_and_exit_with_callback( + &self, + err: &Error, + exit_code: i32, + callback: F, + ) -> ! + where + F: FnOnce(), + { + match err.kind() { + ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => self.handle_display_errors(err), + ErrorKind::UnknownArgument => { + self.handle_unknown_argument_with_callback(err, exit_code, callback) + } + ErrorKind::InvalidValue | ErrorKind::ValueValidation => { + self.handle_invalid_value_with_callback(err, exit_code, callback) + } + ErrorKind::MissingRequiredArgument => { + self.handle_missing_required_with_callback(err, exit_code, callback) } + ErrorKind::TooFewValues | ErrorKind::TooManyValues | ErrorKind::WrongNumberOfValues => { + // These need full clap formatting + eprint!("{}", err.render()); + callback(); + std::process::exit(exit_code); + } + _ => self.handle_generic_error_with_callback(err, exit_code, callback), } - - // 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 = translate!("common-usage"); - eprintln!("{usage_label}: {formatted_usage}"); - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); - } else { - // Generic fallback case - let error_word = translate!("common-error"); - let colored_error_word = maybe_colorize(&error_word, Color::Red); - eprintln!("{colored_error_word}: unexpected argument"); } - // 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, - }; - - std::process::exit(exit_code); -} - -/// Handle InvalidValue and ValueValidation errors with localization -fn handle_invalid_value_error(err: Error, maybe_colorize: impl Fn(&str, Color) -> String) -> ! { - // 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(); + /// Handle help and version display + fn handle_display_errors(&self, err: &Error) -> ! { + print!("{}", err.render()); + std::process::exit(0); + } - // 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 + /// Handle unknown argument errors with callback + fn handle_unknown_argument_with_callback( + &self, + err: &Error, + exit_code: i32, + callback: F, + ) -> ! + where + F: FnOnce(), + { + if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) { + let arg_str = invalid_arg.to_string(); let error_word = translate!("common-error"); + + // Print main error eprintln!( "{}", - translate!("clap-error-value-required", "error_word" => error_word, "option" => option) + translate!( + "clap-error-unexpected-argument", + "arg" => self.color_mgr.colorize(&arg_str, Color::Yellow), + "error_word" => self.color_mgr.colorize(&error_word, Color::Red) + ) ); + eprintln!(); + + // Show suggestion if available + if let Some(suggested_arg) = err.get(ContextKind::SuggestedArg) { + let tip_word = translate!("common-tip"); + eprintln!( + "{}", + translate!( + "clap-error-similar-argument", + "tip_word" => self.color_mgr.colorize(&tip_word, Color::Green), + "suggestion" => self.color_mgr.colorize(&suggested_arg.to_string(), Color::Green) + ) + ); + eprintln!(); + } else { + // Look for other tips from clap + self.print_clap_tips(err); + } + + self.print_usage_and_help(); } else { - // Get localized error word and prepare message components outside conditionals - let error_word = translate!("common-error"); - let colored_error_word = maybe_colorize(&error_word, Color::Red); - let colored_value = maybe_colorize(&value, Color::Yellow); - let colored_option = maybe_colorize(&option, Color::Green); - - let error_msg = translate!( - "clap-error-invalid-value", - "error_word" => colored_error_word, - "value" => colored_value, - "option" => colored_option + self.print_simple_error_with_callback( + &translate!("clap-error-unexpected-argument-simple"), + exit_code, + || {}, ); + } + callback(); + std::process::exit(exit_code); + } + + /// Handle invalid value errors with callback + fn handle_invalid_value_with_callback(&self, err: &Error, exit_code: i32, callback: F) -> ! + where + F: FnOnce(), + { + 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(); + + if value.is_empty() { + // Value required but not provided + let error_word = translate!("common-error"); + eprintln!( + "{}", + translate!("clap-error-value-required", + "error_word" => self.color_mgr.colorize(&error_word, Color::Red), + "option" => self.color_mgr.colorize(&option, Color::Green)) + ); + } else { + // Invalid value provided + let error_word = translate!("common-error"); + let error_msg = translate!( + "clap-error-invalid-value", + "error_word" => self.color_mgr.colorize(&error_word, Color::Red), + "value" => self.color_mgr.colorize(&value, Color::Yellow), + "option" => self.color_mgr.colorize(&option, Color::Green) + ); + + // Include validation error if present + match err.source() { + Some(source) if matches!(err.kind(), ErrorKind::ValueValidation) => { + eprintln!("{error_msg}: {source}"); + } + _ => eprintln!("{error_msg}"), + } + } - // For ValueValidation errors, include the validation error in the message - match err.source() { - Some(source) if matches!(err.kind(), ErrorKind::ValueValidation) => { - eprintln!("{error_msg}: {source}"); + // Show possible values for InvalidValue errors + if matches!(err.kind(), ErrorKind::InvalidValue) { + if let Some(valid_values) = err.get(ContextKind::ValidValue) { + if !valid_values.to_string().is_empty() { + eprintln!(); + eprintln!( + " [{}: {valid_values}]", + translate!("clap-error-possible-values") + ); + } } - _ => eprintln!("{error_msg}"), } + + eprintln!(); + eprintln!("{}", translate!("common-help-suggestion")); + } else { + self.print_simple_error(&err.render().to_string(), exit_code); } - // 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!(err.kind(), ErrorKind::InvalidValue) { - if let Some(valid_values) = err.get(ContextKind::ValidValue) { - if !valid_values.to_string().is_empty() { - // Don't show possible values if they are empty - eprintln!(); - let possible_values_label = translate!("clap-error-possible-values"); - eprintln!(" [{possible_values_label}: {valid_values}]"); + // InvalidValue errors traditionally use exit code 1 for backward compatibility + // But if a utility explicitly requests a high exit code (>= 125), respect it + // This allows utilities like runcon (125) to override the default while preserving + // the standard behavior for utilities using normal error codes (1, 2, etc.) + let actual_exit_code = if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 { + 1 // Force exit code 1 for InvalidValue unless using special exit codes + } else { + exit_code // Respect the requested exit code for special cases + }; + callback(); + std::process::exit(actual_exit_code); + } + + /// Handle missing required argument errors with callback + fn handle_missing_required_with_callback( + &self, + err: &Error, + exit_code: i32, + callback: F, + ) -> ! + where + F: FnOnce(), + { + let rendered_str = err.render().to_string(); + let lines: Vec<&str> = rendered_str.lines().collect(); + + match lines.first() { + Some(first_line) + if first_line + .starts_with("error: the following required arguments were not provided:") => + { + let error_word = translate!("common-error"); + eprintln!( + "{}", + translate!( + "clap-error-missing-required-arguments", + "error_word" => self.color_mgr.colorize(&error_word, Color::Red) + ) + ); + + // Print the missing arguments + for line in lines.iter().skip(1) { + if line.starts_with(" ") { + eprintln!("{line}"); + } else if line.starts_with("Usage:") || line.starts_with("For more information") + { + break; + } } + eprintln!(); + + // Print usage + lines + .iter() + .skip_while(|line| !line.starts_with("Usage:")) + .for_each(|line| { + if line.starts_with("For more information, try '--help'.") { + eprintln!("{}", translate!("common-help-suggestion")); + } else { + eprintln!("{line}"); + } + }); } + _ => eprint!("{}", err.render()), } + callback(); + std::process::exit(exit_code); + } - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); - } else { - // Fallback if we can't extract context - use clap's default formatting + /// Handle generic errors with callback + fn handle_generic_error_with_callback(&self, err: &Error, exit_code: i32, callback: F) -> ! + where + F: FnOnce(), + { let rendered_str = err.render().to_string(); - if let Some(main_error_line) = rendered_str.lines().next() { - eprintln!("{main_error_line}"); + self.print_localized_error_line(main_error_line); eprintln!(); eprintln!("{}", translate!("common-help-suggestion")); } else { eprint!("{}", err.render()); } + callback(); + std::process::exit(exit_code); } - std::process::exit(1); -} -pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! { - // 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["); + /// Print a simple error message + fn print_simple_error(&self, message: &str, exit_code: i32) -> ! { + self.print_simple_error_with_callback(message, exit_code, || {}) + } - // Helper function to conditionally colorize text - let maybe_colorize = |text: &str, color: Color| -> String { - if colors_enabled { - colorize(text, color) - } else { - text.to_string() - } - }; + /// Print a simple error message with callback + fn print_simple_error_with_callback(&self, message: &str, exit_code: i32, callback: F) -> ! + where + F: FnOnce(), + { + let error_word = translate!("common-error"); + eprintln!( + "{}: {message}", + self.color_mgr.colorize(&error_word, Color::Red) + ); + callback(); + std::process::exit(exit_code); + } - match err.kind() { - ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { - handle_display_errors(err); - } - ErrorKind::UnknownArgument => { - handle_unknown_argument_error(err, util_name, maybe_colorize); + /// Print error line with localized "error:" prefix + fn print_localized_error_line(&self, line: &str) { + let error_word = translate!("common-error"); + let colored_error = self.color_mgr.colorize(&error_word, Color::Red); + + if let Some(colon_pos) = line.find(':') { + let after_colon = &line[colon_pos..]; + eprintln!("{colored_error}{after_colon}"); + } else { + eprintln!("{line}"); } - // Check if this is a simple validation error that should show simple help - kind if should_show_simple_help_for_clap_error(kind) => { - // Special handling for InvalidValue and ValueValidation to provide localized error - if matches!(kind, ErrorKind::InvalidValue | ErrorKind::ValueValidation) { - handle_invalid_value_error(err, maybe_colorize); - } + } - // For other simple validation errors, use the same simple format as other errors - if let Some(main_error_line) = rendered_str.lines().next() { - // Keep the "error: " prefix for test compatibility - eprintln!("{main_error_line}"); + /// Extract and print clap's built-in tips + fn print_clap_tips(&self, err: &Error) { + let rendered_str = err.render().to_string(); + for line in rendered_str.lines() { + let trimmed = line.trim_start(); + if trimmed.starts_with("tip:") && !line.contains("similar argument") { + let tip_word = translate!("common-tip"); + if let Some(colon_pos) = trimmed.find(':') { + let after_colon = &trimmed[colon_pos..]; + eprintln!( + " {}{after_colon}", + self.color_mgr.colorize(&tip_word, Color::Green) + ); + } else { + eprintln!("{line}"); + } eprintln!(); - // Use the execution phrase for the help suggestion to match test expectations - eprintln!("{}", translate!("common-help-suggestion")); - } else { - // Fallback to original rendering if we can't parse - eprint!("{}", err.render()); } - - // 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 - 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(); - - // Print error message (first line) - if let Some(first_line) = rendered_str.lines().next() { - eprintln!("{first_line}"); - } - - // For other errors, just show help suggestion - eprintln!(); - eprintln!("{}", translate!("common-help-suggestion")); + } - std::process::exit(exit_code); - } + /// Print usage information and help suggestion + fn print_usage_and_help(&self) { + let usage_key = format!("{}-usage", self.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!(); + eprintln!("{}", translate!("common-help-suggestion")); } } -/// 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; - - /// 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; - - /// Get matches from mutable args with localized error handling - fn get_matches_from_mut_localized(self, itr: I) -> ArgMatches - where - Self: Sized, - I: IntoIterator, - T: Into + Clone; +/// Handles clap command parsing results with proper localization support. +/// +/// This is the main entry point for processing command-line arguments with localized error messages. +/// It parses the provided arguments and returns either the parsed matches or handles errors with +/// localized messages. +/// +/// # Arguments +/// +/// * `cmd` - The clap `Command` to parse arguments against +/// * `itr` - An iterator of command-line arguments to parse +/// +/// # Returns +/// +/// * `Ok(ArgMatches)` - Successfully parsed command-line arguments +/// * `Err` - For help/version display (preserves original styling) +/// +/// # Examples +/// +/// ```no_run +/// use clap::Command; +/// use uucore::clap_localization::handle_clap_result; +/// +/// let cmd = Command::new("myutil"); +/// let args = vec!["myutil", "--help"]; +/// let result = handle_clap_result(cmd, args); +/// ``` +pub fn handle_clap_result(cmd: Command, itr: I) -> UResult +where + I: IntoIterator, + T: Into + Clone, +{ + handle_clap_result_with_exit_code(cmd, itr, 1) } -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)) - } +/// Handles clap command parsing with a custom exit code for errors. +/// +/// Similar to `handle_clap_result` but allows specifying a custom exit code +/// for error conditions. This is useful for utilities that need specific +/// exit codes for different error types. +/// +/// # Arguments +/// +/// * `cmd` - The clap `Command` to parse arguments against +/// * `itr` - An iterator of command-line arguments to parse +/// * `exit_code` - The exit code to use when exiting due to an error +/// +/// # Returns +/// +/// * `Ok(ArgMatches)` - Successfully parsed command-line arguments +/// * `Err` - For help/version display (preserves original styling) +/// +/// # Exit Behavior +/// +/// This function will call `std::process::exit()` with the specified exit code +/// when encountering parsing errors (except help/version which use exit code 0). +/// +/// # Examples +/// +/// ```no_run +/// use clap::Command; +/// use uucore::clap_localization::handle_clap_result_with_exit_code; +/// +/// let cmd = Command::new("myutil"); +/// let args = vec!["myutil", "--invalid"]; +/// let result = handle_clap_result_with_exit_code(cmd, args, 125); +/// ``` +pub fn handle_clap_result_with_exit_code( + cmd: Command, + itr: I, + exit_code: i32, +) -> UResult +where + I: IntoIterator, + T: Into + Clone, +{ + cmd.try_get_matches_from(itr).map_err(|e| { + if e.exit_code() == 0 { + e.into() // Preserve help/version + } else { + handle_clap_error_with_exit_code(e, exit_code) + } + }) +} - fn 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)) - } +/// Handles a clap error directly with a custom exit code. +/// +/// This function processes a clap error and exits the program with the specified +/// exit code. It formats error messages with proper localization and color support +/// based on environment variables. +/// +/// # Arguments +/// +/// * `err` - The clap `Error` to handle +/// * `exit_code` - The exit code to use when exiting +/// +/// # Panics +/// +/// This function never returns - it always calls `std::process::exit()`. +/// +/// # Examples +/// +/// ```no_run +/// use clap::Command; +/// use uucore::clap_localization::handle_clap_error_with_exit_code; +/// +/// let cmd = Command::new("myutil"); +/// match cmd.try_get_matches() { +/// Ok(matches) => { /* handle matches */ }, +/// Err(e) => handle_clap_error_with_exit_code(e, 1), +/// } +/// ``` +pub fn handle_clap_error_with_exit_code(err: Error, exit_code: i32) -> ! { + let formatter = ErrorFormatter::new(crate::util_name()); + formatter.print_error_and_exit(&err, exit_code); +} - fn 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)) - } +/// Configures a clap `Command` with proper localization and color settings. +/// +/// This function sets up a `Command` with: +/// - Appropriate color settings based on environment variables (`NO_COLOR`, `CLICOLOR_FORCE`, etc.) +/// - Localized help template with proper formatting +/// - TTY detection for automatic color enabling/disabling +/// +/// # Arguments +/// +/// * `cmd` - The clap `Command` to configure +/// +/// # Returns +/// +/// The configured `Command` with localization and color settings applied. +/// +/// # Environment Variables +/// +/// The following environment variables affect color output: +/// - `NO_COLOR` - Disables all color output +/// - `CLICOLOR_FORCE` or `FORCE_COLOR` - Forces color output even when not in a TTY +/// - `TERM` - If set to "dumb", colors are disabled in auto mode +/// +/// # Examples +/// +/// ```no_run +/// use clap::Command; +/// use uucore::clap_localization::configure_localized_command; +/// +/// let cmd = Command::new("myutil") +/// .arg(clap::Arg::new("input").short('i')); +/// let configured_cmd = configure_localized_command(cmd); +/// ``` +pub fn configure_localized_command(mut cmd: Command) -> Command { + let color_choice = get_color_choice(); + cmd = cmd.color(color_choice); + + // For help output (stdout), we check stdout TTY status + let colors_enabled = should_use_color_for_stream(&std::io::stdout()); + + cmd = cmd.help_template(crate::localized_help_template_with_colors( + crate::util_name(), + colors_enabled, + )); + cmd } /* spell-checker: disable */ @@ -399,15 +576,14 @@ mod tests { } #[test] - fn test_colorize() { - let red_text = colorize("error", Color::Red); + fn test_color_manager() { + let mgr = ColorManager(true); + let red_text = mgr.colorize("error", Color::Red); assert_eq!(red_text, "\x1b[31merror\x1b[0m"); - let yellow_text = colorize("warning", Color::Yellow); - assert_eq!(yellow_text, "\x1b[33mwarning\x1b[0m"); - - let green_text = colorize("success", Color::Green); - assert_eq!(green_text, "\x1b[32msuccess\x1b[0m"); + let mgr_disabled = ColorManager(false); + let plain_text = mgr_disabled.colorize("error", Color::Red); + assert_eq!(plain_text, "error"); } fn create_test_command() -> Command { @@ -435,207 +611,104 @@ mod tests { } #[test] - fn test_get_matches_from_localized_with_valid_args() { - let result = std::panic::catch_unwind(|| { - let cmd = create_test_command(); - let matches = cmd.get_matches_from_localized(vec!["test", "--input", "file.txt"]); - matches.get_one::("input").unwrap().clone() - }); - - if let Ok(input_value) = result { - assert_eq!(input_value, "file.txt"); - } - } - - #[test] - fn test_get_matches_from_localized_with_osstring_args() { - let args: Vec = vec!["test".into(), "--input".into(), "test.txt".into()]; - - let result = std::panic::catch_unwind(|| { - let cmd = create_test_command(); - let matches = cmd.get_matches_from_localized(args); - matches.get_one::("input").unwrap().clone() - }); - - if let Ok(input_value) = result { - assert_eq!(input_value, "test.txt"); - } - } - - #[test] - fn test_localized_command_from_mut() { - let args: Vec = vec!["test".into(), "--output".into(), "result.txt".into()]; - - let result = std::panic::catch_unwind(|| { - let cmd = create_test_command(); - let matches = cmd.get_matches_from_mut_localized(args); - matches.get_one::("output").unwrap().clone() - }); - - if let Ok(output_value) = result { - assert_eq!(output_value, "result.txt"); - } - } - - fn create_unknown_argument_error() -> Error { + fn test_handle_clap_result_with_valid_args() { let cmd = create_test_command(); - cmd.try_get_matches_from(vec!["test", "--unknown-arg"]) - .unwrap_err() + let result = handle_clap_result(cmd, vec!["test", "--input", "file.txt"]); + assert!(result.is_ok()); + let matches = result.unwrap(); + assert_eq!(matches.get_one::("input").unwrap(), "file.txt"); } - fn create_invalid_value_error() -> Error { - let cmd = create_test_command(); - cmd.try_get_matches_from(vec!["test", "--format", "invalid"]) - .unwrap_err() - } - - fn create_help_error() -> Error { + #[test] + fn test_handle_clap_result_with_osstring() { + let args: Vec = vec!["test".into(), "--output".into(), "out.txt".into()]; let cmd = create_test_command(); - cmd.try_get_matches_from(vec!["test", "--help"]) - .unwrap_err() - } - - fn create_version_error() -> Error { - let cmd = Command::new("test").version("1.0.0"); - cmd.try_get_matches_from(vec!["test", "--version"]) - .unwrap_err() + let result = handle_clap_result(cmd, args); + assert!(result.is_ok()); + let matches = result.unwrap(); + assert_eq!(matches.get_one::("output").unwrap(), "out.txt"); } #[test] - fn test_error_kind_detection() { - let unknown_err = create_unknown_argument_error(); - assert_eq!(unknown_err.kind(), ErrorKind::UnknownArgument); - - let invalid_value_err = create_invalid_value_error(); - assert_eq!(invalid_value_err.kind(), ErrorKind::InvalidValue); - - let help_err = create_help_error(); - assert_eq!(help_err.kind(), ErrorKind::DisplayHelp); - - let version_err = create_version_error(); - assert_eq!(version_err.kind(), ErrorKind::DisplayVersion); + fn test_configure_localized_command() { + let cmd = Command::new("test"); + let configured = configure_localized_command(cmd); + // The command should have color and help template configured + // We can't easily test the internal state, but we can verify it doesn't panic + assert_eq!(configured.get_name(), "test"); } #[test] - fn test_context_extraction() { - let unknown_err = create_unknown_argument_error(); - let invalid_arg = unknown_err.get(ContextKind::InvalidArg); - assert!(invalid_arg.is_some()); - assert!(invalid_arg.unwrap().to_string().contains("unknown-arg")); - - let invalid_value_err = create_invalid_value_error(); - let invalid_value = invalid_value_err.get(ContextKind::InvalidValue); - assert!(invalid_value.is_some()); - assert_eq!(invalid_value.unwrap().to_string(), "invalid"); - } - - fn test_maybe_colorize_helper(colors_enabled: bool) { - let maybe_colorize = |text: &str, color: Color| -> String { - if colors_enabled { - colorize(text, color) - } else { - text.to_string() - } - }; + fn test_color_environment_vars() { + use std::env; - let result = maybe_colorize("test", Color::Red); - if colors_enabled { - assert!(result.contains("\x1b[31m")); - assert!(result.contains("\x1b[0m")); - } else { - assert_eq!(result, "test"); + // Test NO_COLOR disables colors + unsafe { + env::set_var("NO_COLOR", "1"); + } + assert_eq!(get_color_choice(), clap::ColorChoice::Never); + assert!(!should_use_color_for_stream(&std::io::stderr())); + let mgr = ColorManager::from_env(); + assert!(!mgr.0); + unsafe { + env::remove_var("NO_COLOR"); } - } - #[test] - fn test_maybe_colorize_with_colors() { - test_maybe_colorize_helper(true); - } + // Test CLICOLOR_FORCE enables colors + unsafe { + env::set_var("CLICOLOR_FORCE", "1"); + } + assert_eq!(get_color_choice(), clap::ColorChoice::Always); + assert!(should_use_color_for_stream(&std::io::stderr())); + let mgr = ColorManager::from_env(); + assert!(mgr.0); + unsafe { + env::remove_var("CLICOLOR_FORCE"); + } - #[test] - fn test_maybe_colorize_without_colors() { - test_maybe_colorize_helper(false); + // Test FORCE_COLOR also enables colors + unsafe { + env::set_var("FORCE_COLOR", "1"); + } + assert_eq!(get_color_choice(), clap::ColorChoice::Always); + assert!(should_use_color_for_stream(&std::io::stderr())); + unsafe { + env::remove_var("FORCE_COLOR"); + } } #[test] - fn test_simple_help_classification() { - let simple_help_kinds = [ - ErrorKind::InvalidValue, - ErrorKind::ValueValidation, - ErrorKind::InvalidSubcommand, - ErrorKind::InvalidUtf8, - ErrorKind::ArgumentConflict, - ErrorKind::NoEquals, - ErrorKind::Io, - ErrorKind::Format, - ]; - - let non_simple_help_kinds = [ - ErrorKind::TooFewValues, - ErrorKind::TooManyValues, - ErrorKind::WrongNumberOfValues, - ErrorKind::MissingSubcommand, - ErrorKind::MissingRequiredArgument, - ErrorKind::DisplayHelp, - ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, - ErrorKind::DisplayVersion, - ErrorKind::UnknownArgument, - ]; - - for kind in &simple_help_kinds { - assert!( - should_show_simple_help_for_clap_error(*kind), - "Expected {:?} to show simple help", - kind - ); - } - - for kind in &non_simple_help_kinds { - assert!( - !should_show_simple_help_for_clap_error(*kind), - "Expected {:?} to NOT show simple help", - kind - ); - } + fn test_error_formatter_creation() { + let formatter = ErrorFormatter::new("test"); + assert_eq!(formatter.util_name, "test"); + // Color manager should be created based on environment } #[test] - fn test_localization_setup() { + fn test_localization_keys_exist() { use crate::locale::{get_message, setup_localization}; let _ = setup_localization("test"); - let common_keys = [ + let required_keys = [ "common-error", "common-usage", + "common-tip", "common-help-suggestion", "clap-error-unexpected-argument", "clap-error-invalid-value", + "clap-error-missing-required-arguments", + "clap-error-similar-argument", + "clap-error-possible-values", + "clap-error-value-required", ]; - for key in &common_keys { + + for key in &required_keys { let message = get_message(key); - assert_ne!(message, *key, "Translation not found for key: {}", key); + assert_ne!(message, *key, "Translation missing for key: {}", key); } } - #[test] - fn test_localization_with_args() { - use crate::locale::{get_message_with_args, setup_localization}; - use fluent::FluentArgs; - - let _ = setup_localization("test"); - - let mut args = FluentArgs::new(); - args.set("error_word", "ERROR"); - args.set("arg", "--test"); - - let message = get_message_with_args("clap-error-unexpected-argument", args); - assert_ne!( - message, "clap-error-unexpected-argument", - "Translation not found for key: clap-error-unexpected-argument" - ); - } - #[test] fn test_french_localization() { use crate::locale::{get_message, setup_localization}; @@ -644,62 +717,13 @@ mod tests { let original_lang = env::var("LANG").unwrap_or_default(); unsafe { - env::set_var("LANG", "fr-FR"); - } - let result = setup_localization("test"); - - if result.is_ok() { - let error_word = get_message("common-error"); - assert_eq!(error_word, "erreur"); - - let usage_word = get_message("common-usage"); - assert_eq!(usage_word, "Utilisation"); - - let tip_word = get_message("common-tip"); - assert_eq!(tip_word, "conseil"); - } - - unsafe { - if original_lang.is_empty() { - env::remove_var("LANG"); - } else { - env::set_var("LANG", original_lang); - } + env::set_var("LANG", "fr_FR.UTF-8"); } - } - #[test] - fn test_french_clap_error_messages() { - use crate::locale::{get_message_with_args, setup_localization}; - use fluent::FluentArgs; - use std::env; - - let original_lang = env::var("LANG").unwrap_or_default(); - - unsafe { - env::set_var("LANG", "fr-FR"); - } - let result = setup_localization("test"); - - if result.is_ok() { - let mut args = FluentArgs::new(); - args.set("error_word", "erreur"); - args.set("arg", "--inconnu"); - - let unexpected_msg = get_message_with_args("clap-error-unexpected-argument", args); - assert!(unexpected_msg.contains("erreur")); - assert!(unexpected_msg.contains("--inconnu")); - assert!(unexpected_msg.contains("inattendu")); - - let mut value_args = FluentArgs::new(); - value_args.set("error_word", "erreur"); - value_args.set("value", "invalide"); - value_args.set("option", "--format"); - - let invalid_msg = get_message_with_args("clap-error-invalid-value", value_args); - assert!(invalid_msg.contains("erreur")); - assert!(invalid_msg.contains("invalide")); - assert!(invalid_msg.contains("--format")); + if setup_localization("test").is_ok() { + assert_eq!(get_message("common-error"), "erreur"); + assert_eq!(get_message("common-usage"), "Utilisation"); + assert_eq!(get_message("common-tip"), "conseil"); } unsafe { diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 0afbcaee099..52b0779d90b 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -1275,3 +1275,35 @@ fn test_chmod_non_utf8_paths() { 0o644 ); } + +#[test] +fn test_chmod_colored_output() { + // Test colored help message + new_ucmd!() + .arg("--help") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "en_US.UTF-8") + .succeeds() + .stdout_contains("\x1b[1m\x1b[4mUsage:\x1b[0m") // Bold+underline "Usage:" + .stdout_contains("\x1b[1m\x1b[4mArguments:\x1b[0m"); // Bold+underline "Arguments:" + + // Test colored error message for invalid option + new_ucmd!() + .arg("--invalid-option") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "en_US.UTF-8") + .fails() + .code_is(1) + .stderr_contains("\x1b[31merror\x1b[0m") // Red "error" + .stderr_contains("\x1b[33m--invalid-option\x1b[0m"); // Yellow invalid option + + // Test French localized colored error message + new_ucmd!() + .arg("--invalid-option") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "fr_FR.UTF-8") + .fails() + .code_is(1) + .stderr_contains("\x1b[31merreur\x1b[0m") // Red "erreur" in French + .stderr_contains("\x1b[33m--invalid-option\x1b[0m"); // Yellow invalid option +} diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 9c04f39c6a4..5038d529ca6 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -574,7 +574,7 @@ fn test_both_inputs_out_of_order_but_identical() { } #[test] -fn test_comm_extra_arg_error() { +fn test_comm_arg_error() { let scene = TestScenario::new(util_name!()); // Test extra argument error case from GNU test @@ -586,4 +586,11 @@ fn test_comm_extra_arg_error() { .stderr_contains("error: unexpected argument 'no-such' found") .stderr_contains("Usage: comm [OPTION]... FILE1 FILE2") .stderr_contains("For more information, try '--help'."); + // Test extra argument error case from GNU test + scene + .ucmd() + .args(&["a"]) + .fails() + .code_is(1) + .stderr_is("error: the following required arguments were not provided:\n \n\nUsage: comm [OPTION]... FILE1 FILE2\n\nFor more information, try '--help'.\n"); } diff --git a/tests/by-util/test_dir.rs b/tests/by-util/test_dir.rs index ef455c6bd8e..0d77de7a0e7 100644 --- a/tests/by-util/test_dir.rs +++ b/tests/by-util/test_dir.rs @@ -51,3 +51,8 @@ fn test_long_output() { .succeeds() .stdout_matches(&Regex::new("[rwx-]{10}.*some-file1\n$").unwrap()); } + +#[test] +fn test_invalid_option_exit_code() { + new_ucmd!().arg("-/").fails().code_is(2); +} diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 82cec188976..64290055dc8 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -1773,3 +1773,20 @@ fn test_simulation_of_terminal_pty_write_in_data_and_sends_eot_automatically() { ); std::assert_eq!(String::from_utf8_lossy(out.stderr()), ""); } + +#[test] +fn test_env_french() { + new_ucmd!() + .arg("--verbo") + .env("LANG", "fr_FR") + .fails() + .stderr_contains("erreur : argument inattendu"); +} + +#[test] +fn test_shebang_error() { + new_ucmd!() + .arg("\'-v \'") + .fails() + .stderr_contains("use -[v]S to pass options in shebang lines"); +} diff --git a/tests/by-util/test_runcon.rs b/tests/by-util/test_runcon.rs index a53c7302b65..39a73d8832e 100644 --- a/tests/by-util/test_runcon.rs +++ b/tests/by-util/test_runcon.rs @@ -51,7 +51,14 @@ fn invalid() { "unconfined_u:unconfined_r:unconfined_t:s0", "inexistent-file", ]; - new_ucmd!().args(args).fails_with_code(127); + // When SELinux is enabled, this should fail with 127 (command not found) + // When SELinux is not enabled, this fails with 1 (SELinux not enabled error) + let expected_code = if uucore::selinux::is_selinux_enabled() { + 127 + } else { + 1 + }; + new_ucmd!().args(args).fails_with_code(expected_code); let args = &["invalid", "/bin/true"]; new_ucmd!().args(args).fails_with_code(1); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 28518396dae..8bf4f7adef1 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1721,4 +1721,187 @@ fn test_clap_localization_invalid_value() { } } +#[test] +fn test_help_colors_enabled() { + // Test that help messages have ANSI color codes when colors are forced + let test_cases = vec![("en_US.UTF-8", "Usage"), ("fr_FR.UTF-8", "Utilisation")]; + + for (locale, usage_word) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .env("CLICOLOR_FORCE", "1") + .arg("--help") + .succeeds(); + + let stdout = result.stdout_str(); + + // Check for ANSI bold+underline codes around the usage header + let expected_pattern = format!("\x1b[1m\x1b[4m{usage_word}:\x1b[0m"); + assert!( + stdout.contains(&expected_pattern), + "Expected bold+underline '{usage_word}:' in locale {locale}, got: {}", + stdout.lines().take(10).collect::>().join("\\n") + ); + } +} + +#[test] +fn test_help_colors_disabled() { + // Test that help messages don't have ANSI color codes when colors are disabled + let test_cases = vec![("en_US.UTF-8", "Usage"), ("fr_FR.UTF-8", "Utilisation")]; + + for (locale, usage_word) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .env("NO_COLOR", "1") + .arg("--help") + .succeeds(); + + let stdout = result.stdout_str(); + + // Check that we have the usage word but no ANSI codes + assert!(stdout.contains(&format!("{usage_word}:"))); + assert!( + !stdout.contains("\x1b["), + "Found ANSI escape codes when colors should be disabled in locale {locale}" + ); + } +} + +#[test] +fn test_error_colors_enabled() { + // Test that error messages have ANSI color codes when colors are forced + let test_cases = vec![ + ("en_US.UTF-8", "error", "tip"), + ("fr_FR.UTF-8", "erreur", "conseil"), + ]; + + for (locale, error_word, tip_word) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .env("CLICOLOR_FORCE", "1") + .arg("--numerc") // Typo to trigger suggestion for --numeric-sort + .fails(); + + let stderr = result.stderr_str(); + + // Check for colored error word (red) + let colored_error = format!("\x1b[31m{error_word}\x1b[0m"); + assert!( + stderr.contains(&colored_error), + "Expected red '{error_word}' in locale {locale}, got: {}", + stderr.lines().take(5).collect::>().join("\\n") + ); + + // Check for colored tip word (green) + let colored_tip = format!("\x1b[32m{tip_word}\x1b[0m"); + assert!( + stderr.contains(&colored_tip), + "Expected green '{tip_word}' in locale {locale}, got: {}", + stderr.lines().take(5).collect::>().join("\\n") + ); + } +} + +#[test] +fn test_error_colors_disabled() { + // Test that error messages don't have ANSI color codes when colors are disabled + let test_cases = vec![ + ("en_US.UTF-8", "error", "tip"), + ("fr_FR.UTF-8", "erreur", "conseil"), + ]; + + for (locale, error_word, tip_word) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .env("NO_COLOR", "1") + .arg("--numerc") // Typo to trigger suggestion for --numeric-sort + .fails(); + + let stderr = result.stderr_str(); + + // Check that we have the error and tip words but no ANSI codes + assert!(stderr.contains(error_word)); + assert!(stderr.contains(tip_word)); + assert!( + !stderr.contains("\x1b["), + "Found ANSI escape codes when colors should be disabled in locale {locale}" + ); + } +} + +#[test] +fn test_argument_suggestion_colors_enabled() { + // Test that argument suggestions have colors + let test_cases = vec![ + ("en_US.UTF-8", "tip", "--reverse"), + ("fr_FR.UTF-8", "conseil", "--reverse"), + ]; + + for (locale, _tip_word, suggestion) in test_cases { + let result = new_ucmd!() + .env("LANG", locale) + .env("LC_ALL", locale) + .env("CLICOLOR_FORCE", "1") + .arg("--revrse") // Typo to trigger suggestion + .fails(); + + let stderr = result.stderr_str(); + + // Check for colored invalid argument (yellow) + let colored_invalid = "\x1b[33m--revrse\x1b[0m"; + assert!( + stderr.contains(colored_invalid), + "Expected yellow '--revrse' in locale {locale}, got: {}", + stderr.lines().take(10).collect::>().join("\\n") + ); + + // Check for colored suggestion (green) + let colored_suggestion = format!("\x1b[32m{suggestion}\x1b[0m"); + assert!( + stderr.contains(&colored_suggestion), + "Expected green '{suggestion}' in locale {locale}, got: {}", + stderr.lines().take(10).collect::>().join("\\n") + ); + } +} + +#[test] +fn test_color_environment_variables() { + // Test different color environment variable combinations + let test_env_vars = vec![ + // Colors should be enabled + (vec![("CLICOLOR_FORCE", "1")], true, "CLICOLOR_FORCE=1"), + // Colors should be disabled + (vec![("NO_COLOR", "1")], false, "NO_COLOR=1"), + ( + vec![("NO_COLOR", "1"), ("CLICOLOR_FORCE", "1")], + false, + "NO_COLOR overrides CLICOLOR_FORCE", + ), + ]; + + for (env_vars, should_have_colors, description) in test_env_vars { + let mut cmd = new_ucmd!(); + cmd.env("LANG", "en_US.UTF-8"); + + for (key, value) in env_vars { + cmd.env(key, value); + } + + let result = cmd.arg("--help").succeeds(); + let stdout = result.stdout_str(); + + let has_ansi_codes = stdout.contains("\x1b["); + assert_eq!( + has_ansi_codes, should_have_colors, + "Color test failed for {description}: expected colors={should_have_colors}, found ANSI codes={has_ansi_codes}" + ); + } +} + /* spell-checker: enable */ diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 874081e808b..6c9107ce527 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -889,12 +889,11 @@ fn gnu_tests() { input: "", // Note: Different from GNU test, but should not matter stdout: Some(""), stderr: Some(concat!( - "uniq: invalid argument 'badoption' for '--all-repeated'\n", - "Valid arguments are:\n", - " - 'none'\n", - " - 'prepend'\n", - " - 'separate'\n", - "Try 'uniq --help' for more information.\n" + "error: invalid value 'badoption' for '--all-repeated[=]'\n", + "\n", + " [possible values: none, prepend, separate]\n", + "\n", + "For more information, try '--help'.\n" )), exit: Some(1), }, @@ -1067,8 +1066,9 @@ fn gnu_tests() { input: "", stdout: Some(""), stderr: Some(concat!( - "uniq: --group is mutually exclusive with -c/-d/-D/-u\n", - "Try 'uniq --help' for more information.\n" + "error: the argument '--group[=]' cannot be used with '--count'\n", + "\n", + "For more information, try '--help'.\n" )), exit: Some(1), }, @@ -1078,8 +1078,9 @@ fn gnu_tests() { input: "", stdout: Some(""), stderr: Some(concat!( - "uniq: --group is mutually exclusive with -c/-d/-D/-u\n", - "Try 'uniq --help' for more information.\n" + "error: the argument '--group[=]' cannot be used with '--repeated'\n", + "\n", + "For more information, try '--help'.\n" )), exit: Some(1), }, @@ -1089,8 +1090,9 @@ fn gnu_tests() { input: "", stdout: Some(""), stderr: Some(concat!( - "uniq: --group is mutually exclusive with -c/-d/-D/-u\n", - "Try 'uniq --help' for more information.\n" + "error: the argument '--group[=]' cannot be used with '--unique'\n", + "\n", + "For more information, try '--help'.\n" )), exit: Some(1), }, @@ -1100,8 +1102,9 @@ fn gnu_tests() { input: "", stdout: Some(""), stderr: Some(concat!( - "uniq: --group is mutually exclusive with -c/-d/-D/-u\n", - "Try 'uniq --help' for more information.\n" + "error: the argument '--group[=]' cannot be used with '--all-repeated[=]'\n", + "\n", + "For more information, try '--help'.\n" )), exit: Some(1), }, @@ -1111,13 +1114,11 @@ fn gnu_tests() { input: "", stdout: Some(""), stderr: Some(concat!( - "uniq: invalid argument 'badoption' for '--group'\n", - "Valid arguments are:\n", - " - 'prepend'\n", - " - 'append'\n", - " - 'separate'\n", - " - 'both'\n", - "Try 'uniq --help' for more information.\n" + "error: invalid value 'badoption' for '--group[=]'\n", + "\n", + " [possible values: separate, prepend, append, both]\n", + "\n", + "For more information, try '--help'.\n" )), exit: Some(1), }, diff --git a/tests/by-util/test_vdir.rs b/tests/by-util/test_vdir.rs index cf389f45e1b..a68e3b543df 100644 --- a/tests/by-util/test_vdir.rs +++ b/tests/by-util/test_vdir.rs @@ -51,3 +51,8 @@ fn test_column_output() { .succeeds() .stdout_does_not_match(&Regex::new("[rwx-]{10}.*some-file1$").unwrap()); } + +#[test] +fn test_invalid_option_exit_code() { + new_ucmd!().arg("-/").fails().code_is(2); +} diff --git a/tests/test_localization_and_colors.rs b/tests/test_localization_and_colors.rs new file mode 100644 index 00000000000..677e2e0b75c --- /dev/null +++ b/tests/test_localization_and_colors.rs @@ -0,0 +1,333 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + +use std::collections::HashSet; +use std::env; +use std::process::Command; +use std::str; + +// Use the same binary path as other tests +pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); + +/// Get list of all enabled utilities from the build-time generated map. +/// Uses `include_str!` to read the generated `uutils_map.rs` at compile time, +/// avoiding runtime execution while staying in sync with the actual build. +fn get_all_enabled_utilities() -> Vec { + // Read the generated utility map file at compile time + const UUTILS_MAP: &str = include_str!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); + + // Extract utility names from lines like: ("arch", (arch::uumain, arch::uu_app)), + UUTILS_MAP + .lines() + .filter_map(|line| { + let line = line.trim(); + if line.starts_with("(\"") && line.contains(", (") { + let end_quote = line[2..].find('"')?; + Some(line[2..2 + end_quote].to_string()) + } else { + None + } + }) + .collect() +} + +/// Utilities that should be skipped in tests due to special behavior +fn get_utilities_to_skip() -> HashSet<&'static str> { + let mut skip_set = HashSet::new(); + + // Utilities that don't follow standard help patterns + skip_set.insert("false"); // Always exits with 1 + skip_set.insert("true"); // Always exits with 0, no help + skip_set.insert("["); // Special test utility syntax + skip_set.insert("test"); // By design, doesn't show --help (use [ --help instead) + + // Alias for ls + skip_set.insert("dir"); + skip_set.insert("vdir"); + + // Utilities that don't show standard clap error messages by design + skip_set.insert("echo"); // Prints arguments as-is, doesn't use clap for validation + skip_set.insert("printf"); // Uses custom argument parsing, doesn't show clap errors + skip_set.insert("expr"); // Uses custom argument parsing, doesn't show clap errors + + // Utilities with special error handling that work but don't follow standard patterns + let utilities_with_special_handling = [ + "seq", // Custom numeric validation with localized messages + "tail", // Complex file following logic + "stty", // Terminal-specific error handling + ]; + + for utility in &utilities_with_special_handling { + skip_set.insert(utility); + } + + skip_set +} + +/// Helper function to create a Command for a utility. +/// Uses the multicall binary (`TESTS_BINARY`) and passes the utility name as an argument. +fn create_utility_command(utility_name: &str) -> Command { + let uu_name = format!("uu_{utility_name}"); + let canonical_name = uucore::get_canonical_util_name(&uu_name); + let mut cmd = Command::new(TESTS_BINARY); + cmd.arg(canonical_name); + cmd +} + +/// Test that help messages contain color codes when `CLICOLOR_FORCE=1` +#[test] +fn test_help_messages_have_colors() { + let utilities = get_all_enabled_utilities(); + let skip_utilities = get_utilities_to_skip(); + + for utility in &utilities { + if skip_utilities.contains(utility.as_str()) { + continue; + } + println!("Testing colors for {utility}"); + + let output = create_utility_command(utility) + .arg("--help") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "en_US.UTF-8") + .output(); + + match output { + Ok(result) => { + let stdout = str::from_utf8(&result.stdout).unwrap_or(""); + + // Check for ANSI color codes in help output + // We expect to see bold+underline codes for headers like "Usage:" + let has_colors = stdout.contains("\x1b[1m\x1b[4m") && stdout.contains("\x1b[0m"); + + if !has_colors { + println!("Help output for {utility}:\n{stdout}"); + } + + assert!( + has_colors, + "Utility '{utility}' help message should contain ANSI color codes for headers" + ); + } + Err(e) => { + panic!("Failed to execute {utility} --help: {e}"); + } + } + } +} + +/// Test that error messages contain color codes when `CLICOLOR_FORCE=1` +#[test] +fn test_error_messages_have_colors() { + let utilities = get_all_enabled_utilities(); + let skip_utilities = get_utilities_to_skip(); + + for utility in &utilities { + if skip_utilities.contains(utility.as_str()) { + continue; + } + println!("Testing error colors for {utility}"); + + let mut cmd = create_utility_command(utility); + let uu_name = format!("uu_{utility}"); + let binary_name = uucore::get_canonical_util_name(&uu_name); + + // For hashsum aliases, we need to pass the hash algorithm as a subcommand + if binary_name == "hashsum" && utility != "hashsum" { + // Extract the hash algorithm from the utility name + let algo = utility.trim_end_matches("sum"); + cmd.arg(algo); + } + + let output = cmd + .arg("--invalid-option-that-should-not-exist") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "en_US.UTF-8") + .output(); + + match output { + Ok(result) => { + let stderr = str::from_utf8(&result.stderr).unwrap_or(""); + + // Check for red error text and yellow invalid option + let has_red_error = stderr.contains("\x1b[31merror") && stderr.contains("\x1b[0m"); + let has_yellow_option = + stderr.contains("\x1b[33m--invalid-option-that-should-not-exist\x1b[0m"); + + if !has_red_error || !has_yellow_option { + println!("Error output for {utility}:\n{stderr}"); + } + + assert!( + has_red_error, + "Utility '{utility}' should show red colored 'error' in error messages" + ); + assert!( + has_yellow_option, + "Utility '{utility}' should show yellow colored invalid options in error messages" + ); + } + Err(e) => { + panic!("Failed to execute {utility} with invalid option: {e}"); + } + } + } +} + +/// Test that help messages are translated to French +#[test] +fn test_help_messages_french_translation() { + let utilities = get_all_enabled_utilities(); + let skip_utilities = get_utilities_to_skip(); + + for utility in &utilities { + if skip_utilities.contains(utility.as_str()) { + continue; + } + println!("Testing French translation for {utility}"); + + let output = create_utility_command(utility) + .arg("--help") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .output(); + + match output { + Ok(result) => { + let stdout = str::from_utf8(&result.stdout).unwrap_or(""); + + // Check for French translation of "Usage:" -> "Utilisation:" + let has_french_usage = stdout.contains("Utilisation:"); + + if !has_french_usage { + println!("Help output for {utility} (French):\n{stdout}"); + } + + assert!( + has_french_usage, + "Utility '{utility}' help message should be translated to French (contain 'Utilisation:')" + ); + } + Err(e) => { + panic!("Failed to execute {utility} --help in French: {e}"); + } + } + } +} + +/// Test that error messages are translated to French +#[test] +fn test_error_messages_french_translation() { + let utilities = get_all_enabled_utilities(); + let skip_utilities = get_utilities_to_skip(); + + for utility in &utilities { + if skip_utilities.contains(utility.as_str()) { + continue; + } + println!("Testing French error translation for {utility}"); + + let mut cmd = create_utility_command(utility); + let uu_name = format!("uu_{utility}"); + let binary_name = uucore::get_canonical_util_name(&uu_name); + + // For hashsum aliases, we need to pass the hash algorithm as a subcommand + if binary_name == "hashsum" && utility != "hashsum" { + // Extract the hash algorithm from the utility name + let algo = utility.trim_end_matches("sum"); + cmd.arg(algo); + } + + let output = cmd + .arg("--invalid-option-that-should-not-exist") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .output(); + + match output { + Ok(result) => { + let stderr = str::from_utf8(&result.stderr).unwrap_or(""); + + // Check for French translation of "error" -> "erreur" + let has_french_error = stderr.contains("erreur"); + + if !has_french_error { + println!("Error output for {utility} (French):\n{stderr}"); + } + + assert!( + has_french_error, + "Utility '{utility}' error message should be translated to French (contain 'erreur')" + ); + } + Err(e) => { + panic!("Failed to execute {utility} with invalid option in French: {e}"); + } + } + } +} + +/// Test that colors work with French translations +#[test] +fn test_french_colored_error_messages() { + let utilities = get_all_enabled_utilities(); + let skip_utilities = get_utilities_to_skip(); + + for utility in &utilities { + if skip_utilities.contains(utility.as_str()) { + continue; + } + println!("Testing French colored errors for {utility}"); + + let mut cmd = create_utility_command(utility); + let uu_name = format!("uu_{utility}"); + let binary_name = uucore::get_canonical_util_name(&uu_name); + + // For hashsum aliases, we need to pass the hash algorithm as a subcommand + if binary_name == "hashsum" && utility != "hashsum" { + // Extract the hash algorithm from the utility name + let algo = utility.trim_end_matches("sum"); + cmd.arg(algo); + } + + let output = cmd + .arg("--invalid-option-that-should-not-exist") + .env("CLICOLOR_FORCE", "1") + .env("LANG", "fr_FR.UTF-8") + .env("LC_ALL", "fr_FR.UTF-8") + .output(); + + match output { + Ok(result) => { + let stderr = str::from_utf8(&result.stderr).unwrap_or(""); + + // Check for red colored French error text and yellow invalid option + let has_red_erreur = + stderr.contains("\x1b[31merreur") && stderr.contains("\x1b[0m"); + let has_yellow_option = + stderr.contains("\x1b[33m--invalid-option-that-should-not-exist\x1b[0m"); + + if !has_red_erreur || !has_yellow_option { + println!("French error output for {utility}:\n{stderr}"); + } + + assert!( + has_red_erreur, + "Utility '{utility}' should show red colored 'erreur' in French error messages" + ); + assert!( + has_yellow_option, + "Utility '{utility}' should show yellow colored invalid options in French error messages" + ); + } + Err(e) => { + panic!("Failed to execute {utility} with invalid option in French: {e}"); + } + } + } +} diff --git a/util/gnu-patches/error_msg_uniq.diff b/util/gnu-patches/error_msg_uniq.diff new file mode 100644 index 00000000000..1c9e32fcb22 --- /dev/null +++ b/util/gnu-patches/error_msg_uniq.diff @@ -0,0 +1,58 @@ +Index: gnu/tests/uniq/uniq.pl +=================================================================== +--- gnu.orig/tests/uniq/uniq.pl ++++ gnu/tests/uniq/uniq.pl +@@ -178,12 +178,9 @@ my @Tests = + {IN=>"a\na\nb\nc\nc\n"}, {OUT=>"\na\na\n\nc\nc\n"}], + ['118', '--all-repeated=prepend', {IN=>"a\nb\n"}, {OUT=>""}], + ['119', '--all-repeated=badoption', {IN=>"a\n"}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: invalid argument 'badoption' for '--all-repeated'\n" +- . "Valid arguments are:\n" +- . " - 'none'\n" +- . " - 'prepend'\n" +- . " - 'separate'\n" +- . $try}], ++ {ERR=>"error: invalid value 'badoption' for '--all-repeated[=]'\n\n" ++ . " [possible values: none, prepend, separate]\n\n" ++ . "For more information, try '--help'.\n"}], + # Check that -d and -u suppress all output, as POSIX requires. + ['120', qw(-d -u), {IN=>"a\na\n\b"}, {OUT=>""}], + ['121', "-d -u -w$limits->{UINTMAX_OFLOW}", {IN=>"a\na\n\b"}, {OUT=>""}], +@@ -214,26 +211,22 @@ my @Tests = + ['140', '--group=both', {IN=>""}, {OUT=>""}], + # Grouping with other options - must fail + ['141', '--group -c', {IN=>""}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: --group is mutually exclusive with -c/-d/-D/-u\n" . +- "Try 'uniq --help' for more information.\n"}], ++ {ERR=>"error: the argument '--group[=]' cannot be used with '--count'\n\n" . ++ "For more information, try '--help'.\n"}], + ['142', '--group -d', {IN=>""}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: --group is mutually exclusive with -c/-d/-D/-u\n" . +- "Try 'uniq --help' for more information.\n"}], ++ {ERR=>"error: the argument '--group[=]' cannot be used with '--repeated'\n\n" . ++ "For more information, try '--help'.\n"}], + ['143', '--group -u', {IN=>""}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: --group is mutually exclusive with -c/-d/-D/-u\n" . +- "Try 'uniq --help' for more information.\n"}], ++ {ERR=>"error: the argument '--group[=]' cannot be used with '--unique'\n\n" . ++ "For more information, try '--help'.\n"}], + ['144', '--group -D', {IN=>""}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: --group is mutually exclusive with -c/-d/-D/-u\n" . +- "Try 'uniq --help' for more information.\n"}], ++ {ERR=>"error: the argument '--group[=]' cannot be used with '--all-repeated[=]'\n\n" . ++ "For more information, try '--help'.\n"}], + # Grouping with badoption + ['145', '--group=badoption',{IN=>""}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: invalid argument 'badoption' for '--group'\n" . +- "Valid arguments are:\n" . +- " - 'prepend'\n" . +- " - 'append'\n" . +- " - 'separate'\n" . +- " - 'both'\n" . +- "Try '$prog --help' for more information.\n"}], ++ {ERR=>"error: invalid value 'badoption' for '--group[=]'\n\n" . ++ " [possible values: separate, prepend, append, both]\n\n" . ++ "For more information, try '--help'.\n"}], + ); + + # Locale related tests diff --git a/util/gnu-patches/series b/util/gnu-patches/series index 8927eb04f81..5fb1398cd17 100644 --- a/util/gnu-patches/series +++ b/util/gnu-patches/series @@ -10,3 +10,4 @@ tests_sort_merge.pl.patch tests_tsort.patch tests_du_move_dir_while_traversing.patch test_mkdir_restorecon.patch +error_msg_uniq.diff diff --git a/util/gnu-patches/tests_comm.pl.patch b/util/gnu-patches/tests_comm.pl.patch index 602071f483a..4b683cc893a 100644 --- a/util/gnu-patches/tests_comm.pl.patch +++ b/util/gnu-patches/tests_comm.pl.patch @@ -3,7 +3,7 @@ Index: gnu/tests/misc/comm.pl --- gnu.orig/tests/misc/comm.pl +++ gnu/tests/misc/comm.pl @@ -73,18 +73,24 @@ my @Tests = - + # invalid missing command line argument (1) ['missing-arg1', $inputs[0], {EXIT=>1}, - {ERR => "$prog: missing operand after 'a'\n" @@ -12,7 +12,7 @@ Index: gnu/tests/misc/comm.pl + . " \n\n" + . "Usage: $prog [OPTION]... FILE1 FILE2\n\n" + . "For more information, try '--help'.\n"}], - + # invalid missing command line argument (both) ['missing-arg2', {EXIT=>1}, - {ERR => "$prog: missing operand\n" @@ -22,7 +22,7 @@ Index: gnu/tests/misc/comm.pl + . " \n\n" + . "Usage: $prog [OPTION]... FILE1 FILE2\n\n" + . "For more information, try '--help'.\n"}], - + # invalid extra command line argument ['extra-arg', @inputs, 'no-such', {EXIT=>1}, - {ERR => "$prog: extra operand 'no-such'\n" @@ -30,15 +30,15 @@ Index: gnu/tests/misc/comm.pl + {ERR => "error: unexpected argument 'no-such' found\n\n" + . "Usage: $prog [OPTION]... FILE1 FILE2\n\n" + . "For more information, try '--help'.\n"}], - + # out-of-order input ['ooo', {IN=>{a=>"1\n3"}}, {IN=>{b=>"3\n2"}}, {EXIT=>1}, @@ -163,7 +169,7 @@ my @Tests = - + # invalid dual delimiter ['delim-dual', '--output-delimiter=,', '--output-delimiter=+', @inputs, - {EXIT=>1}, {ERR => "$prog: multiple output delimiters specified\n"}], + {EXIT=>1}, {ERR => "$prog: multiple conflicting output delimiters specified\n"}], - + # valid dual delimiter specification ['delim-dual2', '--output-delimiter=,', '--output-delimiter=,', @inputs, diff --git a/util/gnu-patches/tests_env_env-S.pl.patch b/util/gnu-patches/tests_env_env-S.pl.patch index b3c5d34d11b..45328352d6e 100644 --- a/util/gnu-patches/tests_env_env-S.pl.patch +++ b/util/gnu-patches/tests_env_env-S.pl.patch @@ -42,7 +42,7 @@ Index: gnu/tests/env/env-S.pl - {ERR=>"env: invalid option -- ' '\n" . - "env: use -[v]S to pass options in shebang lines\n" . - "Try 'env --help' for more information.\n"}], -+ {ERR=>"$prog: error: unexpected argument '- ' found\n\n" . ++ {ERR=>"error: unexpected argument '- ' found\n\n" . + " tip: to pass '- ' as a value, use '-- - '\n\n" . + "Usage: $prog [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n\n" . + "For more information, try '--help'.\n" . @@ -51,7 +51,7 @@ Index: gnu/tests/env/env-S.pl - {ERR=>"env: invalid option -- '\t'\n" . - "env: use -[v]S to pass options in shebang lines\n" . - "Try 'env --help' for more information.\n"}], -+ {ERR=>"$prog: error: unexpected argument '-\t' found\n\n" . ++ {ERR=>"error: unexpected argument '-\t' found\n\n" . + " tip: to pass '-\t' as a value, use '-- -\t'\n\n" . + "Usage: $prog [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n\n" . + "For more information, try '--help'.\n" . diff --git a/util/gnu-patches/tests_tsort.patch b/util/gnu-patches/tests_tsort.patch index 1084f97043a..1cc1603ee8f 100644 --- a/util/gnu-patches/tests_tsort.patch +++ b/util/gnu-patches/tests_tsort.patch @@ -3,15 +3,15 @@ Index: gnu/tests/misc/tsort.pl --- gnu.orig/tests/misc/tsort.pl +++ gnu/tests/misc/tsort.pl @@ -54,8 +54,10 @@ my @Tests = - + ['only-one', {IN => {f => ""}}, {IN => {g => ""}}, {EXIT => 1}, - {ERR => "tsort: extra operand 'g'\n" - . "Try 'tsort --help' for more information.\n"}], -+ {ERR => "error: unexpected argument 'g' found\n\n" ++ {ERR => "tsort: error: unexpected argument 'g' found\n\n" + . "Usage: tsort [OPTIONS] FILE\n\n" + . "For more information, try '--help'.\n" + }], ); - + my $save_temps = $ENV{DEBUG};