-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
I am sure, this must have come up before, but could not find a discussion on it.
When using abbreviated long options, sometimes the option is not unique, e.g.
$ ls --h
ls: option '--h' is ambiguous; possibilities: '--human-readable' '--hide-control-chars' '--hide' '--hyperlink' '--help'
But clap returns just an error with a single suggestion:
error: unexpected argument '--h' found
tip: a similar argument exists: '--hide'
Usage: ls [OPTION]... [FILE]...
This is less information to the user, who has now to call --help first, if he does not remember the exact option.
Use case: User wants to hide control characters, but only remembers "something with --hide",
so he types "ls --hid" and should get
ls: option '--hi' is ambiguous; possibilities: '--hide-control-chars' '--hide'.
He then can just simply repeat the command (cursor up) and add a few letters: "ls --hide-c".
With uutils this is not possible.
clap should return a list of possible options instead of the first suggested one, but does not support that functionality.
Below is a code change that would output something like:
Error: Argument '--h' is ambiguous.
Did you mean one of these?
--help
--hide
--hide-control-chars
--human-readable
--hyperlink
What is your take on that?
pub fn handle_clap_result_with_exit_code<I, T>(
mut cmd: Command,
itr: I,
exit_code: i32,
) -> UResult<ArgMatches>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
// cloning args for double use in error case
let args = itr.into_iter().collect::<Vec<T>>();
let itr = args.clone();
// using mut to avoid cloning cmd
cmd.try_get_matches_from_mut(itr).map_err(|e| {
if e.exit_code() == 0 {
e.into() // Preserve help/version
} else {
// find ambiguous options
if e.kind() == ErrorKind::UnknownArgument || e.kind() == ErrorKind::InvalidSubcommand {
// Find the string the user actually typed (e.g., "--de")
// for arg in &itr {}
let args_str: Vec<String> = args
.into_iter()
.map(|t| {
let o: OsString = t.into();
o.to_string_lossy().to_string()
})
.collect();
if let Some(provided) = args_str.iter().find(|a| a.starts_with("--")) {
let search_term = provided.trim_start_matches("--");
// Manually filter all defined long arguments
let mut matches: Vec<_> = cmd
.get_arguments()
.filter_map(|arg| arg.get_long())
.filter(|l| l.starts_with(search_term))
.collect();
if matches.len() > 1 {
// TODO error handling to UError
eprintln!("Error: Argument '{}' is ambiguous.", provided);
eprintln!("Did you mean one of these?");
matches.sort();
for m in matches {
eprintln!(" --{}", m);
}
std::process::exit(1);
}
}
}
let formatter = ErrorFormatter::new(crate::util_name());
let code = formatter.print_error(&e, exit_code);
USimpleError::new(code, "")
}
})
}