clap localization: address PR review comments

- Make colorize() function private since only used internally
  - Remove redundant colors_enabled checks
  - Add apply_color helper closure to reduce code duplication
  - Remove try_ prefix from function names for consistency
  - Update all utilities to use renamed functions
  - Fix app variable reference in mv utility
This commit is contained in:
Sylvestre Ledru 2025-08-09 18:49:04 +02:00
parent 5086310687
commit f1beeb904c
79 changed files with 267 additions and 140 deletions

View file

@ -12,7 +12,7 @@ use uucore::translate;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().try_get_matches_from_localized(args);
uu_app().get_matches_from_localized(args);
let uts =
PlatformInfo::new().map_err(|_e| USimpleError::new(1, translate!("cannot-get-system")))?;

View file

@ -100,7 +100,7 @@ pub fn parse_base_cmd_args(
usage: &str,
) -> UResult<Config> {
let command = base_app(about, usage);
let matches = command.try_get_matches_from_localized(args);
let matches = command.get_matches_from_localized(args);
Config::from(&matches)
}

View file

@ -30,7 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
//
// Argument parsing
//
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));

View file

@ -231,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let number_mode = if matches.get_flag(options::NUMBER_NONBLANK) {
NumberingMode::NonEmpty

View file

@ -304,7 +304,7 @@ struct Options {
}
fn parse_command_line(config: Command, args: impl uucore::Args) -> Result<Options> {
let matches = config.try_get_matches_from_localized(args);
let matches = config.get_matches_from_localized(args);
let verbose = matches.get_flag(options::VERBOSE);

View file

@ -113,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name
let matches = uu_app()
.after_help(translate!("chmod-after-help"))
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let changes = matches.get_flag(options::CHANGES);
let quiet = matches.get_flag(options::QUIET);

View file

@ -237,7 +237,7 @@ fn handle_tag_text_binary_flags<S: AsRef<OsStr>>(
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let check = matches.get_flag(options::CHECK);

View file

@ -281,7 +281,7 @@ fn open_file(name: &str, line_ending: LineEnding) -> io::Result<LineReader> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
let filename1 = matches.get_one::<String>(options::FILE_1).unwrap();
let filename2 = matches.get_one::<String>(options::FILE_2).unwrap();

View file

@ -779,7 +779,7 @@ pub fn uu_app() -> Command {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let options = Options::from_matches(&matches)?;

View file

@ -605,7 +605,7 @@ where
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
// get the file to split
let file_name = matches.get_one::<String>(options::FILE).unwrap();

View file

@ -483,7 +483,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
})
.collect();
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let complement = matches.get_flag(options::COMPLEMENT);
let only_delimited = matches.get_flag(options::ONLY_DELIMITED);

View file

@ -112,7 +112,7 @@ impl From<&str> for Rfc3339Format {
#[uucore::main]
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let format = if let Some(form) = matches.get_one::<String>(OPT_FORMAT) {
if !form.starts_with('+') {

View file

@ -1416,7 +1416,7 @@ fn is_fifo(filename: &str) -> bool {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let settings: Settings = Parser::new().parse(
matches

View file

@ -407,7 +407,7 @@ impl UError for DfError {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
#[cfg(windows)]
{

View file

@ -121,7 +121,7 @@ fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let files = matches
.get_many::<String>(options::FILE)

View file

@ -22,7 +22,7 @@ mod options {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(translate!("dirname-after-help"))
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));

View file

@ -581,7 +581,7 @@ fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
#[uucore::main]
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let summarize = matches.get_flag(options::SUMMARIZE);

View file

@ -80,7 +80,7 @@ fn write_result(
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
// If matches find --exponents flag than variable print_exponents is true and p^e output format will be used.
let print_exponents = matches.get_flag(options::EXPONENTS);

View file

@ -335,7 +335,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
}
let matches = uu_app().try_get_matches_from_localized(&args);
let matches = uu_app().get_matches_from_localized(&args);
let files = extract_files(&matches)?;

View file

@ -32,7 +32,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_lossy();
let (args, obs_width) = handle_obsolete(&args[..]);
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let bytes = matches.get_flag(options::BYTES);
let spaces = matches.get_flag(options::SPACES);

View file

@ -48,7 +48,7 @@ fn infallible_gid2grp(gid: &u32) -> String {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let users: Vec<String> = matches
.get_many::<String>(options::USERS)

View file

@ -182,7 +182,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
// causes "error: " to be printed twice (once from crash!() and once from clap). With
// the current setup, the name of the utility is not printed, but I think this is at
// least somewhat better from a user's perspective.
let matches = command.try_get_matches_from_localized(args);
let matches = command.get_matches_from_localized(args);
let input_length: Option<&usize> = if binary_name == "b2sum" {
matches.get_one::<usize>(options::LENGTH)

View file

@ -551,7 +551,7 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args_vec: Vec<_> = arg_iterate(args)?.collect();
let matches = uu_app().try_get_matches_from_localized(args_vec);
let matches = uu_app().get_matches_from_localized(args_vec);
let args = match HeadOptions::get_from(&matches) {
Ok(o) => o,
Err(s) => {

View file

@ -14,7 +14,7 @@ use uucore::translate;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().try_get_matches_from_localized(args);
uu_app().get_matches_from_localized(args);
hostid();
Ok(())
}

View file

@ -61,7 +61,7 @@ mod wsa {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
#[cfg(windows)]
let _handle = wsa::start().map_err_context(|| translate!("hostname-error-winsock"))?;

View file

@ -122,7 +122,7 @@ struct State {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(translate!("id-after-help"))
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let users: Vec<String> = matches
.get_many::<String>(options::ARG_USERS)

View file

@ -166,7 +166,7 @@ static ARG_FILES: &str = "files";
///
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let paths: Vec<String> = matches
.get_many::<String>(ARG_FILES)

View file

@ -822,7 +822,7 @@ fn parse_settings(matches: &clap::ArgMatches) -> UResult<Settings> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let settings = parse_settings(&matches)?;

View file

@ -41,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut args = args.collect_ignore();
let obs_signal = handle_obsolete(&mut args);
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mode = if matches.get_flag(options::TABLE) {
Mode::Table

View file

@ -20,7 +20,7 @@ pub mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let files: Vec<_> = matches
.get_many::<OsString>(options::FILES)
.unwrap_or_default()

View file

@ -97,7 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(after_help)
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
/* the list of files */

View file

@ -24,7 +24,7 @@ fn get_userlogin() -> Option<String> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _ = uu_app().try_get_matches_from_localized(args);
let _ = uu_app().get_matches_from_localized(args);
match get_userlogin() {
Some(userlogin) => println!("{userlogin}"),

View file

@ -82,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// " of each created directory to CTX"),
let matches = uu_app()
.after_help(translate!("mkdir-after-help"))
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let dirs = matches
.get_many::<OsString>(options::DIRS)

View file

@ -24,7 +24,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mode = calculate_mode(matches.get_one::<String>(options::MODE))
.map_err(|e| USimpleError::new(1, translate!("mkfifo-error-invalid-mode", "error" => e)))?;

View file

@ -112,7 +112,7 @@ fn mknod(file_name: &str, config: Config) -> i32 {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let file_type = matches.get_one::<FileType>("type").unwrap();

View file

@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
print!("\r");
println!("{panic_info}");
}));
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mut options = Options::from(&matches);
if let Some(files) = matches.get_many::<String>(options::FILES) {
let length = files.len();

View file

@ -153,7 +153,7 @@ static OPT_SELINUX: &str = "selinux";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let files: Vec<OsString> = matches
.get_many::<OsString>(ARG_FILES)
@ -162,12 +162,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect();
if files.len() == 1 && !matches.contains_id(OPT_TARGET_DIRECTORY) {
return Err(UUsageError::new(
1,
format!(
"The argument '<{ARG_FILES}>...' requires at least 2 values, but only 1 was provided"
),
));
let err = uu_app().error(
ErrorKind::TooFewValues,
translate!("mv-error-insufficient-arguments", "arg_files" => ARG_FILES),
);
uucore::clap_localization::handle_clap_error_with_exit_code(err, uucore::util_name(), 1);
}
let overwrite_mode = determine_overwrite_mode(&matches);

View file

@ -177,7 +177,7 @@ pub mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mut settings = Settings::default();

View file

@ -27,7 +27,7 @@ static OPT_IGNORE: &str = "ignore";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let ignore = match matches.get_one::<String>(OPT_IGNORE) {
Some(numstr) => match numstr.trim().parse::<usize>() {

View file

@ -255,7 +255,7 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let options = parse_options(&matches).map_err(NumfmtError::IllegalArgument)?;

View file

@ -221,7 +221,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let clap_opts = uu_app();
let clap_matches = clap_opts.try_get_matches_from_localized(&args);
let clap_matches = clap_opts.get_matches_from_localized(&args);
let od_options = OdOptions::new(&clap_matches, &args)?;

View file

@ -25,7 +25,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let serial = matches.get_flag(options::SERIAL);
let delimiters = matches.get_one::<String>(options::DELIMITER).unwrap();

View file

@ -35,7 +35,7 @@ const POSIX_NAME_MAX: usize = 14;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
// set working mode
let is_posix = matches.get_flag(options::POSIX);

View file

@ -10,7 +10,7 @@ use uucore::error::UResult;
use uucore::translate;
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _matches = uu_app().try_get_matches_from_localized(args);
let _matches = uu_app().get_matches_from_localized(args);
println!("{}", translate!("pinky-unsupported-openbsd"));
Ok(())
}

View file

@ -35,7 +35,7 @@ fn get_long_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(get_long_usage())
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let users: Vec<String> = matches
.get_many::<String>(options::USER)

View file

@ -317,7 +317,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let opt_args = recreate_arguments(&args);
let command = uu_app();
let matches = command.try_get_matches_from_mut_localized(opt_args);
let matches = command.get_matches_from_mut_localized(opt_args);
let mut files = matches
.get_many::<String>(options::FILES)

View file

@ -15,7 +15,7 @@ static ARG_VARIABLES: &str = "variables";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let variables: Vec<String> = matches
.get_many::<String>(ARG_VARIABLES)

View file

@ -22,7 +22,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let format = matches
.get_one::<OsString>(options::FORMAT)

View file

@ -729,7 +729,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let config = get_config(&matches)?;
let input_files;

View file

@ -110,7 +110,7 @@ fn logical_path() -> io::Result<PathBuf> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
// if POSIXLY_CORRECT is set, we want to a logical resolution.
// This produces a different output when doing mkdir -p a/b && ln -s a/b c && cd c && pwd
// We should get c in this case instead of a/b at the end of the path

View file

@ -30,7 +30,7 @@ const ARG_FILES: &str = "files";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mut no_trailing_delimiter = matches.get_flag(OPT_NO_NEWLINE);
let use_zero = matches.get_flag(OPT_ZERO);

View file

@ -144,7 +144,7 @@ static ARG_FILES: &str = "files";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let files: Vec<_> = matches
.get_many::<OsString>(ARG_FILES)

View file

@ -26,7 +26,7 @@ static ARG_DIRS: &str = "dirs";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let opts = Opts {
ignore: matches.get_flag(OPT_IGNORE_FAIL_NON_EMPTY),

View file

@ -239,7 +239,7 @@ impl<'a> BytesWriter<'a> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
if !matches.contains_id(options::FILE) {
return Err(UUsageError::new(

View file

@ -52,7 +52,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mode = if matches.get_flag(options::ECHO) {
Mode::Echo(

View file

@ -21,7 +21,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let numbers = matches
.get_many::<String>(options::NUMBER)

View file

@ -52,7 +52,7 @@ static ARG_PREFIX: &str = "prefix";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let (args, obs_lines) = handle_obsolete(args);
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
match Settings::from(&matches, obs_lines.as_deref()) {
Ok(settings) => split(&settings),

View file

@ -1221,7 +1221,7 @@ impl Stater {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(translate!("stat-after-help"))
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let stater = Stater::new(&matches)?;
let exit_status = stater.exec();

View file

@ -243,7 +243,7 @@ ioctl_write_ptr_bad!(
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let opts = Options::from(&matches)?;

View file

@ -99,7 +99,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
Some(v) => v.cloned().collect(),

View file

@ -174,7 +174,7 @@ mod platform {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let files: Vec<String> = matches
.get_many::<String>(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())

View file

@ -33,7 +33,7 @@ mod options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let before = matches.get_flag(options::BEFORE);
let regex = matches.get_flag(options::REGEX);

View file

@ -52,7 +52,7 @@ enum OutputErrorMode {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let append = matches.get_flag(options::APPEND);
let ignore_interrupts = matches.get_flag(options::IGNORE_INTERRUPTS);

View file

@ -187,7 +187,7 @@ fn shr2(s: &str) -> String {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mut filenames: Vec<&String> = matches
.get_many::<String>(ARG_FILES)

View file

@ -41,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let delete_flag = matches.get_flag(options::DELETE);
let complement_flag = matches.get_flag(options::COMPLEMENT);

View file

@ -44,7 +44,7 @@ impl UError for TsortError {}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let input = matches
.get_one::<String>(options::FILE)

View file

@ -121,7 +121,7 @@ pub struct Options {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let options = Options {
all: matches.get_flag(options::ALL),

View file

@ -19,7 +19,7 @@ static OPT_PATH: &str = "FILE";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let path: &Path = matches.get_one::<OsString>(OPT_PATH).unwrap().as_ref();

View file

@ -48,7 +48,7 @@ impl UError for UptimeError {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
#[cfg(unix)]
let file_path = matches.get_one::<OsString>(options::PATH);

View file

@ -38,7 +38,7 @@ fn get_long_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(get_long_usage())
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let maybe_file: Option<&Path> = matches.get_one::<OsString>(ARG_FILE).map(AsRef::as_ref);

View file

@ -377,7 +377,7 @@ impl UError for WcError {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let settings = Settings::new(&matches);
let inputs = Inputs::new(&matches)?;

View file

@ -12,7 +12,7 @@ use uucore::error::UResult;
use uucore::translate;
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _matches = uu_app().try_get_matches_from_localized(args);
let _matches = uu_app().get_matches_from_localized(args);
println!("{}", translate!("who-unsupported-openbsd"));
Ok(())
}

View file

@ -29,7 +29,7 @@ fn get_long_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app()
.after_help(get_long_usage())
.try_get_matches_from_localized(args);
.get_matches_from_localized(args);
let files: Vec<String> = matches
.get_many::<String>(options::FILE)

View file

@ -14,7 +14,7 @@ mod platform;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().try_get_matches_from_localized(args);
uu_app().get_matches_from_localized(args);
let username = whoami()?;
println_verbatim(username).map_err_context(|| translate!("whoami-error-failed-to-print"))?;
Ok(())

View file

@ -22,7 +22,7 @@ const BUF_SIZE: usize = 16 * 1024;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from_localized(args);
let matches = uu_app().get_matches_from_localized(args);
let mut buffer = Vec::with_capacity(BUF_SIZE);
args_into_buffer(&mut buffer, matches.get_many::<OsString>("STRING")).unwrap();

View file

@ -13,16 +13,79 @@ use clap::error::{ContextKind, ErrorKind};
use clap::{ArgMatches, Command, Error};
use std::ffi::OsString;
/// Apply color to text using ANSI escape codes
pub fn colorize(text: &str, color_code: &str) -> String {
format!("\x1b[{color_code}m{text}\x1b[0m")
/// Determines if a clap error should show simple help instead of full usage
/// Based on clap's own design patterns and error categorization
fn should_show_simple_help_for_clap_error(kind: ErrorKind) -> bool {
match kind {
// Most validation errors should show simple help
ErrorKind::InvalidValue
| ErrorKind::InvalidSubcommand
| ErrorKind::ValueValidation
| ErrorKind::InvalidUtf8
| ErrorKind::ArgumentConflict
| ErrorKind::NoEquals => true,
// Argument count and structural errors need special formatting
ErrorKind::TooFewValues
| ErrorKind::TooManyValues
| ErrorKind::WrongNumberOfValues
| ErrorKind::MissingSubcommand => false,
// MissingRequiredArgument needs different handling
ErrorKind::MissingRequiredArgument => false,
// Special cases - handle their own display
ErrorKind::DisplayHelp
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
| ErrorKind::DisplayVersion => false,
// UnknownArgument gets special handling elsewhere, so mark as false here
ErrorKind::UnknownArgument => false,
// System errors - keep simple
ErrorKind::Io | ErrorKind::Format => true,
// Default for any new ErrorKind variants - be conservative and show simple help
_ => true,
}
}
/// Color constants for consistent styling
pub mod colors {
pub const RED: &str = "31";
pub const YELLOW: &str = "33";
pub const GREEN: &str = "32";
/// Color enum for consistent styling
#[derive(Debug, Clone, Copy)]
pub enum Color {
Red,
Yellow,
Green,
}
impl Color {
fn code(self) -> &'static str {
match self {
Color::Red => "31",
Color::Yellow => "33",
Color::Green => "32",
}
}
}
/// Apply color to text using ANSI escape codes
fn colorize(text: &str, color: Color) -> String {
format!("\x1b[{}m{text}\x1b[0m", color.code())
}
/// Display usage information and help suggestion for errors that require it
/// This consolidates the shared logic between clap errors and UUsageError
pub fn display_usage_and_help(util_name: &str) {
eprintln!();
// Try to get usage information from localization
let usage_key = format!("{}-usage", util_name);
let usage_text = translate!(&usage_key);
let formatted_usage = crate::format_usage(&usage_text);
let usage_label = translate!("common-usage");
eprintln!("{}: {}", usage_label, formatted_usage);
eprintln!();
let help_msg = translate!("clap-error-help-suggestion", "command" => crate::execution_phrase());
eprintln!("{help_msg}");
}
pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code: i32) -> ! {
@ -30,6 +93,19 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
// If it's already initialized, that's fine - we'll use the existing one
let _ = crate::locale::setup_localization_with_common(util_name);
// Check if colors are enabled by examining clap's rendered output
let rendered_str = err.render().to_string();
let colors_enabled = rendered_str.contains("\x1b[");
// Helper function to conditionally colorize text
let maybe_colorize = |text: &str, color: Color| -> String {
if colors_enabled {
colorize(text, color)
} else {
text.to_string()
}
};
match err.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
// For help and version, use clap's built-in formatting and exit with 0
@ -38,13 +114,7 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
std::process::exit(0);
}
ErrorKind::UnknownArgument => {
// Use clap's rendering system but capture the output to check if colors are used
let rendered = err.render();
let rendered_str = rendered.to_string();
// Simple check - if the rendered output contains ANSI escape codes, colors are enabled
let colors_enabled = rendered_str.contains("\x1b[");
// UnknownArgument gets special handling for suggestions, but should still show simple help
if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) {
let arg_str = invalid_arg.to_string();
@ -52,16 +122,9 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
let error_word = translate!("common-error");
let tip_word = translate!("common-tip");
// Apply colors only if they're enabled in the original error
let (colored_arg, colored_error_word, colored_tip_word) = if colors_enabled {
(
colorize(&arg_str, colors::YELLOW),
colorize(&error_word, colors::RED),
colorize(&tip_word, colors::GREEN),
)
} else {
(arg_str.clone(), error_word.clone(), tip_word.clone())
};
let colored_arg = maybe_colorize(&arg_str, Color::Yellow);
let colored_error_word = maybe_colorize(&error_word, Color::Red);
let colored_tip_word = maybe_colorize(&tip_word, Color::Green);
// Print main error message
let error_msg = translate!(
@ -75,11 +138,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
// Show suggestion or generic tip
let suggestion = err.get(ContextKind::SuggestedArg);
if let Some(suggested_arg) = suggestion {
let colored_suggestion = if colors_enabled {
colorize(&suggested_arg.to_string(), colors::GREEN)
} else {
suggested_arg.to_string()
};
let colored_suggestion =
maybe_colorize(&suggested_arg.to_string(), Color::Green);
let suggestion_msg = translate!(
"clap-error-similar-argument",
"tip_word" => colored_tip_word,
@ -87,11 +147,8 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
);
eprintln!(" {suggestion_msg}");
} else {
let colored_tip_command = if colors_enabled {
colorize(&format!("-- {arg_str}"), colors::GREEN)
} else {
format!("-- {arg_str}")
};
let colored_tip_command =
maybe_colorize(&format!("-- {arg_str}"), Color::Green);
let tip_msg = translate!(
"clap-error-pass-as-value",
"arg" => colored_arg,
@ -101,36 +158,72 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
eprintln!(" {tip_msg}");
}
// Show usage and help
// Show usage information for unknown arguments but use simple --help format
eprintln!();
// Try to get usage information from localization
let usage_key = format!("{}-usage", util_name);
let usage_text = translate!(&usage_key);
let formatted_usage = crate::format_usage(&usage_text);
let usage_label = translate!("common-usage");
let usage_pattern = translate!(&format!("{util_name}-usage"));
eprintln!("{usage_label}: {usage_pattern}");
eprintln!("{}: {}", usage_label, formatted_usage);
eprintln!();
let help_msg = translate!("clap-error-help-suggestion", "command" => util_name);
eprintln!("{help_msg}");
// Use simple --help format for GNU test compatibility
eprintln!("For more information, try '--help'.");
std::process::exit(exit_code);
} else {
// Generic fallback case
let rendered = err.render();
let rendered_str = rendered.to_string();
let colors_enabled = rendered_str.contains("\x1b[");
let colored_error_word = if colors_enabled {
colorize(&translate!("common-error"), colors::RED)
} else {
translate!("common-error")
};
let colored_error_word = maybe_colorize(&translate!("common-error"), Color::Red);
eprintln!("{colored_error_word}: unexpected argument");
std::process::exit(exit_code);
}
}
// Check if this is a simple validation error that should show simple help
kind if should_show_simple_help_for_clap_error(kind) => {
// For simple validation errors, use the same simple format as other errors
let lines: Vec<&str> = rendered_str.lines().collect();
if let Some(main_error_line) = lines.first() {
// Keep the "error: " prefix for test compatibility
eprintln!("{}", main_error_line);
eprintln!();
// Use the execution phrase for the help suggestion to match test expectations
eprintln!("For more information, try '--help'.");
} else {
// Fallback to original rendering if we can't parse
eprint!("{}", err.render());
}
std::process::exit(exit_code);
}
_ => {
// For other errors, print using clap's formatter but exit with code 1
eprint!("{}", err.render());
std::process::exit(1);
// For MissingRequiredArgument, use the full clap error as it includes proper usage
if matches!(err.kind(), ErrorKind::MissingRequiredArgument) {
eprint!("{}", err.render());
std::process::exit(exit_code);
}
// For TooFewValues and similar structural errors, use the full clap error
if matches!(
err.kind(),
ErrorKind::TooFewValues | ErrorKind::TooManyValues | ErrorKind::WrongNumberOfValues
) {
eprint!("{}", err.render());
std::process::exit(exit_code);
}
// For other errors, show just the error and help suggestion
let rendered_str = err.render().to_string();
let lines: Vec<&str> = rendered_str.lines().collect();
// Print error message (first line)
if let Some(first_line) = lines.first() {
eprintln!("{}", first_line);
}
// For other errors, just show help suggestion
eprintln!();
eprintln!("For more information, try '--help'.");
std::process::exit(exit_code);
}
}
}
@ -143,15 +236,15 @@ pub trait LocalizedCommand {
where
Self: Sized;
/// Try to get matches from args with localized error handling
fn try_get_matches_from_localized<I, T>(self, itr: I) -> ArgMatches
/// Get matches from args with localized error handling
fn get_matches_from_localized<I, T>(self, itr: I) -> ArgMatches
where
Self: Sized,
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone;
/// Try to get matches from mutable args with localized error handling
fn try_get_matches_from_mut_localized<I, T>(self, itr: I) -> ArgMatches
/// Get matches from mutable args with localized error handling
fn get_matches_from_mut_localized<I, T>(self, itr: I) -> ArgMatches
where
Self: Sized,
I: IntoIterator<Item = T>,
@ -164,7 +257,7 @@ impl LocalizedCommand for Command {
.unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1))
}
fn try_get_matches_from_localized<I, T>(self, itr: I) -> ArgMatches
fn get_matches_from_localized<I, T>(self, itr: I) -> ArgMatches
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
@ -173,7 +266,7 @@ impl LocalizedCommand for Command {
.unwrap_or_else(|err| handle_clap_error_with_exit_code(err, crate::util_name(), 1))
}
fn try_get_matches_from_mut_localized<I, T>(mut self, itr: I) -> ArgMatches
fn get_matches_from_mut_localized<I, T>(mut self, itr: I) -> ArgMatches
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,

View file

@ -856,6 +856,16 @@ fn test_du_invalid_threshold() {
ts.ucmd().arg(format!("--threshold={threshold}")).fails();
}
#[test]
fn test_du_threshold_error_handling() {
// Test missing threshold value - the specific case from GNU test
new_ucmd!()
.arg("--threshold")
.fails()
.stderr_contains("a value is required for '--threshold <SIZE>' but none was supplied")
.stderr_contains("For more information, try '--help'.");
}
#[test]
fn test_du_apparent_size() {
let (at, mut ucmd) = at_and_ucmd!();

View file

@ -2547,3 +2547,27 @@ fn test_mv_selinux_context() {
let _ = std::fs::remove_file(at.plus_as_string(src));
}
}
#[test]
fn test_mv_error_usage_display_missing_arg() {
new_ucmd!()
.arg("--target-directory=.")
.fails()
.code_is(1)
.stderr_contains("error: the following required arguments were not provided:")
.stderr_contains("<files>...")
.stderr_contains("Usage: mv [OPTION]... [-T] SOURCE DEST")
.stderr_contains("For more information, try '--help'.");
}
#[test]
fn test_mv_error_usage_display_too_few() {
new_ucmd!()
.arg("file1")
.fails()
.code_is(1)
.stderr_contains("requires at least 2 values, but only 1 was provided")
.stderr_contains("Usage: mv [OPTION]... [-T] SOURCE DEST")
.stderr_contains("For more information, try '--help'.");
}

View file

@ -36,6 +36,7 @@ fn execution_phrase_double() {
let output = Command::new(&scenario.bin_path)
.arg("ls")
.arg("--some-invalid-arg")
.env("LANG", "en_US.UTF-8")
.output()
.unwrap();
assert!(