From 85ea57835db7b73a19bcd725e82d99973303c7d5 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 21:23:19 -0500 Subject: [PATCH 01/11] chore: updates the contributing instructions --- .clog.toml | 4 ++- .github/CONTRIBUTING.md | 65 ++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/.clog.toml b/.clog.toml index 6c91769039a..5a62fa4badf 100644 --- a/.clog.toml +++ b/.clog.toml @@ -8,4 +8,6 @@ Performance = ["perf"] Improvements = ["impr", "im", "imp"] Documentation = ["docs"] Deprecations = ["depr"] -Examples = ["examples"] \ No newline at end of file +Examples = ["examples"] +"New Settings" = ["setting", "settings"] +"API Additions" = ["add", "api"] diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f8b8514fe58..e9c632842be 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,33 +2,6 @@ Contributions are always welcome! Please use the following guidelines when contributing to `clap` -1. Fork `clap` -2. Clone your fork (`git clone https://github.com/$YOUR_USERNAME/clap-rs && cd clap-rs`) -3. Create new branch (`git checkout -b new-branch`) -4. Make your changes, and commit (`git commit -am "your message"`) - * I use a [conventional](https://github.com/ajoslin/conventional-changelog/blob/a5505865ff3dd710cf757f50530e73ef0ca641da/conventions/angular.md) changelog format so I can update my changelog using [clog](https://github.com/clog-tool/clog-cli) - * In addition to the conventions defined above, I also use `imp`, `wip`, `examples`. - * Format your commit subject line using the following format: `TYPE(COMPONENT): MESSAGE` where `TYPE` is one of the following: - - `feat` - A new feature - - `imp` - An improvement to an existing feature - - `perf` - A performance improvement - - `docs` - Changes to documentation only - - `tests` - Changes to the testing framework or tests only - - `fix` - A bug fix - - `refactor` - Code functionality doesn't change, but underlying structure may - - `style` - Stylistic changes only, no functionality changes - - `wip` - A work in progress commit (Should typically be `git rebase`'ed away) - - `chore` - Catch all or things that have to do with the build system, etc - - `examples` - Changes to existing example, or a new example - * The `COMPONENT` is optional, and may be a single file, directory, or logical component. Can be omitted if commit applies globally -5. Run the tests (`cargo test --features "yaml unstable"`) -5. Run the lints (`cargo build --features lints`) (requires a nightly compiler) -6. `git rebase` into concise commits and remove `--fixup`s (`git rebase -i HEAD~NUM` where `NUM` is number of commits back) -7. Push your changes back to your fork (`git push origin $your-branch`) -8. Create a pull request! (You can also create the pull request first, and we'll merge when ready. This a good way to discuss proposed changes.) - -Another really great way to help is if you find an interesting, or helpful way in which to use `clap`. You can either add it to the [examples/](examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :) - ## Goals There are a few goals of `clap` that I'd like to maintain throughout contributions. @@ -43,3 +16,41 @@ There are a few goals of `clap` that I'd like to maintain throughout contributio * Try to be cognizant of memory usage - Once parsing is complete, the memory footprint of `clap` should be low since the main program is the star of the show * `panic!` on *developer* error, exit gracefully on *end-user* error + +### Commit Messages + +I use a [conventional](https://github.com/ajoslin/conventional-changelog/blob/a5505865ff3dd710cf757f50530e73ef0ca641da/conventions/angular.md) changelog format so I can update my changelog automatically using [clog](https://github.com/clog-tool/clog-cli) + + * Please format your commit subject line using the following format: `TYPE(COMPONENT): MESSAGE` where `TYPE` is one of the following: + - `api` - An addition to the API + - `setting` - A new `AppSettings` variant + - `feat` - A new feature of an existing API + - `imp` - An improvement to an existing feature/API + - `perf` - A performance improvement + - `docs` - Changes to documentation only + - `tests` - Changes to the testing framework or tests only + - `fix` - A bug fix + - `refactor` - Code functionality doesn't change, but underlying structure may + - `style` - Stylistic changes only, no functionality changes + - `wip` - A work in progress commit (Should typically be `git rebase`'ed away) + - `chore` - Catch all or things that have to do with the build system, etc + - `examples` - Changes to existing example, or a new example + * The `COMPONENT` is optional, and may be a single file, directory, or logical component. Parenthesis can be omitted if you are opting not to use the `COMPONENT`. + +### Tests and Documentation + +1. Create tests for your changes +2. **Ensure the tests are passing.** Run the tests (`cargo test --features "yaml unstable"`), alternatively `just run-tests` if you have `just` installed. +3. **Optional** Run the lints (`cargo build --features lints`) (requires a nightly compiler), alternatively `just lint` +4. Ensure your changes contain documentation if adding new APIs or features. + +### Preparing the PR + +1. `git rebase` into concise commits and remove `--fixup`s or `wip` commits (`git rebase -i HEAD~NUM` where `NUM` is number of commits back to start the rebase) +2. Push your changes back to your fork (`git push origin $your-branch`) +3. Create a pull request against `master`! (You can also create the pull request first, and we'll merge when ready. This a good way to discuss proposed changes.) + +### Other ways to contribute + +Another really great way to help is if you find an interesting, or helpful way in which to use `clap`. You can either add it to the [examples/](examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :) + From d34ec3e032d03e402d8e87af9b2942fe2819b2da Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 21:37:09 -0500 Subject: [PATCH 02/11] fix(Help Subcommand): fixes a bug where the help subcommand couldn't be overriden Closes #787 --- src/app/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 144418bf6fc..e198b6d5842 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -240,7 +240,7 @@ impl<'a, 'b> Parser<'a, 'b> debug!("Parser::add_subcommand: Is help..."); if subcmd.p.meta.name == "help" { sdebugln!("Yes"); - self.settings.set(AppSettings::NeedsSubcommandHelp); + self.settings.unset(AppSettings::NeedsSubcommandHelp); } else { sdebugln!("No"); } From a10fc859ee20159fbd9ff4337be59b76467a64f2 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 21:52:45 -0500 Subject: [PATCH 03/11] setting: adds a setting to disable building the help subcommand (`DisableHelpSubcommand`) Relates to #787 --- src/app/parser.rs | 4 ++- src/app/settings.rs | 84 +++++++++++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index e198b6d5842..f4f658de532 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -842,6 +842,7 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::get_matches_with: Positional counter...{}", pos_counter); debug!("Parser::get_matches_with: Checking for low index multiples..."); if self.is_set(AppSettings::LowIndexMultiplePositional) && + !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1) { sdebugln!("Found"); if let Some(na) = it.peek() { @@ -1204,7 +1205,8 @@ impl<'a, 'b> Parser<'a, 'b> self.long_list.push("version"); self.flags.insert(id, arg); } - if !self.subcommands.is_empty() && self.is_set(AppSettings::NeedsSubcommandHelp) { + if !self.subcommands.is_empty() && !self.is_set(AppSettings::DisableHelpSubcommand) && + self.is_set(AppSettings::NeedsSubcommandHelp) { debugln!("Parser::create_help_and_version: Building help"); self.subcommands .push(App::new("help") diff --git a/src/app/settings.rs b/src/app/settings.rs index 3405dbd8534..57cab25bff3 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -4,35 +4,36 @@ use std::str::FromStr; bitflags! { flags Flags: u32 { - const SC_NEGATE_REQS = 0b00000000000000000000000000001, - const SC_REQUIRED = 0b00000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000100, - const GLOBAL_VERSION = 0b00000000000000000000000001000, - const VERSIONLESS_SC = 0b00000000000000000000000010000, - const UNIFIED_HELP = 0b00000000000000000000000100000, - const WAIT_ON_ERROR = 0b00000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000010000000, - const NEEDS_LONG_HELP = 0b00000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b00000000000000000001000000000, - const NEEDS_SC_HELP = 0b00000000000000000010000000000, - const DISABLE_VERSION = 0b00000000000000000100000000000, - const HIDDEN = 0b00000000000000001000000000000, - const TRAILING_VARARG = 0b00000000000000010000000000000, - const NO_BIN_NAME = 0b00000000000000100000000000000, - const ALLOW_UNK_SC = 0b00000000000001000000000000000, - const UTF8_STRICT = 0b00000000000010000000000000000, - const UTF8_NONE = 0b00000000000100000000000000000, - const LEADING_HYPHEN = 0b00000000001000000000000000000, - const NO_POS_VALUES = 0b00000000010000000000000000000, - const NEXT_LINE_HELP = 0b00000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b00000001000000000000000000000, - const COLORED_HELP = 0b00000010000000000000000000000, - const COLOR_ALWAYS = 0b00000100000000000000000000000, - const COLOR_AUTO = 0b00001000000000000000000000000, - const COLOR_NEVER = 0b00010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b00100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b01000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b10000000000000000000000000000, + const SC_NEGATE_REQS = 0b000000000000000000000000000001, + const SC_REQUIRED = 0b000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b000000000000000000000000000100, + const GLOBAL_VERSION = 0b000000000000000000000000001000, + const VERSIONLESS_SC = 0b000000000000000000000000010000, + const UNIFIED_HELP = 0b000000000000000000000000100000, + const WAIT_ON_ERROR = 0b000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b000000000000000000001000000000, + const NEEDS_SC_HELP = 0b000000000000000000010000000000, + const DISABLE_VERSION = 0b000000000000000000100000000000, + const HIDDEN = 0b000000000000000001000000000000, + const TRAILING_VARARG = 0b000000000000000010000000000000, + const NO_BIN_NAME = 0b000000000000000100000000000000, + const ALLOW_UNK_SC = 0b000000000000001000000000000000, + const UTF8_STRICT = 0b000000000000010000000000000000, + const UTF8_NONE = 0b000000000000100000000000000000, + const LEADING_HYPHEN = 0b000000000001000000000000000000, + const NO_POS_VALUES = 0b000000000010000000000000000000, + const NEXT_LINE_HELP = 0b000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b000000001000000000000000000000, + const COLORED_HELP = 0b000000010000000000000000000000, + const COLOR_ALWAYS = 0b000000100000000000000000000000, + const COLOR_AUTO = 0b000001000000000000000000000000, + const COLOR_NEVER = 0b000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b010000000000000000000000000000, + const DISABLE_HELP_SC = 0b10000000000000000000000000000, } } @@ -65,6 +66,7 @@ impl AppFlags { ColorNever => COLOR_NEVER, DontDelimitTrailingValues => DONT_DELIM_TRAIL, DeriveDisplayOrder => DERIVE_DISP_ORDER, + DisableHelpSubcommand => DISABLE_HELP_SC, DisableVersion => DISABLE_VERSION, GlobalVersion => GLOBAL_VERSION, HidePossibleValuesInHelp => NO_POS_VALUES, @@ -328,6 +330,27 @@ pub enum AppSettings { /// [`Arg::use_delimiter(false)`]: ./struct.Arg.html#method.use_delimiter DontDelimitTrailingValues, + /// Disables the `help` subcommand + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings, ErrorKind, SubCommand}; + /// let res = App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::DisableHelpSubcommand) + /// // Normally, creating a subcommand causes a `help` subcommand to automaticaly + /// // be generated as well + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", "help" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + DisableHelpSubcommand, + /// Disables `-V` and `--version` [`App`] without affecting any of the [`SubCommand`]s /// (Defaults to `false`; application *does* have a version flag) /// @@ -680,6 +703,7 @@ impl FromStr for AppSettings { "coloredhelp" => Ok(AppSettings::ColoredHelp), "derivedisplayorder" => Ok(AppSettings::DeriveDisplayOrder), "dontdelimittrailingvalues" => Ok(AppSettings::DontDelimitTrailingValues), + "disablehelpsubcommand" => Ok(AppSettings::DisableHelpSubcommand), "disableversion" => Ok(AppSettings::DisableVersion), "globalversion" => Ok(AppSettings::GlobalVersion), "hidden" => Ok(AppSettings::Hidden), @@ -724,6 +748,8 @@ mod test { AppSettings::ColorAlways); assert_eq!("colornever".parse::().unwrap(), AppSettings::ColorNever); + assert_eq!("disablehelpsubcommand".parse::().unwrap(), + AppSettings::DisableHelpSubcommand); assert_eq!("disableversion".parse::().unwrap(), AppSettings::DisableVersion); assert_eq!("dontdelimittrailingvalues".parse::().unwrap(), From 01caf84b871667d1540a42ffa955671bbd9607bc Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 21:53:58 -0500 Subject: [PATCH 04/11] tests(DisableHelpSubcommand): adds tests for AppSettings::DisableHelpSubcommand --- tests/app_settings.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 0077890433d..40f92f2007d 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -371,3 +371,14 @@ fn test_unset_settings() { assert!(!m.p.is_set(AppSettings::AllowInvalidUtf8)); assert!(!m.p.is_set(AppSettings::ColorAuto)); } + +#[test] +fn disable_help_subcommand() { + let result = App::new("disablehelp") + .setting(AppSettings::DisableHelpSubcommand) + .subcommand(SubCommand::with_name("sub1")) + .get_matches_from_safe(vec!["", "help"]); + assert!(result.is_err()); + let err = result.err().unwrap(); + assert_eq!(err.kind, ErrorKind::UnknownArgument); +} \ No newline at end of file From fabe9267ee2264c1957cad825a8df0b9d7007fcc Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 22:03:36 -0500 Subject: [PATCH 05/11] chore: updates deps and pins pre-1.0 crates --- Cargo.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 702ff3a5841..13926d94ffa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,16 +15,16 @@ A simple to use, efficient, and full featured Command Line Argument Parser """ [dependencies] -bitflags = "~0.7" -vec_map = "~0.6" -unicode-width = "~0.1.3" -unicode-segmentation = "~0.1.2" -strsim = { version = "~0.5.1", optional = true } -ansi_term = { version = "~0.9.0", optional = true } -term_size = { version = "~0.2.0", optional = true } -libc = { version = "~0.2.9", optional = true } -yaml-rust = { version = "~0.3.2", optional = true } -clippy = { version = "~0.0.100", optional = true } +bitflags = "=0.7.0" +vec_map = "=0.6.0" +unicode-width = "=0.1.4" +unicode-segmentation = "1.0.1" +strsim = { version = "=0.6.0", optional = true } +ansi_term = { version = "=0.9.0", optional = true } +term_size = { version = "=0.2.0", optional = true } +libc = { version = "=0.2.18", optional = true } +yaml-rust = { version = "=0.3.5", optional = true } +clippy = { version = "~0.0.104", optional = true } [dev-dependencies] regex = "~0.1.80" From c20701b74a79eaf19b3d6e9983bbd0c7df3ab36c Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 22:08:28 -0500 Subject: [PATCH 06/11] chore: clippy run --- src/app/parser.rs | 10 ++++------ src/args/arg.rs | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index f4f658de532..10b57462036 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -460,7 +460,7 @@ impl<'a, 'b> Parser<'a, 'b> // Determines if we need the `[FLAGS]` tag in the usage string pub fn needs_flags_tag(&self) -> bool { debugln!("Parser::needs_flags_tag;"); - 'outer: for f in self.flags.iter() { + 'outer: for f in &self.flags { debugln!("Parser::needs_flags_tag:iter: f={};", f.b.name); if let Some(l) = f.s.long { if l == "help" || l == "version" { @@ -1791,12 +1791,10 @@ impl<'a, 'b> Parser<'a, 'b> // Validate the conditionally required args for &(a, v, r) in &self.r_ifs { - if let Some(ref ma) = matcher.get(a) { + if let Some(ma) = matcher.get(a) { for val in ma.vals.values() { - if v == val { - if matcher.get(r).is_none() { - return self.missing_required_error(matcher); - } + if v == val && matcher.get(r).is_none() { + return self.missing_required_error(matcher); } } } diff --git a/src/args/arg.rs b/src/args/arg.rs index 38a63a7d879..66916cb9303 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -2966,6 +2966,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value /// [`Arg::default_value`]: ./struct.Arg.html#method.default_value + #[cfg_attr(feature = "lints", allow(explicit_counter_loop))] pub fn default_value_ifs(mut self, ifs: &[(&'a str, Option<&'b str>, &'b str)]) -> Self { self.setb(ArgSettings::TakesValue); if let Some(ref mut vm) = self.default_vals_ifs { From 945acffb5e7a0f4ea1d42c8487802eb65486c0de Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 22:10:14 -0500 Subject: [PATCH 07/11] chore: bumps pinned nightly for clippy CI builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 97d2e740422..86f912cd319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: rust cache: cargo rust: - nightly - - nightly-2016-11-20 + - nightly-2016-12-29 - beta - stable - 1.11.0 From c2978afc61fb46d5263ab3b2d87ecde1c9ce1553 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 22:38:42 -0500 Subject: [PATCH 08/11] setting: adds a new setting to disable the collapsing of positional args into `[ARGS]` in the usage string (`DontCollapseArgsInUsage`) Closes #769 --- src/app/parser.rs | 16 ++++++++-- src/app/settings.rs | 77 +++++++++++++++++++++++++++------------------ 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 10b57462036..743314d6533 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -448,11 +448,23 @@ impl<'a, 'b> Parser<'a, 'b> count += 1; debugln!("Parser::get_args_tag:iter: {} Args not required", count); } - if count > 1 || self.positionals.len() > 1 { + if !self.is_set(AppSettings::DontCollapseArgsInUsage) && + (count > 1 || self.positionals.len() > 1) { return None; // [ARGS] } else if count == 1 { - let p = self.positionals.values().next().expect(INTERNAL_ERROR_MSG); + let p = self.positionals + .values() + .filter(|p| !p.is_set(ArgSettings::Required)) + .next() + .expect(INTERNAL_ERROR_MSG); return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())); + } else if self.is_set(AppSettings::DontCollapseArgsInUsage) && !self.positionals.is_empty() { + return Some(self.positionals + .values() + .filter(|p| !p.is_set(ArgSettings::Required)) + .map(|p| format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())) + .collect::>() + .join("")); } Some("".into()) } diff --git a/src/app/settings.rs b/src/app/settings.rs index 57cab25bff3..2a3bd071bd3 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -4,36 +4,37 @@ use std::str::FromStr; bitflags! { flags Flags: u32 { - const SC_NEGATE_REQS = 0b000000000000000000000000000001, - const SC_REQUIRED = 0b000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b000000000000000000000000000100, - const GLOBAL_VERSION = 0b000000000000000000000000001000, - const VERSIONLESS_SC = 0b000000000000000000000000010000, - const UNIFIED_HELP = 0b000000000000000000000000100000, - const WAIT_ON_ERROR = 0b000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b000000000000000000001000000000, - const NEEDS_SC_HELP = 0b000000000000000000010000000000, - const DISABLE_VERSION = 0b000000000000000000100000000000, - const HIDDEN = 0b000000000000000001000000000000, - const TRAILING_VARARG = 0b000000000000000010000000000000, - const NO_BIN_NAME = 0b000000000000000100000000000000, - const ALLOW_UNK_SC = 0b000000000000001000000000000000, - const UTF8_STRICT = 0b000000000000010000000000000000, - const UTF8_NONE = 0b000000000000100000000000000000, - const LEADING_HYPHEN = 0b000000000001000000000000000000, - const NO_POS_VALUES = 0b000000000010000000000000000000, - const NEXT_LINE_HELP = 0b000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b000000001000000000000000000000, - const COLORED_HELP = 0b000000010000000000000000000000, - const COLOR_ALWAYS = 0b000000100000000000000000000000, - const COLOR_AUTO = 0b000001000000000000000000000000, - const COLOR_NEVER = 0b000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b010000000000000000000000000000, - const DISABLE_HELP_SC = 0b10000000000000000000000000000, + const SC_NEGATE_REQS = 0b0000000000000000000000000000001, + const SC_REQUIRED = 0b0000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b0000000000000000000000000000100, + const GLOBAL_VERSION = 0b0000000000000000000000000001000, + const VERSIONLESS_SC = 0b0000000000000000000000000010000, + const UNIFIED_HELP = 0b0000000000000000000000000100000, + const WAIT_ON_ERROR = 0b0000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b0000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b0000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b0000000000000000000001000000000, + const NEEDS_SC_HELP = 0b0000000000000000000010000000000, + const DISABLE_VERSION = 0b0000000000000000000100000000000, + const HIDDEN = 0b0000000000000000001000000000000, + const TRAILING_VARARG = 0b0000000000000000010000000000000, + const NO_BIN_NAME = 0b0000000000000000100000000000000, + const ALLOW_UNK_SC = 0b0000000000000001000000000000000, + const UTF8_STRICT = 0b0000000000000010000000000000000, + const UTF8_NONE = 0b0000000000000100000000000000000, + const LEADING_HYPHEN = 0b0000000000001000000000000000000, + const NO_POS_VALUES = 0b0000000000010000000000000000000, + const NEXT_LINE_HELP = 0b0000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b0000000001000000000000000000000, + const COLORED_HELP = 0b0000000010000000000000000000000, + const COLOR_ALWAYS = 0b0000000100000000000000000000000, + const COLOR_AUTO = 0b0000001000000000000000000000000, + const COLOR_NEVER = 0b0000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b0000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b0001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b0010000000000000000000000000000, + const DISABLE_HELP_SC = 0b0100000000000000000000000000000, + const DONT_COLLAPSE_ARGS = 0b1000000000000000000000000000000, } } @@ -65,6 +66,7 @@ impl AppFlags { ColorAuto => COLOR_AUTO, ColorNever => COLOR_NEVER, DontDelimitTrailingValues => DONT_DELIM_TRAIL, + DontCollapseArgsInUsage => DONT_COLLAPSE_ARGS, DeriveDisplayOrder => DERIVE_DISP_ORDER, DisableHelpSubcommand => DISABLE_HELP_SC, DisableVersion => DISABLE_VERSION, @@ -311,6 +313,18 @@ pub enum AppSettings { /// ``` ColorNever, + /// Disables the automatic collapsing of positional args into `[ARGS]` inside the usage string + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::DontCollapseArgsInUsage) + /// .get_matches(); + /// ``` + DontCollapseArgsInUsage, + /// Disables the automatic delimiting of values when `--` or [`AppSettings::TrailingVarArg`] /// was used. /// @@ -702,6 +716,7 @@ impl FromStr for AppSettings { "colornever" => Ok(AppSettings::ColorNever), "coloredhelp" => Ok(AppSettings::ColoredHelp), "derivedisplayorder" => Ok(AppSettings::DeriveDisplayOrder), + "dontcollapseargsinusage" => Ok(AppSettings::DontCollapseArgsInUsage), "dontdelimittrailingvalues" => Ok(AppSettings::DontDelimitTrailingValues), "disablehelpsubcommand" => Ok(AppSettings::DisableHelpSubcommand), "disableversion" => Ok(AppSettings::DisableVersion), @@ -752,6 +767,8 @@ mod test { AppSettings::DisableHelpSubcommand); assert_eq!("disableversion".parse::().unwrap(), AppSettings::DisableVersion); + assert_eq!("dontcollapseargsinusage".parse::().unwrap(), + AppSettings::DontCollapseArgsInUsage); assert_eq!("dontdelimittrailingvalues".parse::().unwrap(), AppSettings::DontDelimitTrailingValues); assert_eq!("derivedisplayorder".parse::().unwrap(), From 25cbca4e41a9ae41f9f11fd7e2286bc7fd1ce51c Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 22:40:28 -0500 Subject: [PATCH 09/11] tests: adds tests for --- tests/app_settings.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 40f92f2007d..ef4e79721b9 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -1,7 +1,24 @@ extern crate clap; +extern crate regex; use clap::{App, Arg, SubCommand, AppSettings, ErrorKind}; +include!("../clap-test.rs"); + +static DONT_COLLAPSE_ARGS: &'static str = "clap-test v1.4.8 + +USAGE: + clap-test [arg1] [arg2] [arg3] + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + some + some + some"; + #[test] fn sub_command_negate_required() { App::new("sub_command_negate") @@ -381,4 +398,17 @@ fn disable_help_subcommand() { assert!(result.is_err()); let err = result.err().unwrap(); assert_eq!(err.kind, ErrorKind::UnknownArgument); +} + +#[test] +fn dont_collapse_args() { + let app = App::new("clap-test") + .version("v1.4.8") + .setting(AppSettings::DontCollapseArgsInUsage) + .args(&[ + Arg::with_name("arg1").help("some"), + Arg::with_name("arg2").help("some"), + Arg::with_name("arg3").help("some"), + ]); + test::check_help(app, DONT_COLLAPSE_ARGS); } \ No newline at end of file From 5e2af8c96adb5ab75fa2d1536237ebcb41869494 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 23:19:08 -0500 Subject: [PATCH 10/11] setting: adds a setting to disable args being allowed between subcommands (`ArgsNegateSubcommands`) Specifies that use of a valid [argument] negates [subcomands] being used after. By default `clap` allows arguments between subcommands such as ` [cmd_args] [cmd2_args] [cmd3_args]`. This setting disables that functionality and says that arguments can only follow the *final* subcommand. For instance using this setting makes only the following invocations possible: * ` [cmd3_args]` * ` [cmd2_args]` * ` [cmd_args]` Closes #793 --- src/app/parser.rs | 31 +++++++++++----- src/app/settings.rs | 89 +++++++++++++++++++++++++++++---------------- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 743314d6533..47e85564a36 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -59,9 +59,11 @@ pub struct Parser<'a, 'b> settings: AppFlags, pub g_settings: Vec, pub meta: AppMeta<'b>, - trailing_vals: bool, pub id: usize, + trailing_vals: bool, valid_neg_num: bool, + // have we found a valid arg yet + valid_arg: bool, } impl<'a, 'b> Default for Parser<'a, 'b> { @@ -87,6 +89,7 @@ impl<'a, 'b> Default for Parser<'a, 'b> { trailing_vals: false, id: 0, valid_neg_num: false, + valid_arg: false, } } } @@ -458,7 +461,8 @@ impl<'a, 'b> Parser<'a, 'b> .next() .expect(INTERNAL_ERROR_MSG); return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())); - } else if self.is_set(AppSettings::DontCollapseArgsInUsage) && !self.positionals.is_empty() { + } else if self.is_set(AppSettings::DontCollapseArgsInUsage) && + !self.positionals.is_empty() { return Some(self.positionals .values() .filter(|p| !p.is_set(ArgSettings::Required)) @@ -608,6 +612,9 @@ impl<'a, 'b> Parser<'a, 'b> #[inline] fn possible_subcommand(&self, arg_os: &OsStr) -> bool { debugln!("Parser::possible_subcommand;"); + if self.is_set(AppSettings::ArgsNegateSubcommands) && self.valid_arg { + return false; + } self.subcommands .iter() .any(|s| { @@ -835,6 +842,8 @@ impl<'a, 'b> Parser<'a, 'b> } subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned()); break; + } else if self.is_set(AppSettings::ArgsNegateSubcommands) { + (); } else if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), self.subcommands @@ -877,6 +886,7 @@ impl<'a, 'b> Parser<'a, 'b> } if let Some(p) = self.positionals.get(pos_counter) { parse_positional!(self, p, arg_os, pos_counter, matcher); + self.valid_arg = true; } else if self.settings.is_set(AppSettings::AllowExternalSubcommands) { // Get external subcommand name let sc_name = match arg_os.to_str() { @@ -922,17 +932,15 @@ impl<'a, 'b> Parser<'a, 'b> let sc_name = &*self.subcommands .iter() .filter(|sc| sc.p.meta.aliases.is_some()) - .filter_map(|sc| if sc.p + .filter(|sc| sc.p .meta .aliases .as_ref() - .unwrap() + .expect(INTERNAL_ERROR_MSG) .iter() - .any(|&(a, _)| &a == &&*pos_sc_name) { - Some(sc.p.meta.name.clone()) - } else { - None - }) + .any(|&(a, _)| &a == &&*pos_sc_name) + ) + .map(|sc| sc.p.meta.name.clone()) .next() .expect(INTERNAL_ERROR_MSG); try!(self.parse_subcommand(sc_name, matcher, it)); @@ -1321,12 +1329,14 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(opt) = find_by_long!(self, &arg, opts) { debugln!("Parser::parse_long_arg: Found valid opt '{}'", opt.to_string()); + self.valid_arg = true; let ret = try!(self.parse_opt(val, opt, matcher)); arg_post_processing!(self, opt, matcher); return Ok(ret); } else if let Some(flag) = find_by_long!(self, &arg, flags) { debugln!("Parser::parse_long_arg: Found valid flag '{}'", flag.to_string()); + self.valid_arg = true; // Only flags could be help or version, and we need to check the raw long // so this is the first point to check try!(self.check_for_help_and_version_str(arg)); @@ -1383,6 +1393,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .find(|&o| o.s.short.is_some() && o.s.short.unwrap() == c) { debugln!("Parser::parse_short_arg:iter: Found valid short opt -{} in '{}'", c, arg); + self.valid_arg = true; // Check for trailing concatenated value let p: Vec<_> = arg.splitn(2, c).collect(); debugln!("Parser::parse_short_arg:iter: arg: {:?}, arg_os: {:?}, full_arg: {:?}", @@ -1410,6 +1421,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .find(|&f| f.s.short.is_some() && f.s.short.unwrap() == c) { debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}", c); + self.valid_arg = true; // Only flags can be help or version try!(self.check_for_help_and_version_char(c)); try!(self.parse_flag(flag, matcher)); @@ -2164,6 +2176,7 @@ impl<'a, 'b> Clone for Parser<'a, 'b> id: self.id, valid_neg_num: self.valid_neg_num, r_ifs: self.r_ifs.clone(), + valid_arg: self.valid_arg, } } } diff --git a/src/app/settings.rs b/src/app/settings.rs index 2a3bd071bd3..756c759d916 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -4,37 +4,38 @@ use std::str::FromStr; bitflags! { flags Flags: u32 { - const SC_NEGATE_REQS = 0b0000000000000000000000000000001, - const SC_REQUIRED = 0b0000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b0000000000000000000000000000100, - const GLOBAL_VERSION = 0b0000000000000000000000000001000, - const VERSIONLESS_SC = 0b0000000000000000000000000010000, - const UNIFIED_HELP = 0b0000000000000000000000000100000, - const WAIT_ON_ERROR = 0b0000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b0000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b0000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b0000000000000000000001000000000, - const NEEDS_SC_HELP = 0b0000000000000000000010000000000, - const DISABLE_VERSION = 0b0000000000000000000100000000000, - const HIDDEN = 0b0000000000000000001000000000000, - const TRAILING_VARARG = 0b0000000000000000010000000000000, - const NO_BIN_NAME = 0b0000000000000000100000000000000, - const ALLOW_UNK_SC = 0b0000000000000001000000000000000, - const UTF8_STRICT = 0b0000000000000010000000000000000, - const UTF8_NONE = 0b0000000000000100000000000000000, - const LEADING_HYPHEN = 0b0000000000001000000000000000000, - const NO_POS_VALUES = 0b0000000000010000000000000000000, - const NEXT_LINE_HELP = 0b0000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b0000000001000000000000000000000, - const COLORED_HELP = 0b0000000010000000000000000000000, - const COLOR_ALWAYS = 0b0000000100000000000000000000000, - const COLOR_AUTO = 0b0000001000000000000000000000000, - const COLOR_NEVER = 0b0000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b0000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b0001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b0010000000000000000000000000000, - const DISABLE_HELP_SC = 0b0100000000000000000000000000000, - const DONT_COLLAPSE_ARGS = 0b1000000000000000000000000000000, + const SC_NEGATE_REQS = 0b00000000000000000000000000000001, + const SC_REQUIRED = 0b00000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000100, + const GLOBAL_VERSION = 0b00000000000000000000000000001000, + const VERSIONLESS_SC = 0b00000000000000000000000000010000, + const UNIFIED_HELP = 0b00000000000000000000000000100000, + const WAIT_ON_ERROR = 0b00000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b00000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b00000000000000000000001000000000, + const NEEDS_SC_HELP = 0b00000000000000000000010000000000, + const DISABLE_VERSION = 0b00000000000000000000100000000000, + const HIDDEN = 0b00000000000000000001000000000000, + const TRAILING_VARARG = 0b00000000000000000010000000000000, + const NO_BIN_NAME = 0b00000000000000000100000000000000, + const ALLOW_UNK_SC = 0b00000000000000001000000000000000, + const UTF8_STRICT = 0b00000000000000010000000000000000, + const UTF8_NONE = 0b00000000000000100000000000000000, + const LEADING_HYPHEN = 0b00000000000001000000000000000000, + const NO_POS_VALUES = 0b00000000000010000000000000000000, + const NEXT_LINE_HELP = 0b00000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b00000000001000000000000000000000, + const COLORED_HELP = 0b00000000010000000000000000000000, + const COLOR_ALWAYS = 0b00000000100000000000000000000000, + const COLOR_AUTO = 0b00000001000000000000000000000000, + const COLOR_NEVER = 0b00000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b00000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b00001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b00010000000000000000000000000000, + const DISABLE_HELP_SC = 0b00100000000000000000000000000000, + const DONT_COLLAPSE_ARGS = 0b01000000000000000000000000000000, + const ARGS_NEGATE_SCS = 0b10000000000000000000000000000000, } } @@ -57,6 +58,7 @@ impl AppFlags { impl_settings! { AppSettings, ArgRequiredElseHelp => A_REQUIRED_ELSE_HELP, + ArgsNegateSubcommands => ARGS_NEGATE_SCS, AllowExternalSubcommands => ALLOW_UNK_SC, AllowInvalidUtf8 => UTF8_NONE, AllowLeadingHyphen => LEADING_HYPHEN, @@ -223,6 +225,28 @@ pub enum AppSettings { /// [`ArgMatches`]: ./struct.ArgMatches.html AllowExternalSubcommands, + /// Specifies that use of a valid [argument] negates [subcomands] being used after. By default + /// `clap` allows arguments between subcommands such as + /// ` [cmd_args] [cmd2_args] [cmd3_args]`. This setting disables that + /// functionality and says that arguments can only follow the *final* subcommand. For instance + /// using this setting makes only the following invocations possible: + /// + /// * ` [cmd3_args]` + /// * ` [cmd2_args]` + /// * ` [cmd_args]` + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ArgsNegateSubcommands) + /// # ; + /// ``` + /// [subcommands]: ./struct.SubCommand.html + /// [argument]: ./struct.Arg.html + ArgsNegateSubcommands, + /// Specifies that the help text should be displayed (and then exit gracefully), /// if no arguments are present at runtime (i.e. an empty run such as, `$ myprog`. /// @@ -707,6 +731,7 @@ impl FromStr for AppSettings { fn from_str(s: &str) -> Result::Err> { match &*s.to_ascii_lowercase() { "argrequiredelsehelp" => Ok(AppSettings::ArgRequiredElseHelp), + "argsnegatesubcommands" => Ok(AppSettings::ArgsNegateSubcommands), "allowinvalidutf8" => Ok(AppSettings::AllowInvalidUtf8), "allowleadinghyphen" => Ok(AppSettings::AllowLeadingHyphen), "allowexternalsubcommands" => Ok(AppSettings::AllowExternalSubcommands), @@ -745,6 +770,8 @@ mod test { #[test] fn app_settings_fromstr() { + assert_eq!("argsnegatesubcommands".parse::().unwrap(), + AppSettings::ArgsNegateSubcommands); assert_eq!("argrequiredelsehelp".parse::().unwrap(), AppSettings::ArgRequiredElseHelp); assert_eq!("allowexternalsubcommands".parse::().unwrap(), From 12026f63d960f0d3a0bf55759f259d522060d287 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 23:22:09 -0500 Subject: [PATCH 11/11] tests: adds tests for --- tests/app_settings.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/app_settings.rs b/tests/app_settings.rs index ef4e79721b9..a495f84eac3 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -411,4 +411,42 @@ fn dont_collapse_args() { Arg::with_name("arg3").help("some"), ]); test::check_help(app, DONT_COLLAPSE_ARGS); +} + +#[test] +fn args_negate_subcommands_one_level() { + let res = App::new("disablehelp") + .setting(AppSettings::ArgsNegateSubcommands) + .setting(AppSettings::SubcommandsNegateReqs) + .arg_from_usage(" 'some arg'") + .arg_from_usage(" 'some arg'") + .subcommand(SubCommand::with_name("sub1") + .subcommand(SubCommand::with_name("sub2") + .subcommand(SubCommand::with_name("sub3")) + ) + ) + .get_matches_from_safe(vec!["", "pickles", "sub1"]); + assert!(res.is_ok(), "error: {:?}", res.unwrap_err().kind); + let m = res.unwrap(); + assert_eq!(m.value_of("arg2"), Some("sub1")); +} + +#[test] +fn args_negate_subcommands_two_levels() { + let res = App::new("disablehelp") + .global_setting(AppSettings::ArgsNegateSubcommands) + .global_setting(AppSettings::SubcommandsNegateReqs) + .arg_from_usage(" 'some arg'") + .arg_from_usage(" 'some arg'") + .subcommand(SubCommand::with_name("sub1") + .arg_from_usage(" 'some'") + .arg_from_usage(" 'some'") + .subcommand(SubCommand::with_name("sub2") + .subcommand(SubCommand::with_name("sub3")) + ) + ) + .get_matches_from_safe(vec!["", "sub1", "arg", "sub2"]); + assert!(res.is_ok(), "error: {:?}", res.unwrap_err().kind); + let m = res.unwrap(); + assert_eq!(m.subcommand_matches("sub1").unwrap().value_of("arg2"), Some("sub2")); } \ No newline at end of file