From 92cc30577dc25e629eb5946ba0beb93b4db7e123 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 4 Apr 2017 19:54:20 -0400 Subject: [PATCH 01/11] tests: adds tests to verify help and version can be properly overridden --- tests/help.rs | 13 +++++++++++++ tests/version.rs | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/help.rs b/tests/help.rs index 3f273931..89ed7ec6 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -831,3 +831,16 @@ fn hidden_default_val() { .default_value("default-argument")); assert!(test::compare_output(app2, "default --help", HIDE_DEFAULT_VAL, false)); } + +#[test] +fn override_help() { + let m = App::new("test") + .author("Kevin K.") + .about("tests stuff") + .version("1.3") + .arg(Arg::from_usage("-H, --help 'some help'")) + .get_matches_from_safe(vec!["test", "--help"]); + + assert!(m.is_ok()); + assert!(m.unwrap().is_present("help")); +} \ No newline at end of file diff --git a/tests/version.rs b/tests/version.rs index f6e9ff8b..de0f8179 100644 --- a/tests/version.rs +++ b/tests/version.rs @@ -3,7 +3,7 @@ extern crate regex; use std::str; -use clap::{App, ErrorKind}; +use clap::{App, Arg, ErrorKind}; include!("../clap-test.rs"); @@ -43,3 +43,16 @@ fn complex_version_output() { a.write_version(&mut ver).unwrap(); assert_eq!(str::from_utf8(&ver).unwrap(), VERSION); } + +#[test] +fn override_ver() { + let m = App::new("test") + .author("Kevin K.") + .about("tests stuff") + .version("1.3") + .arg(Arg::from_usage("-v, --version 'some version'")) + .get_matches_from_safe(vec!["test", "--version"]); + + assert!(m.is_ok()); + assert!(m.unwrap().is_present("version")); +} \ No newline at end of file From 8b2ceb8368bcb70689fadf1c7f4b9549184926c1 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 4 Apr 2017 19:59:03 -0400 Subject: [PATCH 02/11] fix: fixes a bug that wasn't allowing help and version to be properly overridden One should be able to override the auto generated help and version flags by simply providing an arg with a long of `help` or `version` respectively. This bug was preventing that from happening. However, now that it's fixed if one was relying on adding an arg with a long of `help` or `version` and using `get_matches_safe` to produce the `ErrorKind::HelpDisplayed` or `ErrorKind::VersionDisplayed` errors they **WILL NOT** happen anymore. This is because the bug was causing those "errors" to occur (i.e. the help or version message to be displayed). The "fix" to get that functionality back is to either: * Remove the arg with the long of `help` or `version` * Use `ArgMatches::is_present` instead of `App::get_matches_safe` to check for the presence of your custom help or version flag Option 1 is the easiest provided one wasn't using said arg to *also* override other aspects of the flags as well (short, help message, etc.) Even though this may break some code, as per the compatibility policy listed in the readme; code that breaks due to relying on a bug does *not* constitute a major version bump. I will however be bumping the minor version instead of just the patch version. I will also be searching for, contacting, and attempting to submit PRs to any affected projects prior to releasing the next version to crates.io Closes #922 --- src/app/parser.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app/parser.rs b/src/app/parser.rs index d0cdae9e..e26cb0f8 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -206,6 +206,13 @@ impl<'a, 'b> Parser<'a, 'b> self.set(AS::DontCollapseArgsInUsage); self.set(AS::ContainsLast); } + if let Some(l) = a.s.long { + if l == "version" { + self.unset(AS::NeedsLongVersion); + } else if l == "help" { + self.unset(AS::NeedsLongHelp); + } + } } // actually adds the arguments From b48a5bb9308bfc418bc029864c93c7e9588a02dd Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 4 Apr 2017 20:11:33 -0400 Subject: [PATCH 03/11] chore: bump minor version due to bug fix that may break code that was relying on said bug --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b41c32ac..306fd5c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clap" -version = "2.22.2" +version = "2.23.0" authors = ["Kevin K. "] exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"] repository = "https://github.com/kbknapp/clap-rs.git" From ef1b24c3a0dff2f58c5e2e90880fbc2b69df20ee Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 4 Apr 2017 23:36:18 -0400 Subject: [PATCH 04/11] feat: allows specifying a short help vs a long help (i.e. varying levels of detail depending on if -h or --help was used) One can now use `Arg::long_help` which will be displayed when the user runs `--help`. This is typically longer form content and will be displayed using the NextLineHelp methodology. If one specifies the standard `Arg::help` it will be displayed when the user runs `-h` (by default, unless the help short has been overridden). The help text will be displayed in the typical clap fashion. The help format requested (-h/short or --help/long) will be the *default* displayed. Meaning, if one runs `--help` (long) but only an `Arg::help` has been provided, the message from `Arg::help` will be used. Likewise, if one requests `-h` (short) but only `Arg::long_help` was provided, `Arg::long_help` will be displayed appropriately wrapped and aligned. Completion script generation *only* uses `Arg::help` in order to be concise. cc @BurntSushi thanks for the idea! --- src/app/help.rs | 92 ++++++++++++++++++------------ src/app/meta.rs | 50 ++-------------- src/app/mod.rs | 3 +- src/app/parser.rs | 28 +++++++-- src/args/any_arg.rs | 1 + src/args/arg.rs | 83 ++++++++++++++++++++++++++- src/args/arg_builder/base.rs | 1 + src/args/arg_builder/flag.rs | 1 + src/args/arg_builder/option.rs | 1 + src/args/arg_builder/positional.rs | 1 + 10 files changed, 168 insertions(+), 93 deletions(-) diff --git a/src/app/help.rs b/src/app/help.rs index 1b400ef4..c79142fe 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -89,6 +89,7 @@ pub struct Help<'a> { cizer: Colorizer, longest: usize, force_next_line: bool, + use_long: bool, } // Public Functions @@ -100,7 +101,8 @@ impl<'a> Help<'a> { color: bool, cizer: Colorizer, term_w: Option, - max_w: Option) + max_w: Option, + use_long: bool) -> Self { debugln!("Help::new;"); Help { @@ -121,21 +123,22 @@ impl<'a> Help<'a> { cizer: cizer, longest: 0, force_next_line: false, + use_long: use_long, } } /// Reads help settings from an App /// and write its help to the wrapped stream. - pub fn write_app_help(w: &'a mut Write, app: &App) -> ClapResult<()> { + pub fn write_app_help(w: &'a mut Write, app: &App, use_long: bool) -> ClapResult<()> { debugln!("Help::write_app_help;"); - Self::write_parser_help(w, &app.p) + Self::write_parser_help(w, &app.p, use_long) } /// Reads help settings from a Parser /// and write its help to the wrapped stream. - pub fn write_parser_help(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { + pub fn write_parser_help(w: &'a mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> { debugln!("Help::write_parser_help;"); - Self::_write_parser_help(w, parser, false) + Self::_write_parser_help(w, parser, false, use_long) } /// Reads help settings from a Parser @@ -143,11 +146,11 @@ impl<'a> Help<'a> { /// formatting when required. pub fn write_parser_help_to_stderr(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { debugln!("Help::write_parser_help;"); - Self::_write_parser_help(w, parser, true) + Self::_write_parser_help(w, parser, true, false) } #[doc(hidden)] - pub fn _write_parser_help(w: &'a mut Write, parser: &Parser, stderr: bool) -> ClapResult<()> { + pub fn _write_parser_help(w: &'a mut Write, parser: &Parser, stderr: bool, use_long: bool) -> ClapResult<()> { debugln!("Help::write_parser_help;"); let nlh = parser.is_set(AppSettings::NextLineHelp); let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); @@ -162,8 +165,9 @@ impl<'a> Help<'a> { color, cizer, parser.meta.term_w, - parser.meta.max_w) - .write_help(parser) + parser.meta.max_w, + use_long) + .write_help(parser) } /// Writes the parser help to the wrapped stream. @@ -191,8 +195,9 @@ impl<'a> Help<'a> { self.longest = 2; let mut arg_v = Vec::with_capacity(10); for arg in args.filter(|arg| { - !(arg.is_set(ArgSettings::Hidden)) || arg.is_set(ArgSettings::NextLineHelp) - }) { + !(arg.is_set(ArgSettings::Hidden)) || + arg.is_set(ArgSettings::NextLineHelp) + }) { if arg.longest_filter() { self.longest = cmp::max(self.longest, arg.to_string().len()); } @@ -432,8 +437,12 @@ impl<'a> Help<'a> { fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, spec_vals: &str) -> io::Result<()> { debugln!("Help::help;"); let mut help = String::new(); - let h = arg.help().unwrap_or(""); - let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp); + let h = if self.use_long { + arg.long_help().unwrap_or(arg.help().unwrap_or("")) + } else { + arg.help().unwrap_or(arg.long_help().unwrap_or("")) + }; + let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) || self.use_long; debugln!("Help::help: Next Line...{:?}", nlh); let spcs = if nlh || self.force_next_line { @@ -513,7 +522,8 @@ impl<'a> Help<'a> { debugln!("Help::spec_vals: Found aliases...{:?}", aliases); spec_vals.push(format!(" [aliases: {}]", if self.color { - aliases.iter() + aliases + .iter() .map(|v| format!("{}", self.cizer.good(v))) .collect::>() .join(", ") @@ -525,14 +535,14 @@ impl<'a> Help<'a> { if let Some(pv) = a.possible_vals() { debugln!("Help::spec_vals: Found possible vals...{:?}", pv); spec_vals.push(if self.color { - format!(" [values: {}]", - pv.iter() - .map(|v| format!("{}", self.cizer.good(v))) - .collect::>() - .join(", ")) - } else { - format!(" [values: {}]", pv.join(", ")) - }); + format!(" [values: {}]", + pv.iter() + .map(|v| format!("{}", self.cizer.good(v))) + .collect::>() + .join(", ")) + } else { + format!(" [values: {}]", pv.join(", ")) + }); } } spec_vals.join(" ") @@ -548,7 +558,10 @@ impl<'a> Help<'a> { pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { debugln!("Help::write_all_args;"); let flags = parser.has_flags(); - let pos = parser.positionals().filter(|arg| !arg.is_set(ArgSettings::Hidden)).count() > 0; + let pos = parser + .positionals() + .filter(|arg| !arg.is_set(ArgSettings::Hidden)) + .count() > 0; let opts = parser.has_opts(); let subcmds = parser.has_subcommands(); @@ -557,7 +570,8 @@ impl<'a> Help<'a> { let mut first = true; if unified_help && (flags || opts) { - let opts_flags = parser.flags() + let opts_flags = parser + .flags() .map(as_arg_trait) .chain(parser.opts().map(as_arg_trait)); try!(color!(self, "OPTIONS:\n", warning)); @@ -566,8 +580,7 @@ impl<'a> Help<'a> { } else { if flags { try!(color!(self, "FLAGS:\n", warning)); - try!(self.write_args(parser.flags() - .map(as_arg_trait))); + try!(self.write_args(parser.flags().map(as_arg_trait))); first = false; } if opts { @@ -606,8 +619,13 @@ impl<'a> Help<'a> { // The shortest an arg can legally be is 2 (i.e. '-x') self.longest = 2; let mut ord_m = VecMap::new(); - for sc in parser.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) { - let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); + for sc in parser + .subcommands + .iter() + .filter(|s| !s.p.is_set(AppSettings::Hidden)) { + let btm = ord_m + .entry(sc.p.meta.disp_ord) + .or_insert(BTreeMap::new()); self.longest = cmp::max(self.longest, sc.p.meta.name.len()); btm.insert(sc.p.meta.name.clone(), sc.clone()); } @@ -861,9 +879,9 @@ impl<'a> Help<'a> { debugln!("Help::write_template_help:iter: tag_buf={};", unsafe { String::from_utf8_unchecked(tag_buf.get_ref()[0..tag_length] - .iter() - .map(|&i| i) - .collect::>()) + .iter() + .map(|&i| i) + .collect::>()) }); match &tag_buf.get_ref()[0..tag_length] { b"?" => { @@ -894,22 +912,20 @@ impl<'a> Help<'a> { try!(self.write_all_args(&parser)); } b"unified" => { - let opts_flags = parser.flags() + let opts_flags = parser + .flags() .map(as_arg_trait) .chain(parser.opts().map(as_arg_trait)); try!(self.write_args(opts_flags)); } b"flags" => { - try!(self.write_args(parser.flags() - .map(as_arg_trait))); + try!(self.write_args(parser.flags().map(as_arg_trait))); } b"options" => { - try!(self.write_args(parser.opts() - .map(as_arg_trait))); + try!(self.write_args(parser.opts().map(as_arg_trait))); } b"positionals" => { - try!(self.write_args(parser.positionals() - .map(as_arg_trait))); + try!(self.write_args(parser.positionals().map(as_arg_trait))); } b"subcommands" => { try!(self.write_subcommands(&parser)); diff --git a/src/app/meta.rs b/src/app/meta.rs index 33b2f355..0985e999 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -1,11 +1,13 @@ #[doc(hidden)] #[allow(missing_debug_implementations)] +#[derive(Default, Clone)] pub struct AppMeta<'b> { pub name: String, pub bin_name: Option, pub author: Option<&'b str>, pub version: Option<&'b str>, pub about: Option<&'b str>, + pub long_about: Option<&'b str>, pub more_help: Option<&'b str>, pub pre_help: Option<&'b str>, pub aliases: Option>, // (name, visible) @@ -18,51 +20,7 @@ pub struct AppMeta<'b> { pub template: Option<&'b str>, } -impl<'b> Default for AppMeta<'b> { - fn default() -> Self { - AppMeta { - name: String::new(), - author: None, - about: None, - more_help: None, - pre_help: None, - version: None, - usage_str: None, - usage: None, - bin_name: None, - help_str: None, - disp_ord: 999, - template: None, - aliases: None, - term_w: None, - max_w: None, - } - } -} - impl<'b> AppMeta<'b> { pub fn new() -> Self { Default::default() } - pub fn with_name(s: String) -> Self { AppMeta { name: s, ..Default::default() } } -} - -impl<'b> Clone for AppMeta<'b> { - fn clone(&self) -> Self { - AppMeta { - name: self.name.clone(), - author: self.author, - about: self.about, - more_help: self.more_help, - pre_help: self.pre_help, - version: self.version, - usage_str: self.usage_str, - usage: self.usage.clone(), - bin_name: self.bin_name.clone(), - help_str: self.help_str, - disp_ord: self.disp_ord, - template: self.template, - aliases: self.aliases.clone(), - term_w: self.term_w, - max_w: self.max_w, - } - } -} + pub fn with_name(s: String) -> Self { AppMeta { name: s, disp_ord: 999, ..Default::default() } } +} \ No newline at end of file diff --git a/src/app/mod.rs b/src/app/mod.rs index 428bcdbd..f4226ccc 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1108,7 +1108,7 @@ impl<'a, 'b> App<'a, 'b> { // self.p.derive_display_order(); // self.p.create_help_and_version(); - Help::write_app_help(w, self) + Help::write_app_help(w, self, false) } /// Writes the version message to the user to a [`io::Write`] object @@ -1622,6 +1622,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { fn val_delim(&self) -> Option { None } fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.p.meta.about } + fn long_help(&self) -> Option<&'e str> { self.p.meta.long_about } fn default_val(&self) -> Option<&'e OsStr> { None } fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { None diff --git a/src/app/parser.rs b/src/app/parser.rs index e26cb0f8..f8082dab 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -680,7 +680,7 @@ impl<'a, 'b> Parser<'a, 'b> if sc.meta.bin_name != self.meta.bin_name { sc.meta.bin_name = Some(format!("{} {}", bin_name, sc.meta.name)); } - sc._help() + sc._help(false) } // allow wrong self convention due to self.valid_neg_num = true and it's a private method @@ -1246,7 +1246,7 @@ impl<'a, 'b> Parser<'a, 'b> arg.to_str().unwrap()); if arg == "help" && self.is_set(AS::NeedsLongHelp) { sdebugln!("Help"); - try!(self._help()); + try!(self._help(true)); } if arg == "version" && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); @@ -1264,7 +1264,7 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(h) = self.help_short { if arg == h && self.is_set(AS::NeedsLongHelp) { sdebugln!("Help"); - try!(self._help()); + try!(self._help(false)); } } if let Some(v) = self.version_short { @@ -1277,9 +1277,20 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn _help(&self) -> ClapResult<()> { + fn use_long_help(&self) -> bool { + let ul = self.flags.iter().any(|f| f.b.long_help.is_some()) || + self.opts.iter().any(|o| o.b.long_help.is_some()) || + self.positionals.values().any(|p| p.b.long_help.is_some()) || + self.subcommands.iter().any(|s| s.p.meta.long_about.is_some()); + debugln!("Parser::use_long_help: ret={:?}", ul); + ul + } + + fn _help(&self, mut use_long: bool) -> ClapResult<()> { + debugln!("Parser::_help: use_long={:?}", use_long); + use_long = use_long && self.use_long_help(); let mut buf = vec![]; - try!(Help::write_parser_help(&mut buf, self)); + try!(Help::write_parser_help(&mut buf, self, use_long)); Err(Error { message: unsafe { String::from_utf8_unchecked(buf) }, kind: ErrorKind::HelpDisplayed, @@ -1288,6 +1299,7 @@ impl<'a, 'b> Parser<'a, 'b> } fn _version(&self) -> ClapResult<()> { + debugln!("Parser::_version: "); let out = io::stdout(); let mut buf_w = BufWriter::new(out.lock()); try!(self.print_version(&mut buf_w)); @@ -1630,7 +1642,11 @@ impl<'a, 'b> Parser<'a, 'b> } pub fn write_help(&self, w: &mut W) -> ClapResult<()> { - Help::write_parser_help(w, self) + Help::write_parser_help(w, self, false) + } + + pub fn write_long_help(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help(w, self, true) } pub fn write_help_err(&self, w: &mut W) -> ClapResult<()> { diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 55245dba..5f8615fc 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -32,6 +32,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display { fn takes_value(&self) -> bool; fn val_names(&self) -> Option<&VecMap<&'e str>>; fn help(&self) -> Option<&'e str>; + fn long_help(&self) -> Option<&'e str>; fn default_val(&self) -> Option<&'e OsStr>; fn default_vals_ifs(&self) -> Option, &'e OsStr)>>; fn longest_filter(&self) -> bool; diff --git a/src/args/arg.rs b/src/args/arg.rs index 9f57a883..860477c6 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -103,6 +103,7 @@ impl<'a, 'b> Arg<'a, 'b> { "long" => yaml_to_str!(a, v, long), "aliases" => yaml_vec_or_str!(v, a, alias), "help" => yaml_to_str!(a, v, help), + "long_help" => yaml_to_str!(a, v, long_help), "required" => yaml_to_bool!(a, v, required), "required_if" => yaml_tuple2!(a, v, required_if), "required_ifs" => yaml_tuple2!(a, v, required_if), @@ -489,8 +490,14 @@ impl<'a, 'b> Arg<'a, 'b> { self } - /// Sets the help text of the argument that will be displayed to the user when they print the - /// usage/help information. + /// Sets the short help text of the argument that will be displayed to the user when they print + /// the help information with `-h`. Typically, this is a short (one line) description of the + /// arg. + /// + /// **NOTE:** If only `Arg::help` is provided, and not [`Arg::long_help`] but the user requests + /// `--help` clap will still display the contents of `help` appropriately + /// + /// **NOTE:** Only `Arg::help` is used in completion script generation in order to be concise /// /// # Examples /// @@ -532,11 +539,83 @@ impl<'a, 'b> Arg<'a, 'b> { /// -h, --help Prints help information /// -V, --version Prints version information /// ``` + /// [`Arg::long_help`]: ./struct.Arg.html#method.long_help pub fn help(mut self, h: &'b str) -> Self { self.b.help = Some(h); self } + /// Sets the long help text of the argument that will be displayed to the user when they print + /// the help information with `--help`. Typically this a more detailed (multi-line) message + /// that describes the arg. + /// + /// **NOTE:** If only `long_help` is provided, and not [`Arg::help`] but the user requests `-h` + /// clap will still display the contents of `long_help` appropriately + /// + /// **NOTE:** Only [`Arg::help`] is used in completion script generation in order to be concise + /// + /// # Examples + /// + /// Any valid UTF-8 is allowed in the help text. The one exception is when one wishes to + /// include a newline in the help text and have the following text be properly aligned with all + /// the other help text. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .long_help( + /// "The config file used by the myprog must be in JSON format + /// with only valid keys and may not contain other nonsense + /// that cannot be read by this program. Obviously I'm going on + /// and on, so I'll stop now.") + /// # ; + /// ``` + /// + /// Setting `help` displays a short message to the side of the argument when the user passes + /// `-h` or `--help` (by default). + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .long_help( + /// "The config file used by the myprog must be in JSON format + /// with only valid keys and may not contain other nonsense + /// that cannot be read by this program. Obviously I'm going on + /// and on, so I'll stop now.")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// --config + /// The config file used by the myprog must be in JSON format + /// with only valid keys and may not contain other nonsense + /// that cannot be read by this program. Obviously I'm going on + /// and on, so I'll stop now. + /// + /// -h, --help + /// Prints help information + /// + /// -V, --version + /// Prints version information + /// ``` + /// [`Arg::help`]: ./struct.Arg.html#method.help + pub fn long_help(mut self, h: &'b str) -> Self { + self.b.long_help = Some(h); + self + } + /// Specifies that this arg is the last, or final, positional argument (i.e. has the highest /// index) and is *only* able to be accessed via the `--` syntax (i.e. `$ prog args -- /// last_arg`). Even, if no other arguments are left to parse, if the user omits the `--` syntax diff --git a/src/args/arg_builder/base.rs b/src/args/arg_builder/base.rs index 8b566e38..5990bc08 100644 --- a/src/args/arg_builder/base.rs +++ b/src/args/arg_builder/base.rs @@ -7,6 +7,7 @@ pub struct Base<'a, 'b> { pub name: &'a str, pub help: Option<&'b str>, + pub long_help: Option<&'b str>, pub blacklist: Option>, pub settings: ArgFlags, pub r_unless: Option>, diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index d1c16690..e3e9dd01 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -79,6 +79,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn long(&self) -> Option<&'e str> { self.s.long } fn val_delim(&self) -> Option { None } fn help(&self) -> Option<&'e str> { self.b.help } + fn long_help(&self) -> Option<&'e str> { self.b.long_help } fn val_terminator(&self) -> Option<&'e str> { None } fn default_val(&self) -> Option<&'e OsStr> { None } fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 342ccbdc..64dd9c46 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -129,6 +129,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn val_delim(&self) -> Option { self.v.val_delim } fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.b.help } + fn long_help(&self) -> Option<&'e str> { self.b.long_help } fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index ddbd8964..c77976b1 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -126,6 +126,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn val_delim(&self) -> Option { self.v.val_delim } fn takes_value(&self) -> bool { true } fn help(&self) -> Option<&'e str> { self.b.help } + fn long_help(&self) -> Option<&'e str> { self.b.long_help } fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) } From 6b371891a1702173a849d1e95f9fecb168bf6fc4 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 00:10:21 -0400 Subject: [PATCH 05/11] feat: allows distinguishing between short and long help with subcommands in the same manner as args One can use `App::about` for `-h` short help messsages or `App::long_about` for `--help` long help messages. --- src/app/mod.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index f4226ccc..f5761e17 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -200,7 +200,13 @@ impl<'a, 'b> App<'a, 'b> { } /// Sets a string describing what the program does. This will be displayed when displaying help - /// information. + /// information with `-h`. + /// + /// **NOTE:** If only `about` is provided, and not [`App::long_about`] but the user requests + /// `--help` clap will still display the contents of `about` appropriately + /// + /// **NOTE:** Only [`App::about`] is used in completion script generation in order to be + /// concise /// /// # Examples /// @@ -210,11 +216,38 @@ impl<'a, 'b> App<'a, 'b> { /// .about("Does really amazing things to great people") /// # ; /// ``` + /// [`App::long_about`]: ./struct.App.html#method.long_about pub fn about>(mut self, about: S) -> Self { self.p.meta.about = Some(about.into()); self } + /// Sets a string describing what the program does. This will be displayed when displaying help + /// information. + /// + /// **NOTE:** If only `long_about` is provided, and not [`App::about`] but the user requests + /// `-h` clap will still display the contents of `long_about` appropriately + /// + /// **NOTE:** Only [`App::about`] is used in completion script generation in order to be + /// concise + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .long_about( + /// "Does really amazing things to great people. Now let's talk a little + /// more in depth about how this subcommand really works. It may take about + /// a few lines of text, but that's ok!") + /// # ; + /// ``` + /// [`App::about`]: ./struct.App.html#method.about + pub fn long_about>(mut self, about: S) -> Self { + self.p.meta.long_about = Some(about.into()); + self + } + /// Sets the program's name. This will be displayed when displaying help information. /// /// **Pro-top:** This function is particularly useful when configuring a program via From d82b4be5d7f3f9c2aa960bc23dbbc87df3863b7d Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 00:11:50 -0400 Subject: [PATCH 06/11] api: adds new App method calls for printing and writing the long version of help messages One can now use `App::print_long_help` or `App::write_long_help` Note that `App::write_long_help` is **NOT** affected by kbknapp/clap-rs#808 in the manner that `App::write_help` help is and thus should be preferred if at all possible. --- src/app/mod.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index f5761e17..bfd7db51 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1095,7 +1095,11 @@ impl<'a, 'b> App<'a, 'b> { self } - /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] + /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same + /// method as if someone ran `-h` to request the help message + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] /// /// # Examples /// @@ -1106,6 +1110,8 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html /// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help pub fn print_help(&mut self) -> ClapResult<()> { // If there are global arguments, or settings we need to propgate them down to subcommands // before parsing incase we run into a subcommand @@ -1119,7 +1125,45 @@ impl<'a, 'b> App<'a, 'b> { self.write_help(&mut buf_w) } - /// Writes the full help message to the user to a [`io::Write`] object + /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same + /// method as if someone ran `-h` to request the help message + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// let mut app = App::new("myprog"); + /// app.print_long_help(); + /// ``` + /// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html + /// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help + pub fn print_long_help(&mut self) -> ClapResult<()> { + // If there are global arguments, or settings we need to propgate them down to subcommands + // before parsing incase we run into a subcommand + self.p.propogate_globals(); + self.p.propogate_settings(); + self.p.derive_display_order(); + + self.p.create_help_and_version(); + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + self.write_long_help(&mut buf_w) + } + + /// Writes the full help message to the user to a [`io::Write`] object in the same method as if + /// the user ran `-h` + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// **NOTE:** There is a known bug where this method does not write propogated global arguments + /// or autogenerated arguments (i.e. the default help/version args). Prefer + /// [`App::write_long_help`] instead if possibe! /// /// # Examples /// @@ -1131,6 +1175,8 @@ impl<'a, 'b> App<'a, 'b> { /// app.write_help(&mut out).expect("failed to write to stdout"); /// ``` /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help pub fn write_help(&self, w: &mut W) -> ClapResult<()> { // PENDING ISSUE: 808 // https://github.com/kbknapp/clap-rs/issues/808 @@ -1144,6 +1190,33 @@ impl<'a, 'b> App<'a, 'b> { Help::write_app_help(w, self, false) } + /// Writes the full help message to the user to a [`io::Write`] object in the same method as if + /// the user ran `--help` + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_long_help(&mut out).expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help + pub fn write_long_help(&mut self, w: &mut W) -> ClapResult<()> { + self.p.propogate_globals(); + self.p.propogate_settings(); + self.p.derive_display_order(); + self.p.create_help_and_version(); + + Help::write_app_help(w, self, true) + } + /// Writes the version message to the user to a [`io::Write`] object /// /// # Examples @@ -1216,7 +1289,7 @@ impl<'a, 'b> App<'a, 'b> { /// build = "build.rs" /// /// [build-dependencies] - /// clap = "2.9" + /// clap = "2.23" /// ``` /// /// Next, we place a `build.rs` in our project root. From 59272b06cc213289dc604dbc694cb95d383a5d68 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 00:34:29 -0400 Subject: [PATCH 07/11] feat: allows distinguishing between short and long version messages (-V/short or --version/long) One can now use `App::long_version` to dsiplay a different (presumably longer) message when `--version` is called. This commit also adds the corresponding print/write long version methods of `App` --- src/app/meta.rs | 1 + src/app/mod.rs | 66 +++++++++++++- src/app/parser.rs | 220 ++++++++++++++++++++++------------------------ 3 files changed, 168 insertions(+), 119 deletions(-) diff --git a/src/app/meta.rs b/src/app/meta.rs index 0985e999..6fbf412c 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -6,6 +6,7 @@ pub struct AppMeta<'b> { pub bin_name: Option, pub author: Option<&'b str>, pub version: Option<&'b str>, + pub long_version: Option<&'b str>, pub about: Option<&'b str>, pub long_about: Option<&'b str>, pub more_help: Option<&'b str>, diff --git a/src/app/mod.rs b/src/app/mod.rs index bfd7db51..0c9f9aee 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -310,7 +310,10 @@ impl<'a, 'b> App<'a, 'b> { } /// Sets a string of the version number to be displayed when displaying version or help - /// information. + /// information with `-V`. + /// + /// **NOTE:** If only `version` is provided, and not [`App::long_version`] but the user + /// requests `--version` clap will still display the contents of `version` appropriately /// /// **Pro-tip:** Use `clap`s convenience macro [`crate_version!`] to automatically set your /// application's version to the same thing as your crate at compile time. See the [`examples/`] @@ -326,11 +329,43 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`crate_version!`]: ./macro.crate_version!.html /// [`examples/`]: https://github.com/kbknapp/clap-rs/tree/master/examples + /// [`App::long_version`]: ./struct.App.html#method.long_version pub fn version>(mut self, ver: S) -> Self { self.p.meta.version = Some(ver.into()); self } + /// Sets a string of the version number to be displayed when displaying version or help + /// information with `--version`. + /// + /// **NOTE:** If only `long_version` is provided, and not [`App::version`] but the user + /// requests `-V` clap will still display the contents of `long_version` appropriately + /// + /// **Pro-tip:** Use `clap`s convenience macro [`crate_version!`] to automatically set your + /// application's version to the same thing as your crate at compile time. See the [`examples/`] + /// directory for more information + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .long_version( + /// "v0.1.24 + /// commit: abcdef89726d + /// revision: 123 + /// release: 2 + /// binary: myprog") + /// # ; + /// ``` + /// [`crate_version!`]: ./macro.crate_version!.html + /// [`examples/`]: https://github.com/kbknapp/clap-rs/tree/master/examples + /// [`App::version`]: ./struct.App.html#method.version + pub fn long_version>(mut self, ver: S) -> Self { + self.p.meta.long_version = Some(ver.into()); + self + } + /// Sets a custom usage string to override the auto-generated usage string. /// /// This will be displayed to the user when errors are found in argument parsing, or when you @@ -1217,7 +1252,10 @@ impl<'a, 'b> App<'a, 'b> { Help::write_app_help(w, self, true) } - /// Writes the version message to the user to a [`io::Write`] object + /// Writes the version message to the user to a [`io::Write`] object as if the user ran `-V`. + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages + /// depending on if the user ran [`-V` (short)] or [`--version` (long)] /// /// # Examples /// @@ -1229,10 +1267,32 @@ impl<'a, 'b> App<'a, 'b> { /// app.write_version(&mut out).expect("failed to write to stdout"); /// ``` /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-V` (short)]: ./struct.App.html#method.version + /// [`--version` (long)]: ./struct.App.html#method.long_version pub fn write_version(&self, w: &mut W) -> ClapResult<()> { - self.p.write_version(w).map_err(From::from) + self.p.write_version(w, false).map_err(From::from) } + /// Writes the version message to the user to a [`io::Write`] object + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages + /// depending on if the user ran [`-V` (short)] or [`--version` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_long_version(&mut out).expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-V` (short)]: ./struct.App.html#method.version + /// [`--version` (long)]: ./struct.App.html#method.long_version + pub fn write_long_version(&self, w: &mut W) -> ClapResult<()> { + self.p.write_version(w, true).map_err(From::from) + } /// Generate a completions file for a specified shell at compile time. /// diff --git a/src/app/parser.rs b/src/app/parser.rs index f8082dab..4c6b8664 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -63,7 +63,10 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { pub fn with_name(n: String) -> Self { - Parser { meta: AppMeta::with_name(n), ..Default::default() } + Parser { + meta: AppMeta::with_name(n), + ..Default::default() + } } pub fn help_short(&mut self, s: &str) { @@ -98,11 +101,7 @@ impl<'a, 'b> Parser<'a, 'b> use std::error::Error; let out_dir = PathBuf::from(od); - let name = &*self.meta - .bin_name - .as_ref() - .unwrap() - .clone(); + let name = &*self.meta.bin_name.as_ref().unwrap().clone(); let file_name = match for_shell { Shell::Bash => format!("{}.bash-completion", name), Shell::Fish => format!("{}.fish", name), @@ -146,7 +145,9 @@ impl<'a, 'b> Parser<'a, 'b> global and required", a.b.name); if a.b.is_set(ArgSettings::Last) { - assert!(!self.positionals.values().any(|p| p.b.is_set(ArgSettings::Last)), + assert!(!self.positionals + .values() + .any(|p| p.b.is_set(ArgSettings::Last)), "Only one positional argument may have last(true) set. Found two."); assert!(a.s.long.is_none(), "Flags or Options may not have last(true) set. {} has both a long and last(true) set.", @@ -190,7 +191,10 @@ impl<'a, 'b> Parser<'a, 'b> if a.is_set(ArgSettings::Required) { // If the arg is required, add all it's requirements to master required list if let Some(ref areqs) = a.b.requires { - for name in areqs.iter().filter(|&&(val, _)| val.is_none()).map(|&(_, name)| name) { + for name in areqs + .iter() + .filter(|&&(val, _)| val.is_none()) + .map(|&(_, name)| name) { self.required.push(name); } } @@ -232,7 +236,8 @@ impl<'a, 'b> Parser<'a, 'b> } else { a.index.unwrap() as usize }; - self.positionals.insert(i, PosBuilder::from_arg(a, i as u64)); + self.positionals + .insert(i, PosBuilder::from_arg(a, i as u64)); } else if a.is_set(ArgSettings::TakesValue) { let mut ob = OptBuilder::from(a); ob.s.unified_ord = self.flags.len() + self.opts.len(); @@ -326,11 +331,7 @@ impl<'a, 'b> Parser<'a, 'b> if vsc { sc.p.set(AS::DisableVersion); } - if gv && - sc.p - .meta - .version - .is_none() && self.meta.version.is_some() { + if gv && sc.p.meta.version.is_none() && self.meta.version.is_some() { sc.p.set(AS::GlobalVersion); sc.p.meta.version = Some(self.meta.version.unwrap()); } @@ -404,7 +405,9 @@ impl<'a, 'b> Parser<'a, 'b> if self.flags.is_empty() { return false; } - self.flags.iter().any(|f| !f.is_set(ArgSettings::Hidden)) + self.flags + .iter() + .any(|f| !f.is_set(ArgSettings::Hidden)) } #[inline] @@ -412,7 +415,9 @@ impl<'a, 'b> Parser<'a, 'b> if self.positionals.is_empty() { return false; } - self.positionals.values().any(|p| !p.is_set(ArgSettings::Hidden)) + self.positionals + .values() + .any(|p| !p.is_set(ArgSettings::Hidden)) } #[inline] @@ -440,10 +445,7 @@ impl<'a, 'b> Parser<'a, 'b> // Firt we verify that the index highest supplied index, is equal to the number of // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3 // but no 2) - if let Some((idx, p)) = self.positionals - .iter() - .rev() - .next() { + if let Some((idx, p)) = self.positionals.iter().rev().next() { assert!(!(idx != self.positionals.len()), "Found positional argument \"{}\" who's index is {} but there \ are only {} positional arguments defined", @@ -453,10 +455,12 @@ impl<'a, 'b> Parser<'a, 'b> } // Next we verify that only the highest index has a .multiple(true) (if any) - if self.positionals.values().any(|a| { - a.b.is_set(ArgSettings::Multiple) && - (a.index as usize != self.positionals.len()) - }) { + if self.positionals + .values() + .any(|a| { + a.b.is_set(ArgSettings::Multiple) && + (a.index as usize != self.positionals.len()) + }) { let mut it = self.positionals.values().rev(); let last = it.next().unwrap(); let second_to_last = it.next().unwrap(); @@ -542,10 +546,11 @@ impl<'a, 'b> Parser<'a, 'b> } } } - if self.positionals.values().any(|p| { - p.b.is_set(ArgSettings::Last) && - p.b.is_set(ArgSettings::Required) - }) && self.has_subcommands() && + if self.positionals + .values() + .any(|p| { + p.b.is_set(ArgSettings::Last) && p.b.is_set(ArgSettings::Required) + }) && self.has_subcommands() && !self.is_set(AS::SubcommandsNegateReqs) { panic!("Having a required positional argument with .last(true) set *and* child \ subcommands without setting SubcommandsNegateReqs isn't compatible."); @@ -594,10 +599,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .filter(|s| { starts(&s.p.meta.name[..], &*arg_os) || - (s.p - .meta - .aliases - .is_some() && + (s.p.meta.aliases.is_some() && s.p .meta .aliases @@ -750,11 +752,16 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::get_matches_with;"); // Verify all positional assertions pass debug_assert!(self.verify_positionals()); - if self.positionals.values().any(|a| { - a.b.is_set(ArgSettings::Multiple) && - (a.index as usize != self.positionals.len()) - }) && - self.positionals.values().last().map_or(false, |p| !p.is_set(ArgSettings::Last)) { + if self.positionals + .values() + .any(|a| { + a.b.is_set(ArgSettings::Multiple) && + (a.index as usize != self.positionals.len()) + }) && + self.positionals + .values() + .last() + .map_or(false, |p| !p.is_set(ArgSettings::Last)) { self.settings.set(AS::LowIndexMultiplePositional); } let has_args = self.has_args(); @@ -843,7 +850,8 @@ impl<'a, 'b> Parser<'a, 'b> !self.is_set(AS::InferSubcommands) { if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) { - return Err(Error::invalid_subcommand(arg_os.to_string_lossy() + return Err(Error::invalid_subcommand(arg_os + .to_string_lossy() .into_owned(), cdate, self.meta @@ -962,7 +970,8 @@ impl<'a, 'b> Parser<'a, 'b> None), self.color())); } else { - return Err(Error::unrecognized_subcommand(arg_os.to_string_lossy() + return Err(Error::unrecognized_subcommand(arg_os + .to_string_lossy() .into_owned(), self.meta .bin_name @@ -984,10 +993,7 @@ impl<'a, 'b> Parser<'a, 'b> }; try!(self.parse_subcommand(&*sc_name, matcher, it)); } else if self.is_set(AS::SubcommandRequired) { - let bn = self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta.name); + let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); return Err(Error::missing_subcommand(bn, &usage::create_error_usage(self, matcher, None), self.color())); @@ -1018,10 +1024,7 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::build_bin_names;"); for sc in &mut self.subcommands { debug!("Parser::build_bin_names:iter: bin_name set..."); - if sc.p - .meta - .bin_name - .is_none() { + if sc.p.meta.bin_name.is_none() { sdebugln!("No"); let bin_name = format!("{}{}{}", self.meta @@ -1059,10 +1062,7 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::parse_subcommand;"); let mut mid_string = String::new(); if !self.is_set(AS::SubcommandsNegateReqs) { - let mut hs: Vec<&str> = self.required - .iter() - .map(|n| &**n) - .collect(); + let mut hs: Vec<&str> = self.required.iter().map(|n| &**n).collect(); for k in matcher.arg_names() { hs.push(k); } @@ -1073,41 +1073,35 @@ impl<'a, 'b> Parser<'a, 'b> } } mid_string.push_str(" "); - if let Some(ref mut sc) = self.subcommands.iter_mut().find(|s| &s.p.meta.name == &sc_name) { + if let Some(ref mut sc) = self.subcommands + .iter_mut() + .find(|s| &s.p.meta.name == &sc_name) { let mut sc_matcher = ArgMatcher::new(); // bin_name should be parent's bin_name + [] + the sc's name separated by // a space sc.p.meta.usage = Some(format!("{}{}{}", - self.meta - .bin_name - .as_ref() - .unwrap_or(&String::new()), + self.meta.bin_name.as_ref().unwrap_or(&String::new()), if self.meta.bin_name.is_some() { &*mid_string } else { "" }, &*sc.p.meta.name)); - sc.p.meta.bin_name = Some(format!("{}{}{}", - self.meta - .bin_name - .as_ref() - .unwrap_or(&String::new()), - if self.meta.bin_name.is_some() { - " " - } else { - "" - }, - &*sc.p.meta.name)); + sc.p.meta.bin_name = + Some(format!("{}{}{}", + self.meta.bin_name.as_ref().unwrap_or(&String::new()), + if self.meta.bin_name.is_some() { + " " + } else { + "" + }, + &*sc.p.meta.name)); debugln!("Parser::parse_subcommand: About to parse sc={}", sc.p.meta.name); debugln!("Parser::parse_subcommand: sc settings={:#?}", sc.p.settings); try!(sc.p.get_matches_with(&mut sc_matcher, it)); matcher.subcommand(SubCommand { - name: sc.p - .meta - .name - .clone(), + name: sc.p.meta.name.clone(), matches: sc_matcher.into(), }); } @@ -1218,7 +1212,8 @@ impl<'a, 'b> Parser<'a, 'b> let arg = FlagBuilder { b: Base { name: "vclap_version", - help: self.version_message.or(Some("Prints version information")), + help: self.version_message + .or(Some("Prints version information")), ..Default::default() }, s: Switched { @@ -1250,7 +1245,7 @@ impl<'a, 'b> Parser<'a, 'b> } if arg == "version" && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); - try!(self._version()); + try!(self._version(true)); } sdebugln!("Neither"); @@ -1270,7 +1265,7 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(v) = self.version_short { if arg == v && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); - try!(self._version()); + try!(self._version(false)); } } sdebugln!("Neither"); @@ -1279,9 +1274,13 @@ impl<'a, 'b> Parser<'a, 'b> fn use_long_help(&self) -> bool { let ul = self.flags.iter().any(|f| f.b.long_help.is_some()) || - self.opts.iter().any(|o| o.b.long_help.is_some()) || - self.positionals.values().any(|p| p.b.long_help.is_some()) || - self.subcommands.iter().any(|s| s.p.meta.long_about.is_some()); + self.opts.iter().any(|o| o.b.long_help.is_some()) || + self.positionals + .values() + .any(|p| p.b.long_help.is_some()) || + self.subcommands + .iter() + .any(|s| s.p.meta.long_about.is_some()); debugln!("Parser::use_long_help: ret={:?}", ul); ul } @@ -1298,11 +1297,11 @@ impl<'a, 'b> Parser<'a, 'b> }) } - fn _version(&self) -> ClapResult<()> { + fn _version(&self, use_long: bool) -> ClapResult<()> { debugln!("Parser::_version: "); let out = io::stdout(); let mut buf_w = BufWriter::new(out.lock()); - try!(self.print_version(&mut buf_w)); + try!(self.print_version(&mut buf_w, use_long)); Err(Error { message: String::new(), kind: ErrorKind::VersionDisplayed, @@ -1363,7 +1362,8 @@ impl<'a, 'b> Parser<'a, 'b> } debugln!("Parser::parse_long_arg: Didn't match anything"); - self.did_you_mean_error(arg.to_str().expect(INVALID_UTF8), matcher).map(|_| None) + self.did_you_mean_error(arg.to_str().expect(INVALID_UTF8), matcher) + .map(|_| None) } #[cfg_attr(feature = "lints", allow(len_zero))] @@ -1489,7 +1489,8 @@ impl<'a, 'b> Parser<'a, 'b> matcher.inc_occurrence_of(opt.b.name); // Increment or create the group "args" - self.groups_for_arg(opt.b.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); + self.groups_for_arg(opt.b.name) + .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); if val.is_none() || !has_eq && @@ -1575,7 +1576,8 @@ impl<'a, 'b> Parser<'a, 'b> matcher.inc_occurrence_of(flag.b.name); // Increment or create the group "args" - self.groups_for_arg(flag.b.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); + self.groups_for_arg(flag.b.name) + .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); Ok(()) } @@ -1608,30 +1610,30 @@ impl<'a, 'b> Parser<'a, 'b> } // Prints the version to the user and exits if quit=true - fn print_version(&self, w: &mut W) -> ClapResult<()> { - try!(self.write_version(w)); + fn print_version(&self, w: &mut W, use_long: bool) -> ClapResult<()> { + try!(self.write_version(w, use_long)); w.flush().map_err(Error::from) } - pub fn write_version(&self, w: &mut W) -> io::Result<()> { + pub fn write_version(&self, w: &mut W, use_long: bool) -> io::Result<()> { + let ver = if use_long { + self.meta + .long_version + .unwrap_or(self.meta.version.unwrap_or("".into())) + } else { + self.meta + .version + .unwrap_or(self.meta.long_version.unwrap_or("".into())) + }; if let Some(bn) = self.meta.bin_name.as_ref() { if bn.contains(' ') { // Incase we're dealing with subcommands i.e. git mv is translated to git-mv - write!(w, - "{} {}", - bn.replace(" ", "-"), - self.meta.version.unwrap_or("".into())) + write!(w, "{} {}", bn.replace(" ", "-"), ver) } else { - write!(w, - "{} {}", - &self.meta.name[..], - self.meta.version.unwrap_or("".into())) + write!(w, "{} {}", &self.meta.name[..], ver) } } else { - write!(w, - "{} {}", - &self.meta.name[..], - self.meta.version.unwrap_or("".into())) + write!(w, "{} {}", &self.meta.name[..], ver) } } @@ -1753,20 +1755,10 @@ impl<'a, 'b> Parser<'a, 'b> pub fn find_subcommand(&'b self, sc: &str) -> Option<&'b App<'a, 'b>> { debugln!("Parser::find_subcommand: sc={}", sc); debugln!("Parser::find_subcommand: Currently in Parser...{}", - self.meta - .bin_name - .as_ref() - .unwrap()); + self.meta.bin_name.as_ref().unwrap()); for s in self.subcommands.iter() { - if s.p - .meta - .bin_name - .as_ref() - .unwrap_or(&String::new()) == sc || - (s.p - .meta - .aliases - .is_some() && + if s.p.meta.bin_name.as_ref().unwrap_or(&String::new()) == sc || + (s.p.meta.aliases.is_some() && s.p .meta .aliases @@ -1774,12 +1766,8 @@ impl<'a, 'b> Parser<'a, 'b> .unwrap() .iter() .any(|&(s, _)| { - s == - sc.split(' ') - .rev() - .next() - .expect(INTERNAL_ERROR_MSG) - })) { + s == sc.split(' ').rev().next().expect(INTERNAL_ERROR_MSG) + })) { return Some(s); } if let Some(app) = s.p.find_subcommand(sc) { From bc08ef3e185393073d969d301989b6319c616c1f Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 00:47:19 -0400 Subject: [PATCH 08/11] docs(clap_app!): documents the `--("some-arg")` method for using args with hyphens inside them Closes #919 --- src/macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index 6fc7763a..05254207 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -562,6 +562,8 @@ macro_rules! app_from_crate { /// /// * A single hyphen followed by a character (such as `-c`) sets the [`Arg::short`] /// * A double hyphen followed by a character or word (such as `--config`) sets [`Arg::long`] +/// * If one wishes to use a [`Arg::long`] with a hyphen inside (i.e. `--config-file`), you +/// must use `--("config-file")` due to limitations of the Rust macro system. /// * Three dots (`...`) sets [`Arg::multiple(true)`] /// * Angled brackets after either a short or long will set [`Arg::value_name`] and /// `Arg::required(true)` such as `--config ` = `Arg::value_name("FILE")` and From c83a559df1f93b0999ff066891b8ac161fd48930 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 00:57:40 -0400 Subject: [PATCH 09/11] tests(clap_app!): adds tests for ("config-file") style positonal args --- tests/macros.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/macros.rs b/tests/macros.rs index cc25e1bf..035983bb 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -110,3 +110,40 @@ fn quoted_arg_long_name() { .expect("Expected to successfully match the given args."); assert!(matches.is_present("option2")); } + +#[test] +fn quoted_arg_name() { + let app = clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@arg opt: -o --option +takes_value ... "tests options") + (@arg ("positional-arg"): index(1) "tests positionals") + (@arg flag: -f --flag ... +global "tests flags") + (@arg flag2: -F conflicts_with[flag] requires[option2] + "tests flags with exclusions") + (@arg option2: --("long-option-2") conflicts_with[option] requires[positional2] + "tests long options with exclusions") + (@arg positional2: index(2) "tests positionals with exclusions") + (@arg option3: -O --Option +takes_value possible_value[fast slow] + "tests options with specific value sets") + (@arg ("positional-3"): index(3) ... possible_value[vi emacs] + "tests positionals with specific values") + (@arg multvals: --multvals +takes_value value_name[one two] + "Tests mutliple values, not mult occs") + (@arg multvalsmo: --multvalsmo ... +takes_value value_name[one two] + "Tests mutliple values, not mult occs") + (@arg minvals: --minvals2 min_values(1) ... +takes_value "Tests 2 min vals") + (@arg maxvals: --maxvals3 ... +takes_value max_values(3) "Tests 3 max vals") + (@subcommand subcmd => + (about: "tests subcommands") + (version: "0.1") + (author: "Kevin K. ") + (@arg scoption: -o --option ... +takes_value "tests options") + (@arg scpositional: index(1) "tests positionals")) + ); + + let matches = app.get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"]) + .expect("Expected to successfully match the given args."); + assert!(matches.is_present("option2")); +} From f7a8877978c8f90e6543d4f0d9600c086cf92cd7 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 00:57:47 -0400 Subject: [PATCH 10/11] feat(clap_app!): adds support for arg names with hyphens similar to longs with hyphens One can now use `("config-file")` style arg names Closes #869 --- src/macros.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index 05254207..650042fb 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -597,6 +597,13 @@ macro_rules! app_from_crate { #[macro_export] macro_rules! clap_app { (@app ($builder:expr)) => { $builder }; + (@app ($builder:expr) (@arg ($name:expr): $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + ($builder.arg( + clap_app!{ @arg ($crate::Arg::with_name($name)) (-) $($tail)* })) + $($tt)* + } + }; (@app ($builder:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => { clap_app!{ @app ($builder.arg( From cdae4fdf89a2eed940c0a8bc86bcfe683fc5057a Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 5 Apr 2017 01:09:16 -0400 Subject: [PATCH 11/11] chore: increase version --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ README.md | 20 ++++++++++++++++---- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c6beab..19ee5e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ + +## v2.23.0 (2017-04-05) + + +#### API Additions + +* `App::long_about` +* `App::long_version` +* `App::print_long_help` +* `App::write_long_help` +* `App::print_long_version` +* `App::write_long_version` +* `Arg::long_help` + +#### Features + +* allows distinguishing between short and long version messages (-V/short or --version/long) ([59272b06](https://github.com/kbknapp/clap-rs/commit/59272b06cc213289dc604dbc694cb95d383a5d68)) +* allows distinguishing between short and long help with subcommands in the same manner as args ([6b371891](https://github.com/kbknapp/clap-rs/commit/6b371891a1702173a849d1e95f9fecb168bf6fc4)) +* allows specifying a short help vs a long help (i.e. varying levels of detail depending on if -h or --help was used) ([ef1b24c3](https://github.com/kbknapp/clap-rs/commit/ef1b24c3a0dff2f58c5e2e90880fbc2b69df20ee)) +* **clap_app!:** adds support for arg names with hyphens similar to longs with hyphens ([f7a88779](https://github.com/kbknapp/clap-rs/commit/f7a8877978c8f90e6543d4f0d9600c086cf92cd7), closes [#869](https://github.com/kbknapp/clap-rs/issues/869)) + +#### Bug Fixes + +* fixes a bug that wasn't allowing help and version to be properly overridden ([8b2ceb83](https://github.com/kbknapp/clap-rs/commit/8b2ceb8368bcb70689fadf1c7f4b9549184926c1), closes [#922](https://github.com/kbknapp/clap-rs/issues/922)) + +#### Documentation + +* **clap_app!:** documents the `--("some-arg")` method for using args with hyphens inside them ([bc08ef3e](https://github.com/kbknapp/clap-rs/commit/bc08ef3e185393073d969d301989b6319c616c1f), closes [#919](https://github.com/kbknapp/clap-rs/issues/919)) + + + ### v2.22.2 (2017-03-30) diff --git a/README.md b/README.md index 876e9eb7..36b8677e 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,24 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) ## What's New -Here's the highlights for v2.22.2 +Here's the highlights for v2.23.0 + +* allows specifying a short help vs a long help (i.e. varying levels of detail depending on if -h or --help was used) +* **clap_app!:** adds support for arg names with hyphens similar to longs with hyphens +* fixes a bug that wasn't allowing help and version to be properly overridden + * This may break code that was relying on this bug! If you add a flag with a long of `help` manually *and* rely on the help message to be printed automatically your code could break. Please see the commit link in the full CHANGELOG.md +* `App::long_about` +* `App::long_version` +* `App::print_long_help` +* `App::write_long_help` +* `App::print_long_version` +* `App::write_long_version` +* `Arg::long_help` +* **clap_app!:** documents the `--("some-arg")` method for using args with hyphens inside them + +Here's the highlights for v2.21.0 to v2.22.2 * fixes the usage string regression when using help templates - -Here's the highlights for v2.21.0 to v2.22.1 - * fixes a big regression with custom usage strings * adds the ability to change the name of the App instance after creation * adds ability to hide the default value of an argument from the help string