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 :) + 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 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" diff --git a/src/app/parser.rs b/src/app/parser.rs index 144418bf6fc..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, } } } @@ -240,7 +243,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"); } @@ -448,11 +451,24 @@ 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()) } @@ -460,7 +476,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" { @@ -596,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| { @@ -823,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 @@ -842,6 +863,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() { @@ -864,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() { @@ -909,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)); @@ -1204,7 +1225,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") @@ -1307,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)); @@ -1369,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: {:?}", @@ -1396,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)); @@ -1789,12 +1815,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); } } } @@ -2152,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 3405dbd8534..756c759d916 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -4,35 +4,38 @@ 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 = 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, } } @@ -55,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, @@ -64,7 +68,9 @@ 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, GlobalVersion => GLOBAL_VERSION, HidePossibleValuesInHelp => NO_POS_VALUES, @@ -219,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`. /// @@ -309,6 +337,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. /// @@ -328,6 +368,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) /// @@ -670,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), @@ -679,7 +741,9 @@ 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), "globalversion" => Ok(AppSettings::GlobalVersion), "hidden" => Ok(AppSettings::Hidden), @@ -706,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(), @@ -724,8 +790,12 @@ 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!("dontcollapseargsinusage".parse::().unwrap(), + AppSettings::DontCollapseArgsInUsage); assert_eq!("dontdelimittrailingvalues".parse::().unwrap(), AppSettings::DontDelimitTrailingValues); assert_eq!("derivedisplayorder".parse::().unwrap(), 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 { diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 0077890433d..a495f84eac3 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") @@ -371,3 +388,65 @@ 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); +} + +#[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); +} + +#[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