Skip to content

Commit 2c12757

Browse files
committed
feat(Help Subcommand): adds support passing additional subcommands to help subcommand
The `help` subcommand can now accept other subcommands as arguments to display their help message. This is similar to how many other CLIs already perform. For example: ``` $ myprog help mysubcmd ``` Would print the help message for `mysubcmd`. But even more, the `help` subcommand accepts nested subcommands as well, i.e. a grandchild subcommand such as ``` $ myprog help child grandchild ``` Would print the help message of `grandchild` where `grandchild` is a subcommand of `child` and `child` is a subcommand of `myprog`. Closes #416
1 parent 031b717 commit 2c12757

File tree

11 files changed

+217
-6
lines changed

11 files changed

+217
-6
lines changed

clap-tests/run_tests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<positional3>... tests positionals with specific values [values: vi, emacs]
3737
3838
SUBCOMMANDS:
39-
help Prints this message
39+
help With no arguments it prints this message, otherwise it prints help information about other subcommands
4040
subcmd tests subcommands'''
4141

4242
_version = "claptests v1.4.8"

src/app/meta.rs

+17
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,20 @@ impl<'b> AppMeta<'b> {
3939
}
4040
}
4141
}
42+
43+
impl<'b> Clone for AppMeta<'b> {
44+
fn clone(&self) -> Self {
45+
AppMeta {
46+
name: self.name.clone(),
47+
author: self.author.clone(),
48+
about: self.about.clone(),
49+
more_help: self.more_help.clone(),
50+
version: self.version.clone(),
51+
usage_str: self.usage_str.clone(),
52+
usage: self.usage.clone(),
53+
bin_name: self.bin_name.clone(),
54+
help_str: self.help_str.clone(),
55+
disp_ord: self.disp_ord,
56+
}
57+
}
58+
}

src/app/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -875,3 +875,11 @@ impl<'a> From<&'a Yaml> for App<'a, 'a> {
875875
a
876876
}
877877
}
878+
879+
impl<'a, 'b> Clone for App<'a, 'b> {
880+
fn clone(&self) -> Self {
881+
App {
882+
p: self.p.clone(),
883+
}
884+
}
885+
}

src/app/parser.rs

+40-4
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,22 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
481481
if pos_sc {
482482
if &*arg_os == "help" &&
483483
self.settings.is_set(AppSettings::NeedsSubcommandHelp) {
484-
return self._help();
484+
let cmds: Vec<OsString> = it.map(|c| c.into()).collect();
485+
let mut sc: &Parser = self;
486+
for (i, cmd) in cmds.iter().enumerate() {
487+
if let Some(c) = sc.subcommands.iter().filter(|s| &*s.p.meta.name == cmd).next().map(|sc| &sc.p) {
488+
sc = c;
489+
if i == cmds.len() - 1 {
490+
break;
491+
}
492+
} else {
493+
return Err(
494+
Error::unrecognized_subcommand(
495+
cmd.to_string_lossy().into_owned(),
496+
self.meta.bin_name.as_ref().unwrap_or(&self.meta.name)));
497+
}
498+
}
499+
return sc._help();
485500
}
486501
subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned());
487502
break;
@@ -500,7 +515,6 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
500515
parse_positional!(self, p, arg_os, pos_only, pos_counter, matcher);
501516
} else {
502517
if self.settings.is_set(AppSettings::AllowExternalSubcommands) {
503-
// let arg_str = arg_os.to_str().expect(INVALID_UTF8);
504518
let mut sc_m = ArgMatcher::new();
505519
while let Some(v) = it.next() {
506520
let a = v.into();
@@ -521,7 +535,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
521535
} else {
522536
return Err(Error::unknown_argument(
523537
&*arg_os.to_string_lossy(),
524-
"", //self.meta.bin_name.as_ref().unwrap_or(&self.meta.name),
538+
"",
525539
&*self.create_current_usage(matcher)));
526540
}
527541
}
@@ -800,7 +814,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
800814
.iter()
801815
.any(|s| &s.p.meta.name[..] == "help") {
802816
debugln!("Building help");
803-
self.subcommands.push(App::new("help").about("Prints this message"));
817+
self.subcommands.push(App::new("help").about("With no arguments it prints this message, otherwise it prints help information about other subcommands"));
804818
}
805819
}
806820

@@ -1562,3 +1576,25 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
15621576
Ok(())
15631577
}
15641578
}
1579+
1580+
impl<'a, 'b> Clone for Parser<'a, 'b> where 'a: 'b {
1581+
fn clone(&self) -> Self {
1582+
Parser {
1583+
required: self.required.clone(),
1584+
short_list: self.short_list.clone(),
1585+
long_list: self.long_list.clone(),
1586+
blacklist: self.blacklist.clone(),
1587+
flags: self.flags.clone(),
1588+
opts: self.opts.clone(),
1589+
positionals: self.positionals.clone(),
1590+
subcommands: self.subcommands.clone(),
1591+
groups: self.groups.clone(),
1592+
global_args: self.global_args.clone(),
1593+
overrides: self.overrides.clone(),
1594+
help_short: self.help_short.clone(),
1595+
version_short: self.version_short.clone(),
1596+
settings: self.settings.clone(),
1597+
meta: self.meta.clone(),
1598+
}
1599+
}
1600+
}

src/app/settings.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ bitflags! {
3232
#[derive(Debug)]
3333
pub struct AppFlags(Flags);
3434

35+
impl Clone for AppFlags {
36+
fn clone(&self) -> Self {
37+
AppFlags(self.0)
38+
}
39+
}
40+
3541
impl Default for AppFlags {
3642
fn default() -> Self {
3743
AppFlags(NEEDS_LONG_VERSION | NEEDS_LONG_HELP | NEEDS_SC_HELP | UTF8_NONE)
@@ -480,7 +486,7 @@ impl FromStr for AppSettings {
480486
#[cfg(test)]
481487
mod test {
482488
use super::AppSettings;
483-
489+
484490
#[test]
485491
fn app_settings_fromstr() {
486492
assert_eq!("subcommandsnegatereqs".parse::<AppSettings>().unwrap(), AppSettings::SubcommandsNegateReqs);

src/args/arg.rs

+26
Original file line numberDiff line numberDiff line change
@@ -1911,3 +1911,29 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>>
19111911
}
19121912
}
19131913
}
1914+
1915+
impl<'a, 'b> Clone for Arg<'a, 'b> {
1916+
fn clone(&self) -> Self {
1917+
Arg {
1918+
name: self.name,
1919+
short: self.short,
1920+
long: self.long,
1921+
help: self.help,
1922+
index: self.index,
1923+
possible_vals: self.possible_vals.clone(),
1924+
blacklist: self.blacklist.clone(),
1925+
requires: self.requires.clone(),
1926+
num_vals: self.num_vals,
1927+
min_vals: self.min_vals,
1928+
max_vals: self.max_vals,
1929+
val_names: self.val_names.clone(),
1930+
group: self.group,
1931+
validator: self.validator.clone(),
1932+
overrides: self.overrides.clone(),
1933+
settings: self.settings,
1934+
val_delim: self.val_delim,
1935+
default_val: self.default_val,
1936+
disp_ord: self.disp_ord,
1937+
}
1938+
}
1939+
}

src/args/arg_builder/flag.rs

+16
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ impl<'n, 'e> Display for FlagBuilder<'n, 'e> {
9393
}
9494
}
9595

96+
impl<'n, 'e> Clone for FlagBuilder<'n, 'e> {
97+
fn clone(&self) -> Self {
98+
FlagBuilder {
99+
name: self.name,
100+
short: self.short,
101+
long: self.long,
102+
help: self.help,
103+
blacklist: self.blacklist.clone(),
104+
overrides: self.overrides.clone(),
105+
requires: self.requires.clone(),
106+
settings: self.settings,
107+
disp_ord: self.disp_ord,
108+
}
109+
}
110+
}
111+
96112
impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
97113
fn name(&self) -> &'n str { self.name }
98114
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }

src/args/arg_builder/option.rs

+24
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,30 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> {
146146
}
147147
}
148148

149+
impl<'n, 'e> Clone for OptBuilder<'n, 'e> {
150+
fn clone(&self) -> Self {
151+
OptBuilder {
152+
name: self.name,
153+
short: self.short,
154+
long: self.long,
155+
help: self.help,
156+
blacklist: self.blacklist.clone(),
157+
overrides: self.overrides.clone(),
158+
requires: self.requires.clone(),
159+
settings: self.settings,
160+
disp_ord: self.disp_ord,
161+
num_vals: self.num_vals,
162+
min_vals: self.min_vals,
163+
max_vals: self.max_vals,
164+
val_names: self.val_names.clone(),
165+
val_delim: self.val_delim,
166+
possible_vals: self.possible_vals.clone(),
167+
default_val: self.default_val,
168+
validator: self.validator.clone(),
169+
}
170+
}
171+
}
172+
149173
impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
150174
fn name(&self) -> &'n str { self.name }
151175
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }

src/args/arg_builder/positional.rs

+23
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,29 @@ impl<'n, 'e> Display for PosBuilder<'n, 'e> {
135135
}
136136
}
137137

138+
impl<'n, 'e> Clone for PosBuilder<'n, 'e> {
139+
fn clone(&self) -> Self {
140+
PosBuilder {
141+
name: self.name,
142+
help: self.help,
143+
blacklist: self.blacklist.clone(),
144+
overrides: self.overrides.clone(),
145+
requires: self.requires.clone(),
146+
settings: self.settings,
147+
disp_ord: self.disp_ord,
148+
num_vals: self.num_vals,
149+
min_vals: self.min_vals,
150+
max_vals: self.max_vals,
151+
val_names: self.val_names.clone(),
152+
val_delim: self.val_delim,
153+
possible_vals: self.possible_vals.clone(),
154+
default_val: self.default_val,
155+
validator: self.validator.clone(),
156+
index: self.index,
157+
}
158+
}
159+
}
160+
138161
impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
139162
fn name(&self) -> &'n str { self.name }
140163
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }

src/args/group.rs

+13
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,16 @@ requires:
486486
assert_eq!(g.conflicts, Some(confs));
487487
}
488488
}
489+
490+
impl<'a> Clone for ArgGroup<'a> {
491+
fn clone(&self) -> Self {
492+
ArgGroup {
493+
name: self.name,
494+
required: self.required,
495+
args: self.args.clone(),
496+
requires: self.requires.clone(),
497+
conflicts: self.conflicts.clone(),
498+
}
499+
}
500+
501+
}

src/errors.rs

+42
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ pub enum ErrorKind {
6464
/// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidSubcommand);
6565
/// ```
6666
InvalidSubcommand,
67+
/// Occurs when the user provids an unrecognized subcommand which does not meet the threshold
68+
/// for being similar enough to an existing subcommand so as to not cause the more detailed
69+
/// `InvalidSubcommand` error.
70+
///
71+
/// This error typically happens when passing additional subcommand names to the `help`
72+
/// subcommand. Otherwise, the more general `UnknownArgument` error is used.
73+
///
74+
/// # Examples
75+
///
76+
/// ```rust
77+
/// # use clap::{App, Arg, ErrorKind, SubCommand};
78+
/// let result = App::new("myprog")
79+
/// .subcommand(SubCommand::with_name("config")
80+
/// .about("Used for configuration")
81+
/// .arg(Arg::with_name("config_file")
82+
/// .help("The configuration file to use")
83+
/// .index(1)))
84+
/// .get_matches_from_safe(vec!["myprog", "help", "nothing"]);
85+
/// assert!(result.is_err());
86+
/// assert_eq!(result.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand);
87+
/// ```
88+
UnrecognizedSubcommand,
6789
/// Occurs when the user provides an empty value for an option that does not allow empty
6890
/// values.
6991
///
@@ -441,6 +463,26 @@ impl Error {
441463
}
442464
}
443465

466+
#[doc(hidden)]
467+
pub fn unrecognized_subcommand<S, N>(subcmd: S, name: N) -> Self
468+
where S: Into<String>,
469+
N: Display
470+
{
471+
let s = subcmd.into();
472+
Error {
473+
message: format!("{} The subcommand '{}' wasn't recognized\n\n\
474+
USAGE:\n\t\
475+
{} help <subcommands>...\n\n\
476+
For more information try {}",
477+
Format::Error("error:"),
478+
Format::Warning(&*s),
479+
name,
480+
Format::Good("--help")),
481+
kind: ErrorKind::UnrecognizedSubcommand,
482+
info: Some(vec![s]),
483+
}
484+
}
485+
444486
#[doc(hidden)]
445487
pub fn missing_required_argument<R, U>(required: R, usage: U) -> Self
446488
where R: Display,

0 commit comments

Comments
 (0)