From 6b62c82fe29ff9c7a8cd989a75ab4f7f2ad3144c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 15 Nov 2022 10:17:16 -0600 Subject: [PATCH] feat(parser): Show available subcommands when one is missing Similar to - Listing all required arguments when one is missing - Listing all possible values when no value is provided This came up when discussing #3572 --- src/error/context.rs | 3 +++ src/error/format.rs | 18 ++++++++++++++++++ src/error/mod.rs | 14 +++++++++----- src/parser/validator.rs | 4 ++++ tests/builder/app_settings.rs | 1 + 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/error/context.rs b/src/error/context.rs index 5cc31097f12..cd8e308ee38 100644 --- a/src/error/context.rs +++ b/src/error/context.rs @@ -9,6 +9,8 @@ pub enum ContextKind { InvalidArg, /// Existing arguments PriorArg, + /// Accepted subcommands + ValidSubcommand, /// Accepted values ValidValue, /// Rejected values @@ -44,6 +46,7 @@ impl ContextKind { Self::InvalidSubcommand => Some("Invalid Subcommand"), Self::InvalidArg => Some("Invalid Argument"), Self::PriorArg => Some("Prior Argument"), + Self::ValidSubcommand => Some("Value Subcommand"), Self::ValidValue => Some("Value Value"), Self::InvalidValue => Some("Invalid Value"), Self::ActualNumValues => Some("Actual Number of Values"), diff --git a/src/error/format.rs b/src/error/format.rs index d6edf31c01b..17149439d30 100644 --- a/src/error/format.rs +++ b/src/error/format.rs @@ -227,6 +227,24 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> styled.none("'"); styled.warning(invalid_sub); 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 } else { false diff --git a/src/error/mod.rs b/src/error/mod.rs index af65529fce8..d88df46c689 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -504,17 +504,21 @@ impl Error { pub(crate) fn missing_subcommand( cmd: &Command, - name: String, + parent: String, + available: Vec, usage: Option, ) -> Self { let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] { - err = err.extend_context_unchecked([( - ContextKind::InvalidSubcommand, - ContextValue::String(name), - )]); + err = err.extend_context_unchecked([ + (ContextKind::InvalidSubcommand, ContextValue::String(parent)), + ( + ContextKind::ValidSubcommand, + ContextValue::Strings(available), + ), + ]); if let Some(usage) = usage { err = err .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage)); diff --git a/src/parser/validator.rs b/src/parser/validator.rs index 970804bcca7..0d97c2f6378 100644 --- a/src/parser/validator.rs +++ b/src/parser/validator.rs @@ -70,6 +70,10 @@ impl<'cmd> Validator<'cmd> { return Err(Error::missing_subcommand( self.cmd, bn.to_string(), + self.cmd + .all_subcommand_names() + .map(|s| s.to_owned()) + .collect::>(), Usage::new(self.cmd) .required(&self.required) .create_usage_with_title(&[]), diff --git a/tests/builder/app_settings.rs b/tests/builder/app_settings.rs index 8c1f4a34634..8cf8b27f8cc 100644 --- a/tests/builder/app_settings.rs +++ b/tests/builder/app_settings.rs @@ -63,6 +63,7 @@ fn sub_command_required() { 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