From f905d9ce41ac9175c66c8fb3b20f218b9718a363 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 25 Feb 2024 19:45:32 +0100 Subject: [PATCH] basename: implement and test repeated flags and arguments Note in particular that `args_override_self` would *NOT* work here, since it would in all cases cause `options::NAME` to override itself, or interfere with `trailing_var_arg`. --- src/uu/basename/src/basename.rs | 100 +++++++++++--------------------- tests/by-util/test_basename.rs | 48 +++++++++++++++ 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 6c9baca6f..f502fb234 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -27,86 +27,48 @@ pub mod options { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); - // Since options have to go before names, - // if the first argument is not an option, then there is no option, - // and that implies there is exactly one name (no option => no -a option), - // so simple format is used - if args.len() > 1 && !args[1].starts_with('-') { - if args.len() > 3 { - return Err(UUsageError::new( - 1, - format!("extra operand {}", args[3].to_string().quote()), - )); - } - let suffix = if args.len() > 2 { args[2].as_ref() } else { "" }; - println!("{}", basename(&args[1], suffix)); - return Ok(()); - } - // // Argument parsing // let matches = uu_app().try_get_matches_from(args)?; - // too few arguments - if !matches.contains_id(options::NAME) { - return Err(UUsageError::new(1, "missing operand".to_string())); - } - let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); - let opt_suffix = matches.get_one::(options::SUFFIX).is_some(); - let opt_multiple = matches.get_flag(options::MULTIPLE); - let multiple_paths = opt_suffix || opt_multiple; - let name_args_count = matches + let mut name_args = matches .get_many::(options::NAME) - .map(|n| n.len()) - .unwrap_or(0); - - // too many arguments - if !multiple_paths && name_args_count > 2 { - return Err(UUsageError::new( - 1, - format!( - "extra operand {}", - matches - .get_many::(options::NAME) - .unwrap() - .nth(2) - .unwrap() - .quote() - ), - )); + .unwrap_or_default() + .collect::>(); + if name_args.is_empty() { + return Err(UUsageError::new(1, "missing operand".to_string())); } - - let suffix = if opt_suffix { - matches.get_one::(options::SUFFIX).unwrap() - } else if !opt_multiple && name_args_count > 1 { + let multiple_paths = + matches.get_one::(options::SUFFIX).is_some() || matches.get_flag(options::MULTIPLE); + let suffix = if multiple_paths { matches - .get_many::(options::NAME) - .unwrap() - .nth(1) - .unwrap() + .get_one::(options::SUFFIX) + .cloned() + .unwrap_or_default() } else { - "" + // "simple format" + match name_args.len() { + 0 => panic!("already checked"), + 1 => String::default(), + 2 => name_args.pop().unwrap().clone(), + _ => { + return Err(UUsageError::new( + 1, + format!("extra operand {}", name_args[2].quote(),), + )); + } + } }; // // Main Program Processing // - let paths: Vec<_> = if multiple_paths { - matches.get_many::(options::NAME).unwrap().collect() - } else { - matches - .get_many::(options::NAME) - .unwrap() - .take(1) - .collect() - }; - - for path in paths { - print!("{}{}", basename(path, suffix), line_ending); + for path in name_args { + print!("{}{}", basename(path, &suffix), line_ending); } Ok(()) @@ -123,27 +85,31 @@ pub fn uu_app() -> Command { .short('a') .long(options::MULTIPLE) .help("support multiple arguments and treat each as a NAME") - .action(ArgAction::SetTrue), + .action(ArgAction::SetTrue) + .overrides_with(options::MULTIPLE), ) .arg( Arg::new(options::NAME) .action(clap::ArgAction::Append) .value_hint(clap::ValueHint::AnyPath) - .hide(true), + .hide(true) + .trailing_var_arg(true), ) .arg( Arg::new(options::SUFFIX) .short('s') .long(options::SUFFIX) .value_name("SUFFIX") - .help("remove a trailing SUFFIX; implies -a"), + .help("remove a trailing SUFFIX; implies -a") + .overrides_with(options::SUFFIX), ) .arg( Arg::new(options::ZERO) .short('z') .long(options::ZERO) .help("end each output line with NUL, not newline") - .action(ArgAction::SetTrue), + .action(ArgAction::SetTrue) + .overrides_with(options::ZERO), ) } diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index ff9e3b29c..b9cee2863 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -202,6 +202,54 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_repeated_multiple() { + new_ucmd!() + .args(&["-aa", "-a", "foo"]) + .succeeds() + .stdout_is("foo\n"); +} + +#[test] +fn test_repeated_multiple_many() { + new_ucmd!() + .args(&["-aa", "-a", "1/foo", "q/bar", "x/y/baz"]) + .succeeds() + .stdout_is("foo\nbar\nbaz\n"); +} + +#[test] +fn test_repeated_suffix_last() { + new_ucmd!() + .args(&["-s", ".h", "-s", ".c", "foo.c"]) + .succeeds() + .stdout_is("foo\n"); +} + +#[test] +fn test_repeated_suffix_not_first() { + new_ucmd!() + .args(&["-s", ".h", "-s", ".c", "foo.h"]) + .succeeds() + .stdout_is("foo.h\n"); +} + +#[test] +fn test_repeated_suffix_multiple() { + new_ucmd!() + .args(&["-as", ".h", "-a", "-s", ".c", "foo.c", "bar.c", "bar.h"]) + .succeeds() + .stdout_is("foo\nbar\nbar.h\n"); +} + +#[test] +fn test_repeated_zero() { + new_ucmd!() + .args(&["-zz", "-z", "foo/bar"]) + .succeeds() + .stdout_is("bar\0"); +} + #[test] fn test_zero_does_not_imply_multiple() { new_ucmd!()