Skip to content

Commit 8cefdf3

Browse files
authored
Merge pull request #4482 from epage/suggest
feat(parser): Show available subcommands when one is missing
2 parents 010976c + 6b62c82 commit 8cefdf3

File tree

5 files changed

+52
-5
lines changed

5 files changed

+52
-5
lines changed

src/error/context.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub enum ContextKind {
99
InvalidArg,
1010
/// Existing arguments
1111
PriorArg,
12+
/// Accepted subcommands
13+
ValidSubcommand,
1214
/// Accepted values
1315
ValidValue,
1416
/// Rejected values
@@ -44,6 +46,7 @@ impl ContextKind {
4446
Self::InvalidSubcommand => Some("Invalid Subcommand"),
4547
Self::InvalidArg => Some("Invalid Argument"),
4648
Self::PriorArg => Some("Prior Argument"),
49+
Self::ValidSubcommand => Some("Value Subcommand"),
4750
Self::ValidValue => Some("Value Value"),
4851
Self::InvalidValue => Some("Invalid Value"),
4952
Self::ActualNumValues => Some("Actual Number of Values"),

src/error/format.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,24 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) ->
227227
styled.none("'");
228228
styled.warning(invalid_sub);
229229
styled.none("' requires a subcommand but one was not provided");
230+
231+
let possible_values = error.get(ContextKind::ValidSubcommand);
232+
if let Some(ContextValue::Strings(possible_values)) = possible_values {
233+
if !possible_values.is_empty() {
234+
styled.none("\n");
235+
styled.none(TAB);
236+
styled.none("[subcommands: ");
237+
if let Some((last, elements)) = possible_values.split_last() {
238+
for v in elements {
239+
styled.good(escape(v));
240+
styled.none(", ");
241+
}
242+
styled.good(escape(last));
243+
}
244+
styled.none("]");
245+
}
246+
}
247+
230248
true
231249
} else {
232250
false

src/error/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -504,17 +504,21 @@ impl<F: ErrorFormatter> Error<F> {
504504

505505
pub(crate) fn missing_subcommand(
506506
cmd: &Command,
507-
name: String,
507+
parent: String,
508+
available: Vec<String>,
508509
usage: Option<StyledStr>,
509510
) -> Self {
510511
let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
511512

512513
#[cfg(feature = "error-context")]
513514
{
514-
err = err.extend_context_unchecked([(
515-
ContextKind::InvalidSubcommand,
516-
ContextValue::String(name),
517-
)]);
515+
err = err.extend_context_unchecked([
516+
(ContextKind::InvalidSubcommand, ContextValue::String(parent)),
517+
(
518+
ContextKind::ValidSubcommand,
519+
ContextValue::Strings(available),
520+
),
521+
]);
518522
if let Some(usage) = usage {
519523
err = err
520524
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));

src/parser/validator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ impl<'cmd> Validator<'cmd> {
7070
return Err(Error::missing_subcommand(
7171
self.cmd,
7272
bn.to_string(),
73+
self.cmd
74+
.all_subcommand_names()
75+
.map(|s| s.to_owned())
76+
.collect::<Vec<_>>(),
7377
Usage::new(self.cmd)
7478
.required(&self.required)
7579
.create_usage_with_title(&[]),

tests/builder/app_settings.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ fn sub_command_required() {
5858
assert_eq!(err.kind(), ErrorKind::MissingSubcommand);
5959
}
6060

61+
#[test]
62+
#[cfg(feature = "error-context")]
63+
fn sub_command_required_error() {
64+
static ERROR: &str = "\
65+
error: 'sc_required' requires a subcommand but one was not provided
66+
[subcommands: sub1, help]
67+
68+
Usage: sc_required <COMMAND>
69+
70+
For more information try '--help'
71+
";
72+
73+
let cmd = Command::new("sc_required")
74+
.subcommand_required(true)
75+
.subcommand(Command::new("sub1"));
76+
utils::assert_output(cmd, "sc_required", ERROR, true);
77+
}
78+
6179
#[test]
6280
fn arg_required_else_help() {
6381
let result = Command::new("arg_required")

0 commit comments

Comments
 (0)