mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
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
This commit is contained in:
parent
031b71733c
commit
2c12757bbd
11 changed files with 217 additions and 6 deletions
|
@ -36,7 +36,7 @@ ARGS:
|
|||
<positional3>... tests positionals with specific values [values: vi, emacs]
|
||||
|
||||
SUBCOMMANDS:
|
||||
help Prints this message
|
||||
help With no arguments it prints this message, otherwise it prints help information about other subcommands
|
||||
subcmd tests subcommands'''
|
||||
|
||||
_version = "claptests v1.4.8"
|
||||
|
|
|
@ -39,3 +39,20 @@ impl<'b> AppMeta<'b> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Clone for AppMeta<'b> {
|
||||
fn clone(&self) -> Self {
|
||||
AppMeta {
|
||||
name: self.name.clone(),
|
||||
author: self.author.clone(),
|
||||
about: self.about.clone(),
|
||||
more_help: self.more_help.clone(),
|
||||
version: self.version.clone(),
|
||||
usage_str: self.usage_str.clone(),
|
||||
usage: self.usage.clone(),
|
||||
bin_name: self.bin_name.clone(),
|
||||
help_str: self.help_str.clone(),
|
||||
disp_ord: self.disp_ord,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -875,3 +875,11 @@ impl<'a> From<&'a Yaml> for App<'a, 'a> {
|
|||
a
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Clone for App<'a, 'b> {
|
||||
fn clone(&self) -> Self {
|
||||
App {
|
||||
p: self.p.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -481,7 +481,22 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
if pos_sc {
|
||||
if &*arg_os == "help" &&
|
||||
self.settings.is_set(AppSettings::NeedsSubcommandHelp) {
|
||||
return self._help();
|
||||
let cmds: Vec<OsString> = it.map(|c| c.into()).collect();
|
||||
let mut sc: &Parser = self;
|
||||
for (i, cmd) in cmds.iter().enumerate() {
|
||||
if let Some(c) = sc.subcommands.iter().filter(|s| &*s.p.meta.name == cmd).next().map(|sc| &sc.p) {
|
||||
sc = c;
|
||||
if i == cmds.len() - 1 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return Err(
|
||||
Error::unrecognized_subcommand(
|
||||
cmd.to_string_lossy().into_owned(),
|
||||
self.meta.bin_name.as_ref().unwrap_or(&self.meta.name)));
|
||||
}
|
||||
}
|
||||
return sc._help();
|
||||
}
|
||||
subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned());
|
||||
break;
|
||||
|
@ -500,7 +515,6 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
parse_positional!(self, p, arg_os, pos_only, pos_counter, matcher);
|
||||
} else {
|
||||
if self.settings.is_set(AppSettings::AllowExternalSubcommands) {
|
||||
// let arg_str = arg_os.to_str().expect(INVALID_UTF8);
|
||||
let mut sc_m = ArgMatcher::new();
|
||||
while let Some(v) = it.next() {
|
||||
let a = v.into();
|
||||
|
@ -521,7 +535,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
} else {
|
||||
return Err(Error::unknown_argument(
|
||||
&*arg_os.to_string_lossy(),
|
||||
"", //self.meta.bin_name.as_ref().unwrap_or(&self.meta.name),
|
||||
"",
|
||||
&*self.create_current_usage(matcher)));
|
||||
}
|
||||
}
|
||||
|
@ -800,7 +814,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
.iter()
|
||||
.any(|s| &s.p.meta.name[..] == "help") {
|
||||
debugln!("Building help");
|
||||
self.subcommands.push(App::new("help").about("Prints this message"));
|
||||
self.subcommands.push(App::new("help").about("With no arguments it prints this message, otherwise it prints help information about other subcommands"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1562,3 +1576,25 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Clone for Parser<'a, 'b> where 'a: 'b {
|
||||
fn clone(&self) -> Self {
|
||||
Parser {
|
||||
required: self.required.clone(),
|
||||
short_list: self.short_list.clone(),
|
||||
long_list: self.long_list.clone(),
|
||||
blacklist: self.blacklist.clone(),
|
||||
flags: self.flags.clone(),
|
||||
opts: self.opts.clone(),
|
||||
positionals: self.positionals.clone(),
|
||||
subcommands: self.subcommands.clone(),
|
||||
groups: self.groups.clone(),
|
||||
global_args: self.global_args.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
help_short: self.help_short.clone(),
|
||||
version_short: self.version_short.clone(),
|
||||
settings: self.settings.clone(),
|
||||
meta: self.meta.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,12 @@ bitflags! {
|
|||
#[derive(Debug)]
|
||||
pub struct AppFlags(Flags);
|
||||
|
||||
impl Clone for AppFlags {
|
||||
fn clone(&self) -> Self {
|
||||
AppFlags(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppFlags {
|
||||
fn default() -> Self {
|
||||
AppFlags(NEEDS_LONG_VERSION | NEEDS_LONG_HELP | NEEDS_SC_HELP | UTF8_NONE)
|
||||
|
@ -480,7 +486,7 @@ impl FromStr for AppSettings {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::AppSettings;
|
||||
|
||||
|
||||
#[test]
|
||||
fn app_settings_fromstr() {
|
||||
assert_eq!("subcommandsnegatereqs".parse::<AppSettings>().unwrap(), AppSettings::SubcommandsNegateReqs);
|
||||
|
|
|
@ -1911,3 +1911,29 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>>
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Clone for Arg<'a, 'b> {
|
||||
fn clone(&self) -> Self {
|
||||
Arg {
|
||||
name: self.name,
|
||||
short: self.short,
|
||||
long: self.long,
|
||||
help: self.help,
|
||||
index: self.index,
|
||||
possible_vals: self.possible_vals.clone(),
|
||||
blacklist: self.blacklist.clone(),
|
||||
requires: self.requires.clone(),
|
||||
num_vals: self.num_vals,
|
||||
min_vals: self.min_vals,
|
||||
max_vals: self.max_vals,
|
||||
val_names: self.val_names.clone(),
|
||||
group: self.group,
|
||||
validator: self.validator.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
settings: self.settings,
|
||||
val_delim: self.val_delim,
|
||||
default_val: self.default_val,
|
||||
disp_ord: self.disp_ord,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,22 @@ impl<'n, 'e> Display for FlagBuilder<'n, 'e> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> Clone for FlagBuilder<'n, 'e> {
|
||||
fn clone(&self) -> Self {
|
||||
FlagBuilder {
|
||||
name: self.name,
|
||||
short: self.short,
|
||||
long: self.long,
|
||||
help: self.help,
|
||||
blacklist: self.blacklist.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
requires: self.requires.clone(),
|
||||
settings: self.settings,
|
||||
disp_ord: self.disp_ord,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
|
||||
fn name(&self) -> &'n str { self.name }
|
||||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
|
|
|
@ -146,6 +146,30 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> Clone for OptBuilder<'n, 'e> {
|
||||
fn clone(&self) -> Self {
|
||||
OptBuilder {
|
||||
name: self.name,
|
||||
short: self.short,
|
||||
long: self.long,
|
||||
help: self.help,
|
||||
blacklist: self.blacklist.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
requires: self.requires.clone(),
|
||||
settings: self.settings,
|
||||
disp_ord: self.disp_ord,
|
||||
num_vals: self.num_vals,
|
||||
min_vals: self.min_vals,
|
||||
max_vals: self.max_vals,
|
||||
val_names: self.val_names.clone(),
|
||||
val_delim: self.val_delim,
|
||||
possible_vals: self.possible_vals.clone(),
|
||||
default_val: self.default_val,
|
||||
validator: self.validator.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
|
||||
fn name(&self) -> &'n str { self.name }
|
||||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
|
|
|
@ -135,6 +135,29 @@ impl<'n, 'e> Display for PosBuilder<'n, 'e> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> Clone for PosBuilder<'n, 'e> {
|
||||
fn clone(&self) -> Self {
|
||||
PosBuilder {
|
||||
name: self.name,
|
||||
help: self.help,
|
||||
blacklist: self.blacklist.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
requires: self.requires.clone(),
|
||||
settings: self.settings,
|
||||
disp_ord: self.disp_ord,
|
||||
num_vals: self.num_vals,
|
||||
min_vals: self.min_vals,
|
||||
max_vals: self.max_vals,
|
||||
val_names: self.val_names.clone(),
|
||||
val_delim: self.val_delim,
|
||||
possible_vals: self.possible_vals.clone(),
|
||||
default_val: self.default_val,
|
||||
validator: self.validator.clone(),
|
||||
index: self.index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
|
||||
fn name(&self) -> &'n str { self.name }
|
||||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
|
|
|
@ -486,3 +486,16 @@ requires:
|
|||
assert_eq!(g.conflicts, Some(confs));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Clone for ArgGroup<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
ArgGroup {
|
||||
name: self.name,
|
||||
required: self.required,
|
||||
args: self.args.clone(),
|
||||
requires: self.requires.clone(),
|
||||
conflicts: self.conflicts.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -64,6 +64,28 @@ pub enum ErrorKind {
|
|||
/// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidSubcommand);
|
||||
/// ```
|
||||
InvalidSubcommand,
|
||||
/// Occurs when the user provids an unrecognized subcommand which does not meet the threshold
|
||||
/// for being similar enough to an existing subcommand so as to not cause the more detailed
|
||||
/// `InvalidSubcommand` error.
|
||||
///
|
||||
/// This error typically happens when passing additional subcommand names to the `help`
|
||||
/// subcommand. Otherwise, the more general `UnknownArgument` error is used.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, ErrorKind, SubCommand};
|
||||
/// let result = App::new("myprog")
|
||||
/// .subcommand(SubCommand::with_name("config")
|
||||
/// .about("Used for configuration")
|
||||
/// .arg(Arg::with_name("config_file")
|
||||
/// .help("The configuration file to use")
|
||||
/// .index(1)))
|
||||
/// .get_matches_from_safe(vec!["myprog", "help", "nothing"]);
|
||||
/// assert!(result.is_err());
|
||||
/// assert_eq!(result.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand);
|
||||
/// ```
|
||||
UnrecognizedSubcommand,
|
||||
/// Occurs when the user provides an empty value for an option that does not allow empty
|
||||
/// values.
|
||||
///
|
||||
|
@ -441,6 +463,26 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn unrecognized_subcommand<S, N>(subcmd: S, name: N) -> Self
|
||||
where S: Into<String>,
|
||||
N: Display
|
||||
{
|
||||
let s = subcmd.into();
|
||||
Error {
|
||||
message: format!("{} The subcommand '{}' wasn't recognized\n\n\
|
||||
USAGE:\n\t\
|
||||
{} help <subcommands>...\n\n\
|
||||
For more information try {}",
|
||||
Format::Error("error:"),
|
||||
Format::Warning(&*s),
|
||||
name,
|
||||
Format::Good("--help")),
|
||||
kind: ErrorKind::UnrecognizedSubcommand,
|
||||
info: Some(vec![s]),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn missing_required_argument<R, U>(required: R, usage: U) -> Self
|
||||
where R: Display,
|
||||
|
|
Loading…
Reference in a new issue