diff --git a/CHANGELOG.md b/CHANGELOG.md index 6310bc72e47..537249d90be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,11 @@ -## v2.2.0 (2016-03-14) +## v2.2.0 (2016-03-15) -#### Documentation - -* **Groups:** explains required ArgGroups better ([4ff0205b](https://github.com/kbknapp/clap-rs/commit/4ff0205b85a45151b59bbaf090a89df13438380f), closes [#439](https://github.com/kbknapp/clap-rs/issues/439)) - #### Features * **Help Message:** can auto wrap and aligning help text to term width ([e36af026](https://github.com/kbknapp/clap-rs/commit/e36af0266635f23e85e951b9088d561e9a5d1bf6), closes [#428](https://github.com/kbknapp/clap-rs/issues/428)) +* **Help Subcommand:** adds support passing additional subcommands to help subcommand ([2c12757b](https://github.com/kbknapp/clap-rs/commit/2c12757bbdf34ce481f3446c074e24c09c2e60fd), closes [#416](https://github.com/kbknapp/clap-rs/issues/416)) * **Opts and Flags:** adds support for custom ordering in help messages ([9803b51e](https://github.com/kbknapp/clap-rs/commit/9803b51e799904c0befaac457418ee766ccc1ab9)) * **Settings:** adds support for automatically deriving custom display order of args ([ad86e433](https://github.com/kbknapp/clap-rs/commit/ad86e43334c4f70e86909689a088fb87e26ff95a), closes [#444](https://github.com/kbknapp/clap-rs/issues/444)) * **Subcommands:** adds support for custom ordering in help messages ([7d2a2ed4](https://github.com/kbknapp/clap-rs/commit/7d2a2ed413f5517d45988eef0765cdcd663b6372), closes [#442](https://github.com/kbknapp/clap-rs/issues/442)) @@ -17,7 +14,9 @@ * **From Usage:** fixes a bug where adding empty lines werent ignored ([c5c58c86](https://github.com/kbknapp/clap-rs/commit/c5c58c86b9c503d8de19da356a5a5cffb59fbe84)) +#### Documentation +* **Groups:** explains required ArgGroups better ([4ff0205b](https://github.com/kbknapp/clap-rs/commit/4ff0205b85a45151b59bbaf090a89df13438380f), closes [#439](https://github.com/kbknapp/clap-rs/issues/439)) ### v2.1.2 (2016-02-24) diff --git a/README.md b/README.md index 96dd16ca03b..754dee24aa4 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Here's the highlights from v2.2.0 * **Help text auto wraps and aligns at term width!** - Long help strings will now properly wrap and align to term width on Linux and OSX (and resumably Unix too). This can be turned off as well. * **Can customize the order of opts, flags, and subcommands in help messages** - Instead of using the default alphabetical order, you can now re-arange the order of your args and subcommands in help message. This helps to emphasize more popular or important options. * **Can auto-derive the order from declaration order** - Have a bunch of args or subcommmands to re-order? You can now just derive the order from the declaration order! +* **Help subcommand now accepts other subcommands as arguments!** - Similar to other CLI precedents, the `help` subcommand can now accept other subcommands as arguments to display their help message. i.e. `$ myprog help mysubcmd` (*Note* these can even be nested heavily such as `$ myprog help subcmd1 subcmd2 subcmd3` etc.) + * Other minor bug fixes An example of the help text wrapping at term width: diff --git a/clap-tests/run_tests.py b/clap-tests/run_tests.py index 3e55a8eb359..bbd6140fd9f 100755 --- a/clap-tests/run_tests.py +++ b/clap-tests/run_tests.py @@ -36,7 +36,7 @@ ... 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" diff --git a/src/app/meta.rs b/src/app/meta.rs index 335d77f592d..3ca96e1890b 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -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, + } + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index cd252ed1f41..1ae13a591a5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -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(), + } + } +} diff --git a/src/app/parser.rs b/src/app/parser.rs index 05e373d052e..40ba1124a1d 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -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 = 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(), + } + } +} diff --git a/src/app/settings.rs b/src/app/settings.rs index 3c08f8f55d9..1c08c7df307 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -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::().unwrap(), AppSettings::SubcommandsNegateReqs); diff --git a/src/args/arg.rs b/src/args/arg.rs index 17bd8ec358a..caed4db9ef2 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -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, + } + } +} diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 452a0e37999..dfbebbbce5d 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -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[..]) } diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index a8c4fd4b601..5cdaa6d1292 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -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[..]) } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 289af2f713a..590b934026e 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -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[..]) } diff --git a/src/args/group.rs b/src/args/group.rs index 726ace6b9a8..5dbc3c66eb7 100644 --- a/src/args/group.rs +++ b/src/args/group.rs @@ -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(), + } + } + +} diff --git a/src/errors.rs b/src/errors.rs index dc54fd83dfe..60eee79af42 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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(subcmd: S, name: N) -> Self + where S: Into, + N: Display + { + let s = subcmd.into(); + Error { + message: format!("{} The subcommand '{}' wasn't recognized\n\n\ + USAGE:\n\t\ + {} help ...\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(required: R, usage: U) -> Self where R: Display,