diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea38c9e..34b2d596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,57 @@ + + +#### New Settings + +* **ArgsNegateSubcommands:** disables args being allowed between subcommands ([5e2af8c9](https://github.com/kbknapp/clap-rs/commit/5e2af8c96adb5ab75fa2d1536237ebcb41869494), closes [#793](https://github.com/kbknapp/clap-rs/issues/793)) +* **DontCollapseArgsInUsage:** disables the collapsing of positional args into `[ARGS]` in the usage string ([c2978afc](https://github.com/kbknapp/clap-rs/commit/c2978afc61fb46d5263ab3b2d87ecde1c9ce1553), closes [#769](https://github.com/kbknapp/clap-rs/issues/769)) +* **DisableHelpSubcommand:** disables building the `help` subcommand ([a10fc859](https://github.com/kbknapp/clap-rs/commit/a10fc859ee20159fbd9ff4337be59b76467a64f2)) +* **AllowMissingPositional:** allows one to implement `$ prog [optional] ` style CLIs where the second postional argument is required, but the first is optional ([1110fdc7](https://github.com/kbknapp/clap-rs/commit/1110fdc7a345c108820dc45783a9bf893fa4c214), closes [#636](https://github.com/kbknapp/clap-rs/issues/636)) +* **PropagateGlobalValuesDown:** automatically propagats global arg's values down through *used* subcommands ([985536c8](https://github.com/kbknapp/clap-rs/commit/985536c8ebcc09af98aac835f42a8072ad58c262), closes [#694](https://github.com/kbknapp/clap-rs/issues/694)) + +#### API Additions + +##### Arg + +* **Arg::value_terminator:** adds the ability to terminate multiple values with a given string or char ([be64ce0c](https://github.com/kbknapp/clap-rs/commit/be64ce0c373efc106384baca3f487ea99fe7b8cf), closes [#782](https://github.com/kbknapp/clap-rs/issues/782)) +* **Arg::default_value_if[s]:** adds new methods for *conditional* default values (such as a particular value from another argument was used) ([eb4010e7](https://github.com/kbknapp/clap-rs/commit/eb4010e7b21724447ef837db11ac441915728f22)) +* **Arg::requires_if[s]:** adds the ability to *conditionally* require additional args (such as if a particular value was used) ([198449d6](https://github.com/kbknapp/clap-rs/commit/198449d64393c265f0bc327aaeac23ec4bb97226)) +* **Arg::required_if[s]:** adds the ability for an arg to be *conditionally* required (i.e. "arg X is only required if arg Y was used with value Z") ([ee9cfddf](https://github.com/kbknapp/clap-rs/commit/ee9cfddf345a6b5ae2af42ba72aa5c89e2ca7f59)) +* **Arg::validator_os:** adds ability to validate values which may contain invalid UTF-8 ([47232498](https://github.com/kbknapp/clap-rs/commit/47232498a813db4f3366ccd3e9faf0bff56433a4)) + +##### Macros + +* **crate_description!:** Uses the `Cargo.toml` description field to fill in the `App::about` method at compile time ([4d9a82db](https://github.com/kbknapp/clap-rs/commit/4d9a82db8e875e9b64a9c2a5c6e22c25afc1279d), closes [#778](https://github.com/kbknapp/clap-rs/issues/778)) +* **crate_name!:** Uses the `Cargo.toml` name field to fill in the `App::new` method at compile time ([4d9a82db](https://github.com/kbknapp/clap-rs/commit/4d9a82db8e875e9b64a9c2a5c6e22c25afc1279d), closes [#778](https://github.com/kbknapp/clap-rs/issues/778)) +* **app_from_crate!:** Combines `crate_version!`, `crate_name!`, `crate_description!`, and `crate_authors!` into a single macro call to build a default `App` instance from the `Cargo.toml` fields ([4d9a82db](https://github.com/kbknapp/clap-rs/commit/4d9a82db8e875e9b64a9c2a5c6e22c25afc1279d), closes [#778](https://github.com/kbknapp/clap-rs/issues/778)) + + +#### Features + +* **no_cargo:** adds a `no_cargo` feature to disable Cargo-env-var-dependent macros for those *not* using `cargo` to build their crates (#786) ([6fdd2f9d](https://github.com/kbknapp/clap-rs/commit/6fdd2f9d693aaf1118fc61bd362273950703f43d)) + +#### Bug Fixes + +* **Options:** fixes a critical bug where options weren't forced to have a value ([5a5f2b1e](https://github.com/kbknapp/clap-rs/commit/5a5f2b1e9f598a0d0280ef3e98abbbba2bc41132), closes [#665](https://github.com/kbknapp/clap-rs/issues/665)) +* fixes a bug where calling the help of a subcommand wasn't ignoring required args of parent commands ([d3d34a2b](https://github.com/kbknapp/clap-rs/commit/d3d34a2b51ef31004055b0ab574f766d801c3adf), closes [#789](https://github.com/kbknapp/clap-rs/issues/789)) +* **Help Subcommand:** fixes a bug where the help subcommand couldn't be overriden ([d34ec3e0](https://github.com/kbknapp/clap-rs/commit/d34ec3e032d03e402d8e87af9b2942fe2819b2da), closes [#787](https://github.com/kbknapp/clap-rs/issues/787)) +* **Low Index Multiples:** fixes a bug which caused combinations of LowIndexMultiples and `Arg::allow_hyphen_values` to fail parsing ([26c670ca](https://github.com/kbknapp/clap-rs/commit/26c670ca16d2c80dc26d5c1ce83380ace6357318)) + +#### Improvements + +* **Default Values:** improves the error message when default values are involved ([1f33de54](https://github.com/kbknapp/clap-rs/commit/1f33de545036e7fd2f80faba251fca009bd519b8), closes [#774](https://github.com/kbknapp/clap-rs/issues/774)) +* **YAML:** adds conditional requirements and conditional default values to YAML ([9a4df327](https://github.com/kbknapp/clap-rs/commit/9a4df327893486adb5558ffefba790c634ccdc6e), closes [#764](https://github.com/kbknapp/clap-rs/issues/764)) +* Support `--("some-arg-name")` syntax for defining long arg names when using `clap_app!` macro ([f41ec962](https://github.com/kbknapp/clap-rs/commit/f41ec962c243a5ffff8b1be1ae2ad63970d3d1d4)) +* Support `("some app name")` syntax for defining app names when using `clap_app!` macro ([9895b671](https://github.com/kbknapp/clap-rs/commit/9895b671cff784f35cf56abcd8270f7c2ba09699), closes [#759](https://github.com/kbknapp/clap-rs/issues/759)) +* **Help Wrapping:** long app names (with spaces), authors, and descriptions are now wrapped appropriately ([ad4691b7](https://github.com/kbknapp/clap-rs/commit/ad4691b71a63e951ace346318238d8834e04ad8a), closes [#777](https://github.com/kbknapp/clap-rs/issues/777)) + + +#### Documentation + +* **Conditional Default Values:** fixes the failing doc tests of Arg::default_value_ifs ([4ef09101](https://github.com/kbknapp/clap-rs/commit/4ef091019c083b4db1a0c13f1c1e95ac363259f2)) +* **Conditional Requirements:** adds docs for Arg::requires_ifs ([7f296e29](https://github.com/kbknapp/clap-rs/commit/7f296e29db7d9036e76e5dbcc9c8b20dfe7b25bd)) +* **README.md:** fix some typos ([f22c21b4](https://github.com/kbknapp/clap-rs/commit/f22c21b422d5b287d1a1ac183a379ee02eebf54f)) +* **src/app/mod.rs:** fix some typos ([5c9b0d47](https://github.com/kbknapp/clap-rs/commit/5c9b0d47ca78dea285c5b9dec79063d24c3e451a)) + ### v2.19.3 (2016-12-28) diff --git a/README.md b/README.md index a80c75a0..21567756 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,37 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) ## What's New -Here's the highlights for v2.19.3 +Here's the highlights for v2.20.0 + +* **ArgsNegateSubcommands:** disables args being allowed between subcommands +* **DontCollapseArgsInUsage:** disables the collapsing of positional args into `[ARGS]` in the usage string +* **DisableHelpSubcommand:** disables building the `help` subcommand +* **AllowMissingPositional:** allows one to implement `$ prog [optional] ` style CLIs where the second postional argument is required, but the first is optional +* **PropagateGlobalValuesDown:** automatically propagats global arg's values down through *used* subcommands +* **Arg::value_terminator:** adds the ability to terminate multiple values with a given string or char +* **Arg::default_value_if[s]:** adds new methods for *conditional* default values (such as a particular value from another argument was used) +* **Arg::requires_if[s]:** adds the ability to *conditionally* require additional args (such as if a particular value was used) +* **Arg::required_if[s]:** adds the ability for an arg to be *conditionally* required (i.e. "arg X is only required if arg Y was used with value Z") +* **Arg::validator_os:** adds ability to validate values which may contain invalid UTF-8 +* **crate_description!:** Uses the `Cargo.toml` description field to fill in the `App::about` method at compile time +* **crate_name!:** Uses the `Cargo.toml` name field to fill in the `App::new` method at compile time +* **app_from_crate!:** Combines `crate_version!`, `crate_name!`, `crate_description!`, and `crate_authors!` into a single macro call to build a default `App` instance from the `Cargo.toml` fields +* **no_cargo:** adds a `no_cargo` feature to disable Cargo-env-var-dependent macros for those *not* using `cargo` to build their crates +* **Options:** fixes a critical bug where options weren't forced to have a value +* fixes a bug where calling the help of a subcommand wasn't ignoring required args of parent commands +* **Help Subcommand:** fixes a bug where the help subcommand couldn't be overriden +* **Low Index Multiples:** fixes a bug which caused combinations of LowIndexMultiples and `Arg::allow_hyphen_values` to fail parsing +* **Default Values:** improves the error message when default values are involved +* **YAML:** adds conditional requirements and conditional default values to YAML +* Support `--("some-arg-name")` syntax for defining long arg names when using `clap_app!` macro +* Support `("some app name")` syntax for defining app names when using `clap_app!` macro +* **Help Wrapping:** long app names (with spaces), authors, and descriptions are now wrapped appropriately +* **Conditional Default Values:** fixes the failing doc tests of Arg::default_value_ifs +* **Conditional Requirements:** adds docs for Arg::requires_ifs + +Here's the highlights from v2.0.0 to v2.19.3 * Fixes a bug where calling the help of a subcommand wasn't ignoring required args of parent commands - -Here's the highlights from v2.0.0 to v2.19.2 - * Fixes a bug by escaping square brackets in ZSH completions which were causing conflicts and errors. * **Bash Completion:** allows bash completion to fall back to traidtional bash completion upon no matching completing function * **Arg Setting**: Allows specifying an `AllowLeadingHyphen` style setting for values only for specific args, vice command wide diff --git a/src/app/mod.rs b/src/app/mod.rs index a4ac83cd..3c7d1ec7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1028,14 +1028,16 @@ 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 - pub fn write_help(&mut self, w: &mut W) -> ClapResult<()> { + pub fn write_help(&self, w: &mut W) -> ClapResult<()> { + // PENDING ISSUE: 808 + // https://github.com/kbknapp/clap-rs/issues/808 // 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.propogate_globals(); + // self.p.propogate_settings(); + // self.p.derive_display_order(); + // self.p.create_help_and_version(); - self.p.create_help_and_version(); Help::write_app_help(w, self) } diff --git a/src/app/parser.rs b/src/app/parser.rs index b3487817..c14d7a7b 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -546,10 +546,6 @@ impl<'a, 'b> Parser<'a, 'b> a.b.settings.is_set(ArgSettings::Multiple) && (a.index as usize != self.positionals.len()) }) { - debug_assert!(self.positionals.values() - .filter(|p| p.b.settings.is_set(ArgSettings::Multiple) - && p.v.num_vals.is_none()).map(|_| 1).sum::() <= 1, - "Only one positional argument with .multiple(true) set is allowed per command"); debug_assert!({ let mut it = self.positionals.values().rev(); @@ -581,17 +577,42 @@ impl<'a, 'b> Parser<'a, 'b> // If it's required we also need to ensure all previous positionals are // required too - let mut found = false; - for p in self.positionals.values().rev() { - if found { - debug_assert!(p.b.settings.is_set(ArgSettings::Required), - "Found positional argument which is not required with a lower index \ - than a required positional argument: {:?} index {}", - p.b.name, - p.index); - } else if p.b.settings.is_set(ArgSettings::Required) { - found = true; - continue; + if self.is_set(AppSettings::AllowMissingPositional) { + let mut found = false; + let mut foundx2 = false; + for p in self.positionals.values().rev() { + if foundx2 && !p.b.settings.is_set(ArgSettings::Required) { + // [arg1] is Ok + // [arg1] Is not + debug_assert!(p.b.settings.is_set(ArgSettings::Required), + "Found positional argument which is not required with a lower index \ + than a required positional argument by two or more: {:?} index {}", + p.b.name, + p.index); + } else if p.b.settings.is_set(ArgSettings::Required) { + if found { + foundx2 = true; + continue; + } + found = true; + continue; + } else { + found = false; + } + } + } else { + let mut found = false; + for p in self.positionals.values().rev() { + if found { + debug_assert!(p.b.settings.is_set(ArgSettings::Required), + "Found positional argument which is not required with a lower index \ + than a required positional argument: {:?} index {}", + p.b.name, + p.index); + } else if p.b.settings.is_set(ArgSettings::Required) { + found = true; + continue; + } } } } @@ -861,12 +882,15 @@ impl<'a, 'b> Parser<'a, 'b> let low_index_mults = self.is_set(AppSettings::LowIndexMultiplePositional) && !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); + let missing_pos = self.is_set(AppSettings::AllowMissingPositional) && + !self.positionals.is_empty() && + pos_counter == (self.positionals.len() - 1); debugln!("Parser::get_matches_with: Low index multiples...{:?}", low_index_mults); debugln!("Parser::get_matches_with: Positional counter...{}", pos_counter); - if low_index_mults { + if low_index_mults || missing_pos { if let Some(na) = it.peek() { let n = (*na).clone().into(); - needs_val_of = if let None = needs_val_of { + needs_val_of = if needs_val_of.is_none() { if let Some(p) = self.positionals.get(pos_counter) { Some(p.b.name) } else { diff --git a/src/app/settings.rs b/src/app/settings.rs index ab96d3ca..60481550 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -5,39 +5,40 @@ use std::ops::BitOr; bitflags! { flags Flags: u64 { - const SC_NEGATE_REQS = 0b000000000000000000000000000000001, - const SC_REQUIRED = 0b000000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b000000000000000000000000000000100, - const GLOBAL_VERSION = 0b000000000000000000000000000001000, - const VERSIONLESS_SC = 0b000000000000000000000000000010000, - const UNIFIED_HELP = 0b000000000000000000000000000100000, - const WAIT_ON_ERROR = 0b000000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b000000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b000000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b000000000000000000000001000000000, - const NEEDS_SC_HELP = 0b000000000000000000000010000000000, - const DISABLE_VERSION = 0b000000000000000000000100000000000, - const HIDDEN = 0b000000000000000000001000000000000, - const TRAILING_VARARG = 0b000000000000000000010000000000000, - const NO_BIN_NAME = 0b000000000000000000100000000000000, - const ALLOW_UNK_SC = 0b000000000000000001000000000000000, - const UTF8_STRICT = 0b000000000000000010000000000000000, - const UTF8_NONE = 0b000000000000000100000000000000000, - const LEADING_HYPHEN = 0b000000000000001000000000000000000, - const NO_POS_VALUES = 0b000000000000010000000000000000000, - const NEXT_LINE_HELP = 0b000000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b000000000001000000000000000000000, - const COLORED_HELP = 0b000000000010000000000000000000000, - const COLOR_ALWAYS = 0b000000000100000000000000000000000, - const COLOR_AUTO = 0b000000001000000000000000000000000, - const COLOR_NEVER = 0b000000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b000000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b000001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b000010000000000000000000000000000, - const DISABLE_HELP_SC = 0b000100000000000000000000000000000, - const DONT_COLLAPSE_ARGS = 0b001000000000000000000000000000000, - const ARGS_NEGATE_SCS = 0b010000000000000000000000000000000, - const PROPAGATE_VALS_DOWN = 0b100000000000000000000000000000000, + const SC_NEGATE_REQS = 0b0000000000000000000000000000000001, + const SC_REQUIRED = 0b0000000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b0000000000000000000000000000000100, + const GLOBAL_VERSION = 0b0000000000000000000000000000001000, + const VERSIONLESS_SC = 0b0000000000000000000000000000010000, + const UNIFIED_HELP = 0b0000000000000000000000000000100000, + const WAIT_ON_ERROR = 0b0000000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b0000000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b0000000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b0000000000000000000000001000000000, + const NEEDS_SC_HELP = 0b0000000000000000000000010000000000, + const DISABLE_VERSION = 0b0000000000000000000000100000000000, + const HIDDEN = 0b0000000000000000000001000000000000, + const TRAILING_VARARG = 0b0000000000000000000010000000000000, + const NO_BIN_NAME = 0b0000000000000000000100000000000000, + const ALLOW_UNK_SC = 0b0000000000000000001000000000000000, + const UTF8_STRICT = 0b0000000000000000010000000000000000, + const UTF8_NONE = 0b0000000000000000100000000000000000, + const LEADING_HYPHEN = 0b0000000000000001000000000000000000, + const NO_POS_VALUES = 0b0000000000000010000000000000000000, + const NEXT_LINE_HELP = 0b0000000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b0000000000001000000000000000000000, + const COLORED_HELP = 0b0000000000010000000000000000000000, + const COLOR_ALWAYS = 0b0000000000100000000000000000000000, + const COLOR_AUTO = 0b0000000001000000000000000000000000, + const COLOR_NEVER = 0b0000000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b0000000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b0000001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b0000010000000000000000000000000000, + const DISABLE_HELP_SC = 0b0000100000000000000000000000000000, + const DONT_COLLAPSE_ARGS = 0b0001000000000000000000000000000000, + const ARGS_NEGATE_SCS = 0b0010000000000000000000000000000000, + const PROPAGATE_VALS_DOWN = 0b0100000000000000000000000000000000, + const ALLOW_MISSING_POS = 0b1000000000000000000000000000000000, } } @@ -72,6 +73,7 @@ impl AppFlags { AllowInvalidUtf8 => UTF8_NONE, AllowLeadingHyphen => LEADING_HYPHEN, AllowNegativeNumbers => ALLOW_NEG_NUMS, + AllowMissingPositional => ALLOW_MISSING_POS, ColoredHelp => COLORED_HELP, ColorAlways => COLOR_ALWAYS, ColorAuto => COLOR_AUTO, @@ -199,6 +201,39 @@ pub enum AppSettings { /// [`AllowLeadingHyphen`]: ./enum.AppSettings.html#variant.AllowLeadingHyphen AllowNegativeNumbers, + /// Allows one to implement a CLI where the second to last positional argument is optional, but + /// the final positional argument is required. Such as `$ prog [optional] ` where one + /// of the two following usages is allowed: + /// + /// * `$ prog [optional] ` + /// * `$ prog ` + /// + /// This would otherwise not be allowed. This is useful when `[optional]` has a default value. + /// + /// **Note:** In addition to using this setting, the second positional argument *must* be + /// [required] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowMissingPositional) + /// .arg(Arg::with_name("arg1") + /// .default_value("something")) + /// .arg(Arg::with_name("arg2") + /// .required(true)) + /// .get_matches_from(vec![ + /// "myprog", "other" + /// ]); + /// + /// assert_eq!(m.value_of("arg1"), Some("something")); + /// assert_eq!(m.value_of("arg2"), Some("other")); + /// ``` + /// [required]: ./struct.Arg.html#method.required + AllowMissingPositional, + /// Specifies that an unexpected positional argument, /// which would otherwise cause a [`ErrorKind::UnknownArgument`] error, /// should instead be treated as a [`SubCommand`] within the [`ArgMatches`] struct. diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 0e3a0c6c..17aa8bd8 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -456,4 +456,15 @@ fn propagate_vals_down() { assert_eq!(m.value_of("cmd"), Some("set")); let sub_m = m.subcommand_matches("foo").unwrap(); assert_eq!(sub_m.value_of("cmd"), Some("set")); +} + +#[test] +fn allow_missing_positional() { + let m = App::new("test") + .setting(AppSettings::AllowMissingPositional) + .arg(Arg::from_usage("[src] 'some file'").default_value("src")) + .arg_from_usage(" 'some file'") + .get_matches_from(vec!["test", "file"]); + assert_eq!(m.value_of("src"), Some("src")); + assert_eq!(m.value_of("dest"), Some("file")); } \ No newline at end of file diff --git a/tests/positionals.rs b/tests/positionals.rs index d289d894..e8a0cd7b 100644 --- a/tests/positionals.rs +++ b/tests/positionals.rs @@ -190,3 +190,24 @@ fn single_positional_required_usage_string() { .get_matches_from(vec!["test", "file"]); assert_eq!(m.usage(), "USAGE:\n test "); } + +#[test] +#[should_panic] +fn missing_required() { + let r = App::new("test") + .arg_from_usage("[FILE1] 'some file'") + .arg_from_usage(" 'some file'") + .get_matches_from_safe(vec!["test", "file"]); + assert!(r.is_err()); + assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument); +} + +#[test] +fn missing_required_2() { + let r = App::new("test") + .arg_from_usage(" 'some file'") + .arg_from_usage(" 'some file'") + .get_matches_from_safe(vec!["test", "file"]); + assert!(r.is_err()); + assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument); +}