1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

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`.
This commit is contained in:
Ben Wiederhake 2024-02-25 19:45:32 +01:00
parent 9441806a23
commit f905d9ce41
2 changed files with 81 additions and 67 deletions

View file

@ -27,86 +27,48 @@ pub mod options {
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_lossy(); 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 // Argument parsing
// //
let matches = uu_app().try_get_matches_from(args)?; 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 line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
let opt_suffix = matches.get_one::<String>(options::SUFFIX).is_some(); let mut name_args = matches
let opt_multiple = matches.get_flag(options::MULTIPLE);
let multiple_paths = opt_suffix || opt_multiple;
let name_args_count = matches
.get_many::<String>(options::NAME) .get_many::<String>(options::NAME)
.map(|n| n.len()) .unwrap_or_default()
.unwrap_or(0); .collect::<Vec<_>>();
if name_args.is_empty() {
// too many arguments return Err(UUsageError::new(1, "missing operand".to_string()));
if !multiple_paths && name_args_count > 2 { }
let multiple_paths =
matches.get_one::<String>(options::SUFFIX).is_some() || matches.get_flag(options::MULTIPLE);
let suffix = if multiple_paths {
matches
.get_one::<String>(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( return Err(UUsageError::new(
1, 1,
format!( format!("extra operand {}", name_args[2].quote(),),
"extra operand {}",
matches
.get_many::<String>(options::NAME)
.unwrap()
.nth(2)
.unwrap()
.quote()
),
)); ));
} }
}
let suffix = if opt_suffix {
matches.get_one::<String>(options::SUFFIX).unwrap()
} else if !opt_multiple && name_args_count > 1 {
matches
.get_many::<String>(options::NAME)
.unwrap()
.nth(1)
.unwrap()
} else {
""
}; };
// //
// Main Program Processing // Main Program Processing
// //
let paths: Vec<_> = if multiple_paths { for path in name_args {
matches.get_many::<String>(options::NAME).unwrap().collect() print!("{}{}", basename(path, &suffix), line_ending);
} else {
matches
.get_many::<String>(options::NAME)
.unwrap()
.take(1)
.collect()
};
for path in paths {
print!("{}{}", basename(path, suffix), line_ending);
} }
Ok(()) Ok(())
@ -123,27 +85,31 @@ pub fn uu_app() -> Command {
.short('a') .short('a')
.long(options::MULTIPLE) .long(options::MULTIPLE)
.help("support multiple arguments and treat each as a NAME") .help("support multiple arguments and treat each as a NAME")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue)
.overrides_with(options::MULTIPLE),
) )
.arg( .arg(
Arg::new(options::NAME) Arg::new(options::NAME)
.action(clap::ArgAction::Append) .action(clap::ArgAction::Append)
.value_hint(clap::ValueHint::AnyPath) .value_hint(clap::ValueHint::AnyPath)
.hide(true), .hide(true)
.trailing_var_arg(true),
) )
.arg( .arg(
Arg::new(options::SUFFIX) Arg::new(options::SUFFIX)
.short('s') .short('s')
.long(options::SUFFIX) .long(options::SUFFIX)
.value_name("SUFFIX") .value_name("SUFFIX")
.help("remove a trailing SUFFIX; implies -a"), .help("remove a trailing SUFFIX; implies -a")
.overrides_with(options::SUFFIX),
) )
.arg( .arg(
Arg::new(options::ZERO) Arg::new(options::ZERO)
.short('z') .short('z')
.long(options::ZERO) .long(options::ZERO)
.help("end each output line with NUL, not newline") .help("end each output line with NUL, not newline")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue)
.overrides_with(options::ZERO),
) )
} }

View file

@ -202,6 +202,54 @@ fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1); 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] #[test]
fn test_zero_does_not_imply_multiple() { fn test_zero_does_not_imply_multiple() {
new_ucmd!() new_ucmd!()