mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 23:02:31 +00:00
Merge pull request #4482 from epage/suggest
feat(parser): Show available subcommands when one is missing
This commit is contained in:
commit
8cefdf31cc
5 changed files with 52 additions and 5 deletions
|
@ -9,6 +9,8 @@ pub enum ContextKind {
|
||||||
InvalidArg,
|
InvalidArg,
|
||||||
/// Existing arguments
|
/// Existing arguments
|
||||||
PriorArg,
|
PriorArg,
|
||||||
|
/// Accepted subcommands
|
||||||
|
ValidSubcommand,
|
||||||
/// Accepted values
|
/// Accepted values
|
||||||
ValidValue,
|
ValidValue,
|
||||||
/// Rejected values
|
/// Rejected values
|
||||||
|
@ -44,6 +46,7 @@ impl ContextKind {
|
||||||
Self::InvalidSubcommand => Some("Invalid Subcommand"),
|
Self::InvalidSubcommand => Some("Invalid Subcommand"),
|
||||||
Self::InvalidArg => Some("Invalid Argument"),
|
Self::InvalidArg => Some("Invalid Argument"),
|
||||||
Self::PriorArg => Some("Prior Argument"),
|
Self::PriorArg => Some("Prior Argument"),
|
||||||
|
Self::ValidSubcommand => Some("Value Subcommand"),
|
||||||
Self::ValidValue => Some("Value Value"),
|
Self::ValidValue => Some("Value Value"),
|
||||||
Self::InvalidValue => Some("Invalid Value"),
|
Self::InvalidValue => Some("Invalid Value"),
|
||||||
Self::ActualNumValues => Some("Actual Number of Values"),
|
Self::ActualNumValues => Some("Actual Number of Values"),
|
||||||
|
|
|
@ -227,6 +227,24 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) ->
|
||||||
styled.none("'");
|
styled.none("'");
|
||||||
styled.warning(invalid_sub);
|
styled.warning(invalid_sub);
|
||||||
styled.none("' requires a subcommand but one was not provided");
|
styled.none("' requires a subcommand but one was not provided");
|
||||||
|
|
||||||
|
let possible_values = error.get(ContextKind::ValidSubcommand);
|
||||||
|
if let Some(ContextValue::Strings(possible_values)) = possible_values {
|
||||||
|
if !possible_values.is_empty() {
|
||||||
|
styled.none("\n");
|
||||||
|
styled.none(TAB);
|
||||||
|
styled.none("[subcommands: ");
|
||||||
|
if let Some((last, elements)) = possible_values.split_last() {
|
||||||
|
for v in elements {
|
||||||
|
styled.good(escape(v));
|
||||||
|
styled.none(", ");
|
||||||
|
}
|
||||||
|
styled.good(escape(last));
|
||||||
|
}
|
||||||
|
styled.none("]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
@ -504,17 +504,21 @@ impl<F: ErrorFormatter> Error<F> {
|
||||||
|
|
||||||
pub(crate) fn missing_subcommand(
|
pub(crate) fn missing_subcommand(
|
||||||
cmd: &Command,
|
cmd: &Command,
|
||||||
name: String,
|
parent: String,
|
||||||
|
available: Vec<String>,
|
||||||
usage: Option<StyledStr>,
|
usage: Option<StyledStr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
|
let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
|
||||||
|
|
||||||
#[cfg(feature = "error-context")]
|
#[cfg(feature = "error-context")]
|
||||||
{
|
{
|
||||||
err = err.extend_context_unchecked([(
|
err = err.extend_context_unchecked([
|
||||||
ContextKind::InvalidSubcommand,
|
(ContextKind::InvalidSubcommand, ContextValue::String(parent)),
|
||||||
ContextValue::String(name),
|
(
|
||||||
)]);
|
ContextKind::ValidSubcommand,
|
||||||
|
ContextValue::Strings(available),
|
||||||
|
),
|
||||||
|
]);
|
||||||
if let Some(usage) = usage {
|
if let Some(usage) = usage {
|
||||||
err = err
|
err = err
|
||||||
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
|
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
|
||||||
|
|
|
@ -70,6 +70,10 @@ impl<'cmd> Validator<'cmd> {
|
||||||
return Err(Error::missing_subcommand(
|
return Err(Error::missing_subcommand(
|
||||||
self.cmd,
|
self.cmd,
|
||||||
bn.to_string(),
|
bn.to_string(),
|
||||||
|
self.cmd
|
||||||
|
.all_subcommand_names()
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
Usage::new(self.cmd)
|
Usage::new(self.cmd)
|
||||||
.required(&self.required)
|
.required(&self.required)
|
||||||
.create_usage_with_title(&[]),
|
.create_usage_with_title(&[]),
|
||||||
|
|
|
@ -58,6 +58,24 @@ fn sub_command_required() {
|
||||||
assert_eq!(err.kind(), ErrorKind::MissingSubcommand);
|
assert_eq!(err.kind(), ErrorKind::MissingSubcommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "error-context")]
|
||||||
|
fn sub_command_required_error() {
|
||||||
|
static ERROR: &str = "\
|
||||||
|
error: 'sc_required' requires a subcommand but one was not provided
|
||||||
|
[subcommands: sub1, help]
|
||||||
|
|
||||||
|
Usage: sc_required <COMMAND>
|
||||||
|
|
||||||
|
For more information try '--help'
|
||||||
|
";
|
||||||
|
|
||||||
|
let cmd = Command::new("sc_required")
|
||||||
|
.subcommand_required(true)
|
||||||
|
.subcommand(Command::new("sub1"));
|
||||||
|
utils::assert_output(cmd, "sc_required", ERROR, true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn arg_required_else_help() {
|
fn arg_required_else_help() {
|
||||||
let result = Command::new("arg_required")
|
let result = Command::new("arg_required")
|
||||||
|
|
Loading…
Reference in a new issue