diff --git a/CHANGELOG.md b/CHANGELOG.md index 43844f33..01db9df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ + +## v2.21.0 (2017-03-09) + +#### Performance + +* doesn't run `arg_post_processing` on multiple values anymore ([ec516182](https://github.com/kbknapp/clap-rs/commit/ec5161828729f6a53f0fccec8648f71697f01f78)) +* changes internal use of `VecMap` to `Vec` for matched values of `Arg`s ([22bf137a](https://github.com/kbknapp/clap-rs/commit/22bf137ac581684c6ed460d2c3c640c503d62621)) +* vastly reduces the amount of cloning when adding non-global args minus when they're added from `App::args` which is forced to clone ([8da0303b](https://github.com/kbknapp/clap-rs/commit/8da0303bc02db5fe047cfc0631a9da41d9dc60f7)) +* refactor to remove unneeded vectors and allocations and checks for significant performance increases ([0efa4119](https://github.com/kbknapp/clap-rs/commit/0efa4119632f134fc5b8b9695b007dd94b76735d)) + +#### Documentation + +* Fix examples link in CONTRIBUTING.md ([60cf875d](https://github.com/kbknapp/clap-rs/commit/60cf875d67a252e19bb85054be57696fac2c57a1)) + +#### Improvements + +* when `AppSettings::SubcommandsNegateReqs` and `ArgsNegateSubcommands` are used, a new more accurate double line usage string is shown ([50f02300](https://github.com/kbknapp/clap-rs/commit/50f02300d81788817acefef0697e157e01b6ca32), closes [#871](https://github.com/kbknapp/clap-rs/issues/871)) + +#### API Additions + +* **Arg::last:** adds the ability to mark a positional argument as 'last' which means it should be used with `--` syntax and can be accessed early ([6a7aea90](https://github.com/kbknapp/clap-rs/commit/6a7aea9043b83badd9ab038b4ecc4c787716147e), closes [#888](https://github.com/kbknapp/clap-rs/issues/888)) +* provides `default_value_os` and `default_value_if[s]_os` ([0f2a3782](https://github.com/kbknapp/clap-rs/commit/0f2a378219a6930748d178ba350fe5925be5dad5), closes [#849](https://github.com/kbknapp/clap-rs/issues/849)) +* provides `App::help_message` and `App::version_message` which allows one to override the auto-generated help/version flag associated help ([389c413](https://github.com/kbknapp/clap-rs/commit/389c413b7023dccab8c76aa00577ea1d048e7a99), closes [#889](https://github.com/kbknapp/clap-rs/issues/889)) + +#### New Settings + +* **InferSubcommands:** adds a setting to allow one to infer shortened subcommands or aliases (i.e. for subcommmand "test", "t", "te", or "tes" would be allowed assuming no other ambiguities) ([11602032](https://github.com/kbknapp/clap-rs/commit/11602032f6ff05881e3adf130356e37d5e66e8f9), closes [#863](https://github.com/kbknapp/clap-rs/issues/863)) + +#### Bug Fixes + +* doesn't print the argument sections in the help message if all args in that section are hidden ([ce5ee5f5](https://github.com/kbknapp/clap-rs/commit/ce5ee5f5a76f838104aeddd01c8ec956dd347f50)) +* doesn't include the various [ARGS] [FLAGS] or [OPTIONS] if the only ones available are hidden ([7b4000af](https://github.com/kbknapp/clap-rs/commit/7b4000af97637703645c5fb2ac8bb65bd546b95b), closes [#882](https://github.com/kbknapp/clap-rs/issues/882)) +* now correctly shows subcommand as required in the usage string when AppSettings::SubcommandRequiredElseHelp is used ([8f0884c1](https://github.com/kbknapp/clap-rs/commit/8f0884c1764983a49b45de52a1eddf8d721564d8)) +* fixes some memory leaks when an error is detected and clap exits ([8c2dd287](https://github.com/kbknapp/clap-rs/commit/8c2dd28718262ace4ae0db98563809548e02a86b)) +* fixes a trait that's marked private accidentlly, but should be crate internal public ([1ae21108](https://github.com/kbknapp/clap-rs/commit/1ae21108015cea87e5360402e1747025116c7878)) +* **Completions:** fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts ([5e9b9cf4](https://github.com/kbknapp/clap-rs/commit/5e9b9cf4dd80fa66a624374fd04e6545635c1f94), closes [#846](https://github.com/kbknapp/clap-rs/issues/846)) + +#### Features + +* **Options:** adds the ability to require the equals syntax with options --opt=val ([f002693d](https://github.com/kbknapp/clap-rs/commit/f002693dec6a6959c4e9590cb7b7bfffd6d6e5bc), closes [#833](https://github.com/kbknapp/clap-rs/issues/833)) + + + ### v2.20.5 (2017-02-18) diff --git a/Cargo.toml b/Cargo.toml index da02d8a2..d196268b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ 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.2", optional = true } +term_size = { version = "0.2.3", optional = true } yaml-rust = { version = "0.3.5", optional = true } -clippy = { version = "~0.0.112", optional = true } +clippy = { version = "~0.0.118", optional = true } atty = { version = "0.2.2", optional = true } [dev-dependencies] @@ -65,7 +65,7 @@ debug = true rpath = false lto = false debug-assertions = true -codegen-units = 2 +codegen-units = 4 [profile.bench] opt-level = 3 diff --git a/README.md b/README.md index 7b232784..6d21ae61 100644 --- a/README.md +++ b/README.md @@ -45,131 +45,24 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) ## What's New -Here's the highlights for v2.20.5 +Here's the highlights for v2.21.0 +* adds the ability to mark a positional argument as 'last' which means it should be used with `--` syntax and can be accessed early to effectivly skip other positional args +* Some performance improvements by reducing the ammount of duplicate work, cloning, and allocations in all cases. +* Some massive perfomance gains when using many args (i.e. things like shell glob expansions) +* adds a setting to allow one to infer shortened subcommands or aliases (i.e. for subcommmand "test", "t", "te", or "tes" would be allowed assuming no other ambiguities) +* when `AppSettings::SubcommandsNegateReqs` and `ArgsNegateSubcommands` are used, a new more accurate double line usage string is shown +* provides `default_value_os` and `default_value_if[s]_os` +* provides `App::help_message` and `App::version_message` which allows one to override the auto-generated help/version flag associated help +* adds the ability to require the equals syntax with options `--opt=val` +* doesn't print the argument sections in the help message if all args in that section are hidden +* doesn't include the various `[ARGS]` `[FLAGS]` or `[OPTIONS]` if the only ones available are hidden +* now correctly shows subcommand as required in the usage string when AppSettings::SubcommandRequiredElseHelp is used +* fixes some "memory leaks" when an error is detected and clap exits +* fixes a trait that's marked private accidentlly, but should be crate internal public +* fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts * Fixes a critical bug in the `clap_app!` macro of a missing fragment specifier when using `!property` style tags. - - -Here's the highlights from v2.0.0 to v2.20.4 - -* Fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts * Fix examples link in CONTRIBUTING.md -* **Completions**: fixes bash completions for commands that have an underscore in the name -* **Completions**: fixes a bug where ZSH completions would panic if the binary name had an underscore in it -* allow final word to be wrapped in wrap_help -* **Completions**: fixes a bug where global args weren't included in the generated completion scripts -* **Macros Documentation:** adds a warning about changing values in Cargo.toml not triggering a rebuild automatically -* Fixes a critical bug where subcommand settings were being propogated too far -* Adds ArgGroup::multiple to the supported YAML fields for building ArgGroups from YAML -* Fixes a bug where the final word wasn't wrapped in help messages -* Fixes finding required arguments in group arguments -* **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 -* Fixes a bug where calling the help of a subcommand wasn't ignoring required args of parent commands -* 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 -* **Validators:** improves the error messages for validators -* **Required Unless:** fixes a bug where having required_unless set doesn't work when conflicts are also set -* **ZSH Completions:** fixes an issue where zsh completions caused panics if there were no subcommands -* **Completions:** Adds completion support for Microsoft PowerShell! (Thanks to @Arnavion) -* Allows specifying the second to last positional argument as `multiple(true)` (i.e. things such as `mv ... `) -* Adds an `App::get_name` and `App::get_bin_name` -* Conflicting argument errors are now symetrical, meaning more consistent and better usage suggestions -* **Completions:** adds automatic ZSH completion script generation support! :tada: :tada: -* **AppSettings:** adds new setting `AppSettings::AllowNegativeNumbers` which functions like `AllowLeadingHyphen` except only allows undefined negative numbers to pass parsing. -* Stabilize `clap_app!` macro (i.e. no longer need to use `unstable` feature) -* Deprecate `App::with_defaults` -* One can now alias arguments either visibly (which appears in the help text) or invisibly just like subcommands! -* The `from_usage` parser now correctly handles non-ascii names / options and help! -* **Value Delimiters:** fixes the confusion around implicitly setting value delimiters. (The default is to *not* use a delimiter unless explicitly set) -* Changes the default value delimiter rules (i.e. the default is `use_delimiter(false)` *unless* a setting/method that implies multiple values was used) **[Bugfix that *may* "break" code]** - * If code breaks, simply add `Arg::use_delimiter(true)` to the affected args -* Adds ability to hide the possible values from the help text on a per argument basis, instead of command wide -* Allows for limiting detected terminal width (i.e. wrap at `x` length, unless the terminal width is *smaller*) -* `clap` now ignores hard newlines in help messages and properly re-aligns text, but still wraps if the term width is too small -* Adds support for the setting `Arg::require_delimiter` from YAML -* `clap` no longer requires one to use `{n}` inside help text to insert a newline that is properly aligned. One can now use the normal `\n`. -* `clap` now ignores hard newlines in help messages and properly re-aligns text, but still wraps if the term width is too small -* Errors can now have custom description -* Uses `term_size` instead of home-grown solution on Windows -* Adds the ability to wrap help text intelligently on Windows! -* Moves docs to [docs.rs!](https://docs.rs/clap/)! -* Automatically moves help text to the next line and wraps when term width is determined to be too small, or help text is too long -* Vastly improves *development* error messages when using YAML -* Adds a shorthand way to ignore help text wrapping and use source formatting (i.e. `App::set_term_width(0)`) -* **Help Subcommand:** fixes misleading usage string when using multi-level subcommmands such as `myprog help subcmd1 subcmd2` -* **YAML:** allows using lists or single values with certain arg declarations for increased ergonomics -* **Fish Shell Completions:** one can generate a basic fish completions script at compile time! -* Adds the ability to generate completions to an `io::Write` object -* Adds an `App::unset_setting` and `App::unset_settings` -* **Completions:** one can now [generate a bash completions](https://docs.rs/clap/2.9.0/clap/struct.App.html#method.gen_completions) script at compile time! These completions work with options using [possible values](https://docs.rs/clap/2.9.0/clap/struct.Arg.html#method.possible_values), [subcommand aliases](https://docs.rs/clap/2.9.0/clap/struct.App.html#method.aliases), and even multiple levels of subcommands -* **Arg:** adds new optional setting [`Arg::require_delimiter`](https://docs.rs/clap/2.8.0/clap/struct.Arg.html#method.require_delimiter) which requires val delimiter to parse multiple values -* The terminal sizing portion has been factored out into a separate crate, [term_size](https://crates.io/crates/term_size) -* Options using multiple values and delimiters no longer parse additional values after a trailing space (i.e. `prog -o 1,2 file.txt` parses as `1,2` for `-o` and `file.txt` for a positional arg) -* Using options using multiple values and with an `=` no longer parse args after the trailing space as values (i.e. `prog -o=1 file.txt` parses as `1` for `-o` and `file.txt` for a positional arg) -* **Usage Strings:** `[FLAGS]` and `[ARGS]` are no longer blindly added to usage strings, instead only when applicable -* `arg_enum!`: allows using more than one meta item, or things like `#[repr(C)]` with `arg_enum!`s -* `App::print_help`: now prints the same as would have been printed by `--help` or the like -* Prevents invoking ` help help` and displaying incorrect help message -* Subcommand help messages requested via ` help ` now correctly match ` --help` -* One can now specify groups which require AT LEAST one of the args -* Allows adding multiple ArgGroups per Arg -* **Global Settings:** One can now set an `AppSetting` which is propogated down through child subcommands -* **Terminal Wrapping:** Allows wrapping at specified term width (Even on Windows!) (can now set an absolute width to "smart" wrap at) -* **SubCommands/Aliases:** adds support for visible aliases for subcommands (i.e. aliases that are dipslayed in the help message) -* **Subcommands/Aliases:** when viewing the help of an alias, it now display help of the aliased subcommand -* Adds new setting to stop delimiting values with `--` or `AppSettings::TrailingVarArg` -* Subcommands now support aliases - think of them as hidden subcommands that dispatch to said subcommand automatically -* Fixed times when `ArgGroup`s are duplicated in usage strings -* **Before Help:** adds support for displaying info before help message -* **Required Unless:** adds support for allowing args that are required unless certain other args are present -* **New Help Template Engine!**: Now you have full control over the layout of your help message. Major thanks to @hgrecco -* **Pull crate Authors from Cargo.toml**: One can now use the `crate_authors!` macro to automatically pull the crate authors from their Cargo.toml file -* **Colored Help Messages**: Help messages can now be optionally colored (See the `AppSettings::ColoredHelp` setting). Screenshot below. -* **Help text auto wraps and aligns at for subcommands too!** - Long help strings of subcommands will now properly wrap and align to term width on Linux and OS X. This can be turned off as well. -* **Help text auto wraps and aligns at term width!** - Long help strings will now properly wrap and align to term width on Linux and OS X (and presumably Unix too). This can be turned off as well. -* **Can customize the order of opts, flags, and subcommands in help messages** - Instead of using the default alphabetical order, you can now re-arrange the order of your args and subcommands in help message. This helps to emphasize more popular or important options. -* **Can auto-derive the order from declaration order** - Have a bunch of args or subcommmands to re-order? You can now just derive the order from the declaration order! -* **Help subcommand now accepts other subcommands as arguments!** - Similar to other CLI precedents, the `help` subcommand can now accept other subcommands as arguments to display their help message. i.e. `$ myprog help mysubcmd` (*Note* these can even be nested heavily such as `$ myprog help subcmd1 subcmd2 subcmd3` etc.) -* **Default Values**: Args can now specify default values -* **Next Line Help**: Args can have help strings on the line following the argument (useful for long arguments, or those with many values). This can be set command-wide or for individual args - -Here's a gif of them in action! - -![zsh-comppletions](http://i.imgur.com/rwlMbAv.gif) - -An example of the help text wrapping at term width: - -![screenshot](http://i.imgur.com/PAJzJJG.png) - -An example of the optional colored help: - -![screenshot](http://i.imgur.com/7fs2h5j.png) - For full details, see [CHANGELOG.md](https://github.com/kbknapp/clap-rs/blob/master/CHANGELOG.md) diff --git a/benches/02_simple.rs b/benches/02_simple.rs index eab94b37..010fa866 100644 --- a/benches/02_simple.rs +++ b/benches/02_simple.rs @@ -3,7 +3,7 @@ extern crate clap; extern crate test; -use clap::App; +use clap::{App, Arg}; use test::Bencher; @@ -25,6 +25,69 @@ fn build_app(b: &mut Bencher) { b.iter(|| create_app!()); } +#[bench] +fn add_flag(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| build_app().arg(Arg::from_usage("-s, --some 'something'"))); +} + +#[bench] +fn add_flag_ref(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| { + let arg = Arg::from_usage("-s, --some 'something'"); + build_app().arg(&arg) + }); +} + +#[bench] +fn add_opt(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| build_app().arg(Arg::from_usage("-s, --some 'something'"))); +} + +#[bench] +fn add_opt_ref(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| { + let arg = Arg::from_usage("-s, --some 'something'"); + build_app().arg(&arg) + }); +} + +#[bench] +fn add_pos(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| build_app().arg(Arg::with_name("some"))); +} + +#[bench] +fn add_pos_ref(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| { + let arg = Arg::with_name("some"); + build_app().arg(&arg) + }); +} + #[bench] fn parse_clean(b: &mut Bencher) { b.iter(|| create_app!().get_matches_from(vec![""])); diff --git a/src/app/help.rs b/src/app/help.rs index 848671a8..dab618e9 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -11,6 +11,7 @@ use app::parser::Parser; use args::{AnyArg, ArgSettings, DispOrder}; use errors::{Error, Result as ClapResult}; use fmt::{Format, Colorizer}; +use app::usage; // Third Party use unicode_width::UnicodeWidthStr; @@ -195,9 +196,7 @@ impl<'a> Help<'a> { if arg.longest_filter() { self.longest = cmp::max(self.longest, arg.to_string().len()); } - if !arg.is_set(ArgSettings::Hidden) { - arg_v.push(arg) - } + arg_v.push(arg) } let mut first = true; for arg in arg_v { @@ -541,7 +540,7 @@ 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.has_positionals(); + let pos = parser.positionals().filter(|arg| !arg.is_set(ArgSettings::Hidden)).count() > 0; let opts = parser.has_opts(); let subcmds = parser.has_subcommands(); @@ -684,7 +683,7 @@ impl<'a> Help<'a> { try!(write!(self.writer, "\n{}{}\n\n", TAB, - parser.create_usage_no_title(&[]))); + usage::create_help_usage(parser, true))); let flags = parser.has_flags(); let pos = parser.has_positionals(); @@ -881,7 +880,7 @@ impl<'a> Help<'a> { parser.meta.about.unwrap_or("unknown about"))); } b"usage" => { - try!(write!(self.writer, "{}", parser.create_usage_no_title(&[]))); + try!(write!(self.writer, "{}", usage::create_help_usage(parser, true))); } b"all-args" => { try!(self.write_all_args(&parser)); diff --git a/src/app/macros.rs b/src/app/macros.rs index 7224fa42..41af48c5 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -148,7 +148,12 @@ macro_rules! parse_positional { $matcher.inc_occurrence_of($p.b.name); let _ = $_self.groups_for_arg($p.b.name) .and_then(|vec| Some($matcher.inc_occurrences_of(&*vec))); - arg_post_processing!($_self, $p, $matcher); + if $_self.cache.map_or(true, |name| name != $p.b.name) { + arg_post_processing!($_self, $p, $matcher); + $_self.cache = Some($p.b.name); + } + + $_self.settings.set(AS::ValidArgFound); // Only increment the positional counter if it doesn't allow multiples if !$p.b.settings.is_set(ArgSettings::Multiple) { $pos_counter += 1; diff --git a/src/app/mod.rs b/src/app/mod.rs index 5c25dc89..9fa181ee 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -4,6 +4,8 @@ mod macros; pub mod parser; mod meta; mod help; +mod validator; +mod usage; // Std use std::env; @@ -405,7 +407,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn help_message>(mut self, s: S) -> Self { - self.p.help_message(s.into()); + self.p.help_message = Some(s.into()); self } @@ -423,7 +425,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn version_message>(mut self, s: S) -> Self { - self.p.version_message(s.into()); + self.p.version_message = Some(s.into()); self } diff --git a/src/app/parser.rs b/src/app/parser.rs index 1294df47..751ec189 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1,5 +1,4 @@ // Std -use std::collections::{BTreeMap, VecDeque}; use std::ffi::{OsStr, OsString}; use std::fmt::Display; use std::fs::File; @@ -21,17 +20,18 @@ use app::App; use app::help::Help; use app::meta::AppMeta; use app::settings::AppFlags; -use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder, - MatchedArg}; +use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder}; use args::settings::ArgSettings; use completions::ComplGen; use errors::{Error, ErrorKind}; use errors::Result as ClapResult; -use fmt::{Colorizer, ColorWhen}; +use fmt::ColorWhen; use osstringext::OsStrExt2; use completions::Shell; use suggestions; use app::settings::AppSettings as AS; +use app::validator::Validator; +use app::usage; #[allow(missing_debug_implementations)] #[doc(hidden)] @@ -46,16 +46,17 @@ pub struct Parser<'a, 'b> pub opts: Vec>, pub positionals: VecMap>, pub subcommands: Vec>, - groups: Vec>, + pub groups: Vec>, pub global_args: Vec>, - required: Vec<&'a str>, - r_ifs: Vec<(&'a str, &'b str, &'a str)>, - blacklist: Vec<&'b str>, - overrides: Vec<&'b str>, + pub required: Vec<&'a str>, + pub r_ifs: Vec<(&'a str, &'b str, &'a str)>, + pub blacklist: Vec<&'b str>, + pub overrides: Vec<&'b str>, help_short: Option, version_short: Option, - help_message: Option<&'a str>, - version_message: Option<&'a str>, + cache: Option<&'a str>, + pub help_message: Option<&'a str>, + pub version_message: Option<&'a str>, } impl<'a, 'b> Parser<'a, 'b> @@ -81,14 +82,6 @@ impl<'a, 'b> Parser<'a, 'b> self.version_short = Some(c); } - pub fn help_message(&mut self, s: &'a str) { - self.help_message = Some(s); - } - - pub fn version_message(&mut self, s: &'a str) { - self.version_message = Some(s); - } - pub fn gen_completions_to(&mut self, for_shell: Shell, buf: &mut W) { if !self.is_set(AS::Propogated) { self.propogate_help_version(); @@ -105,7 +98,11 @@ 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), @@ -121,33 +118,44 @@ impl<'a, 'b> Parser<'a, 'b> } #[inline] - fn debug_asserts(&self, a: &Arg) { - debug_assert!(!arg_names!(self).any(|name| name == a.b.name), - format!("Non-unique argument name: {} is already in use", a.b.name)); + fn debug_asserts(&self, a: &Arg) -> bool { + assert!(!arg_names!(self).any(|name| name == a.b.name), + format!("Non-unique argument name: {} is already in use", a.b.name)); if let Some(l) = a.s.long { - debug_assert!(!self.contains_long(l), - format!("Argument long must be unique\n\n\t--{} is already in use", - l)); + assert!(!self.contains_long(l), + "Argument long must be unique\n\n\t--{} is already in use", + l); } if let Some(s) = a.s.short { - debug_assert!(!self.contains_short(s), - format!("Argument short must be unique\n\n\t-{} is already in use", - s)); + assert!(!self.contains_short(s), + "Argument short must be unique\n\n\t-{} is already in use", + s); } let i = if a.index.is_none() { (self.positionals.len() + 1) } else { a.index.unwrap() as usize }; - debug_assert!(!self.positionals.contains_key(i), - format!("Argument \"{}\" has the same index as another positional \ + assert!(!self.positionals.contains_key(i), + "Argument \"{}\" has the same index as another positional \ argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \ to take multiple values", - a.b.name)); - debug_assert!(!(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)), - format!("Global arguments cannot be required.\n\n\t'{}' is marked as \ + a.b.name); + assert!(!(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)), + "Global arguments cannot be required.\n\n\t'{}' is marked as \ global and required", - a.b.name)); + a.b.name); + if a.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.", + a.b.name); + assert!(a.s.short.is_none(), + "Flags or Options may not have last(true) set. {} has both a short and last(true) set.", + a.b.name); + } + true } #[inline] @@ -190,16 +198,27 @@ impl<'a, 'b> Parser<'a, 'b> } } + #[inline] + fn implied_settings(&mut self, a: &Arg<'a, 'b>) { + if a.is_set(ArgSettings::Last) { + // if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args + // in the usage string don't get confused or left out. + self.set(AS::DontCollapseArgsInUsage); + self.set(AS::ContainsLast); + } + } + // actually adds the arguments pub fn add_arg(&mut self, a: Arg<'a, 'b>) { // if it's global we have to clone anyways if a.is_set(ArgSettings::Global) { return self.add_arg_ref(&a); } - self.debug_asserts(&a); + debug_assert!(self.debug_asserts(&a)); self.add_conditional_reqs(&a); self.add_arg_groups(&a); self.add_reqs(&a); + self.implied_settings(&a); if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { let i = if a.index.is_none() { (self.positionals.len() + 1) @@ -219,10 +238,11 @@ impl<'a, 'b> Parser<'a, 'b> } // actually adds the arguments but from a borrow (which means we have to do some clonine) pub fn add_arg_ref(&mut self, a: &Arg<'a, 'b>) { - self.debug_asserts(&a); - self.add_conditional_reqs(&a); - self.add_arg_groups(&a); - self.add_reqs(&a); + debug_assert!(self.debug_asserts(&a)); + self.add_conditional_reqs(a); + self.add_arg_groups(a); + self.add_reqs(a); + self.implied_settings(&a); if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { let i = if a.index.is_none() { (self.positionals.len() + 1) @@ -256,8 +276,10 @@ impl<'a, 'b> Parser<'a, 'b> } } if self.groups.iter().any(|g| g.name == group.name) { - let grp = - self.groups.iter_mut().find(|g| g.name == group.name).expect(INTERNAL_ERROR_MSG); + let grp = self.groups + .iter_mut() + .find(|g| g.name == group.name) + .expect(INTERNAL_ERROR_MSG); grp.args.extend_from_slice(&group.args); grp.requires = group.requires.clone(); grp.conflicts = group.conflicts.clone(); @@ -297,7 +319,11 @@ 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()); } @@ -313,21 +339,21 @@ impl<'a, 'b> Parser<'a, 'b> if self.is_set(AS::DeriveDisplayOrder) { let unified = self.is_set(AS::UnifiedHelpMessage); for (i, o) in self.opts - .iter_mut() - .enumerate() - .filter(|&(_, ref o)| o.s.disp_ord == 999) { + .iter_mut() + .enumerate() + .filter(|&(_, ref o)| o.s.disp_ord == 999) { o.s.disp_ord = if unified { o.s.unified_ord } else { i }; } for (i, f) in self.flags - .iter_mut() - .enumerate() - .filter(|&(_, ref f)| f.s.disp_ord == 999) { + .iter_mut() + .enumerate() + .filter(|&(_, ref f)| f.s.disp_ord == 999) { f.s.disp_ord = if unified { f.s.unified_ord } else { i }; } for (i, sc) in &mut self.subcommands - .iter_mut() - .enumerate() - .filter(|&(_, ref sc)| sc.p.meta.disp_ord == 999) { + .iter_mut() + .enumerate() + .filter(|&(_, ref sc)| sc.p.meta.disp_ord == 999) { sc.p.meta.disp_ord = i; } } @@ -339,199 +365,9 @@ impl<'a, 'b> Parser<'a, 'b> pub fn required(&self) -> Iter<&str> { self.required.iter() } #[cfg_attr(feature = "lints", allow(needless_borrow))] - pub fn get_required_from(&self, - reqs: &[&'a str], - matcher: Option<&ArgMatcher<'a>>, - extra: Option<&str>) - -> VecDeque { - debugln!("Parser::get_required_from: reqs={:?}, extra={:?}", - reqs, - extra); - let mut desc_reqs: Vec<&str> = vec![]; - desc_reqs.extend(extra); - let mut new_reqs: Vec<&str> = vec![]; - macro_rules! get_requires { - (@group $a: ident, $v:ident, $p:ident) => {{ - if let Some(rl) = self.groups.iter().filter(|g| g.requires.is_some()).find(|g| &g.name == $a).map(|g| g.requires.as_ref().unwrap()) { - for r in rl { - if !$p.contains(&r) { - debugln!("Parser::get_required_from:iter:{}: adding group req={:?}", $a, r); - $v.push(r); - } - } - } - }}; - ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{ - if let Some(rl) = self.$what.$how().filter(|a| a.b.requires.is_some()).find(|arg| &arg.b.name == $a).map(|a| a.b.requires.as_ref().unwrap()) { - for &(_, r) in rl.iter() { - if !$p.contains(&r) { - debugln!("Parser::get_required_from:iter:{}: adding arg req={:?}",$a, r); - $v.push(r); - } - } - } - }}; - } - // initialize new_reqs - for a in reqs { - get_requires!(a, flags, iter, new_reqs, reqs); - get_requires!(a, opts, iter, new_reqs, reqs); - get_requires!(a, positionals, values, new_reqs, reqs); - get_requires!(@group a, new_reqs, reqs); - } - desc_reqs.extend_from_slice(&*new_reqs); - debugln!("Parser::get_required_from: after init desc_reqs={:?}", - desc_reqs); - loop { - let mut tmp = vec![]; - for a in &new_reqs { - get_requires!(a, flags, iter, tmp, desc_reqs); - get_requires!(a, opts, iter, tmp, desc_reqs); - get_requires!(a, positionals, values, tmp, desc_reqs); - get_requires!(@group a, tmp, desc_reqs); - } - if tmp.is_empty() { - debugln!("Parser::get_required_from: no more children"); - break; - } else { - debugln!("Parser::get_required_from: after iter tmp={:?}", tmp); - debugln!("Parser::get_required_from: after iter new_reqs={:?}", - new_reqs); - desc_reqs.extend_from_slice(&*new_reqs); - new_reqs.clear(); - new_reqs.extend_from_slice(&*tmp); - debugln!("Parser::get_required_from: after iter desc_reqs={:?}", - desc_reqs); - } - } - desc_reqs.extend_from_slice(reqs); - desc_reqs.sort(); - desc_reqs.dedup(); - debugln!("Parser::get_required_from: final desc_reqs={:?}", desc_reqs); - let mut ret_val = VecDeque::new(); - let args_in_groups = self.groups - .iter() - .filter(|gn| desc_reqs.contains(&gn.name)) - .flat_map(|g| self.arg_names_in_group(&g.name)) - .collect::>(); - - let pmap = if let Some(ref m) = matcher { - desc_reqs.iter() - .filter(|a| self.positionals.values().any(|p| &&p.b.name == a)) - .filter(|&p| !m.contains(p)) - .filter_map(|p| self.positionals.values().find(|x| &x.b.name == p)) - .filter(|p| !args_in_groups.contains(&p.b.name)) - .map(|p| (p.index, p)) - .collect::>() // sort by index - } else { - desc_reqs.iter() - .filter(|a| self.positionals.values().any(|p| &&p.b.name == a)) - .filter_map(|p| self.positionals.values().find(|x| &x.b.name == p)) - .filter(|p| !args_in_groups.contains(&p.b.name)) - .map(|p| (p.index, p)) - .collect::>() // sort by index - }; - debugln!("Parser::get_required_from: args_in_groups={:?}", - args_in_groups); - for &p in pmap.values() { - let s = p.to_string(); - if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) { - ret_val.push_back(s); - } - } - for a in desc_reqs.iter() - .filter(|name| !self.positionals.values().any(|p| &&p.b.name == name)) - .filter(|name| !self.groups.iter().any(|g| &&g.name == name)) - .filter(|name| !args_in_groups.contains(name)) - .filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name))) { - debugln!("Parser::get_required_from:iter:{}:", a); - let arg = find_by_name!(self, a, flags, iter) - .map(|f| f.to_string()) - .unwrap_or_else(|| { - find_by_name!(self, a, opts, iter) - .map(|o| o.to_string()) - .expect(INTERNAL_ERROR_MSG) - }); - ret_val.push_back(arg); - } - let mut g_vec = vec![]; - for g in desc_reqs.iter().filter(|n| self.groups.iter().any(|g| &&g.name == n)) { - let g_string = self.args_in_group(g) - .join("|"); - g_vec.push(format!("<{}>", &g_string[..g_string.len()])); - } - g_vec.sort(); - g_vec.dedup(); - for g in g_vec { - ret_val.push_back(g); - } - - ret_val - } - - // Gets the `[ARGS]` tag for the usage string - pub fn get_args_tag(&self) -> Option { - debugln!("Parser::get_args_tag;"); - let mut count = 0; - 'outer: for p in self.positionals.values().filter(|p| !p.is_set(ArgSettings::Required)) { - debugln!("Parser::get_args_tag:iter:{}:", p.b.name); - if let Some(g_vec) = self.groups_for_arg(p.b.name) { - for grp_s in &g_vec { - debugln!("Parser::get_args_tag:iter:{}:iter:{};", p.b.name, grp_s); - // if it's part of a required group we don't want to count it - if self.groups.iter().any(|g| g.required && (&g.name == grp_s)) { - continue 'outer; - } - } - } - count += 1; - debugln!("Parser::get_args_tag:iter: {} Args not required", count); - } - if !self.is_set(AS::DontCollapseArgsInUsage) && (count > 1 || self.positionals.len() > 1) { - return None; // [ARGS] - } else if count == 1 { - let p = self.positionals - .values() - .find(|p| !p.is_set(ArgSettings::Required)) - .expect(INTERNAL_ERROR_MSG); - return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())); - } else if self.is_set(AS::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()) - } - - // 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 { - debugln!("Parser::needs_flags_tag:iter: f={};", f.b.name); - if let Some(l) = f.s.long { - if l == "help" || l == "version" { - // Don't print `[FLAGS]` just for help or version - continue; - } - } - if let Some(g_vec) = self.groups_for_arg(f.b.name) { - for grp_s in &g_vec { - debugln!("Parser::needs_flags_tag:iter:iter: grp_s={};", grp_s); - if self.groups.iter().any(|g| &g.name == grp_s && g.required) { - debug!("Parser::needs_flags_tag:iter:iter: Group is required"); - continue 'outer; - } - } - } - debugln!("Parser::needs_flags_tag:iter: [FLAGS] required"); - return true; - } - - debugln!("Parser::needs_flags_tag: [FLAGS] not required"); - false + #[inline] + pub fn has_args(&self) -> bool { + !(self.flags.is_empty() && self.opts.is_empty() && self.positionals.is_empty()) } #[inline] @@ -546,6 +382,38 @@ impl<'a, 'b> Parser<'a, 'b> #[inline] pub fn has_subcommands(&self) -> bool { !self.subcommands.is_empty() } + #[inline] + pub fn has_visible_opts(&self) -> bool { + if self.opts.is_empty() { + return false; + } + self.opts.iter().any(|o| !o.is_set(ArgSettings::Hidden)) + } + + #[inline] + pub fn has_visible_flags(&self) -> bool { + if self.flags.is_empty() { + return false; + } + self.flags.iter().any(|f| !f.is_set(ArgSettings::Hidden)) + } + + #[inline] + pub fn has_visible_positionals(&self) -> bool { + if self.positionals.is_empty() { + return false; + } + self.positionals.values().any(|p| !p.is_set(ArgSettings::Hidden)) + } + + #[inline] + pub fn has_visible_subcommands(&self) -> bool { + if self.subcommands.is_empty() { + return false; + } + self.subcommands.iter().any(|s| !s.p.is_set(AS::Hidden)) + } + #[inline] pub fn is_set(&self, s: AS) -> bool { self.settings.is_set(s) } @@ -556,74 +424,83 @@ impl<'a, 'b> Parser<'a, 'b> pub fn unset(&mut self, s: AS) { self.settings.unset(s) } #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] - pub fn verify_positionals(&mut self) { + pub fn verify_positionals(&mut self) -> bool { // Because you must wait until all arguments have been supplied, this is the first chance // to make assertions on positional argument indexes // // 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() { - debug_assert!(!(idx != self.positionals.len()), - format!("Found positional argument \"{}\" who's index is {} but there are \ - only {} positional arguments defined", - p.b.name, - idx, - self.positionals.len())); + 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", + p.b.name, + idx, + self.positionals.len()); } // Next we verify that only the highest index has a .multiple(true) (if any) - if - self.positionals - .values() - .any(|a| { - a.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(); + // Either the final positional is required + // Or the second to last has a terminator or .last(true) set + let ok = last.is_set(ArgSettings::Required) || + (second_to_last.v.terminator.is_some() || + second_to_last.b.is_set(ArgSettings::Last)); + assert!(ok, + "When using a positional argument with .multiple(true) that is *not the \ + last* positional argument, the last positional argument (i.e the one \ + with the highest index) *must* have .required(true) or .last(true) set."); + let num = self.positionals.len() - 1; + let ok = self.positionals + .get(num) + .unwrap() + .is_set(ArgSettings::Multiple); + assert!(ok, + "Only the last positional argument, or second to last positional \ + argument may be set to .multiple(true)"); - debug_assert!({ - let mut it = self.positionals.values().rev(); - // Either the final positional is required - it.next().unwrap().is_set(ArgSettings::Required) - // Or the second to last has a terminator set - || it.next().unwrap().v.terminator.is_some() - }, - "When using a positional argument with .multiple(true) that is *not the last* \ - positional argument, the last positional argument (i.e the one with the highest \ - index) *must* have .required(true) set."); - - debug_assert!({ - let num = self.positionals.len() - 1; - self.positionals.get(num).unwrap().is_set(ArgSettings::Multiple) - }, - "Only the last positional argument, or second to last positional argument may be set to .multiple(true)"); - - self.set(AS::LowIndexMultiplePositional); + self.settings.set(AS::LowIndexMultiplePositional); } - 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"); + let ok = self.positionals + .values() + .filter(|p| p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none()) + .map(|_| 1) + .sum::() <= 1; + assert!(ok, + "Only one positional argument with .multiple(true) set is allowed per \ + command"); - // If it's required we also need to ensure all previous positionals are - // required too if self.is_set(AS::AllowMissingPositional) { + // Check that if a required positional argument is found, all positions with a lower + // index are also required. 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) { + assert!(p.b.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.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) { + // Args that .last(true) don't count since they can be required and have + // positionals with a lower index that aren't required + // Imagine: prog [opt1] -- + // Both of these are valid invocations: + // $ prog r1 -- r2 + // $ prog r1 o1 -- r2 if found { foundx2 = true; continue; @@ -635,20 +512,38 @@ impl<'a, 'b> Parser<'a, 'b> } } } else { + // Check that if a required positional argument is found, all positions with a lower + // index are also required 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) { + assert!(p.b.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.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) { + // Args that .last(true) don't count since they can be required and have + // positionals with a lower index that aren't required + // Imagine: prog [opt1] -- + // Both of these are valid invocations: + // $ prog r1 -- r2 + // $ prog r1 o1 -- r2 found = true; continue; } } } + 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."); + } + + true } pub fn propogate_globals(&mut self) { @@ -665,25 +560,53 @@ impl<'a, 'b> Parser<'a, 'b> } // Checks if the arg matches a subcommand name, or any of it's aliases (if defined) - #[inline] - fn possible_subcommand(&self, arg_os: &OsStr) -> bool { + fn possible_subcommand(&self, arg_os: &OsStr) -> (bool, Option<&str>) { debugln!("Parser::possible_subcommand: arg={:?}", arg_os); - if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { - return false; + fn starts(h: &str, n: &OsStr) -> bool { + #[cfg(not(target_os = "windows"))] + use std::os::unix::ffi::OsStrExt; + #[cfg(target_os = "windows")] + use osstringext::OsStrExt3; + + let n_bytes = n.as_bytes(); + let h_bytes = OsStr::new(h).as_bytes(); + + h_bytes.starts_with(n_bytes) } - self.subcommands - .iter() - .any(|s| { - &s.p.meta.name[..] == &*arg_os || - (s.p.meta.aliases.is_some() && - s.p - .meta - .aliases - .as_ref() - .unwrap() - .iter() - .any(|&(a, _)| a == &*arg_os)) - }) + + if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { + return (false, None); + } + if !self.is_set(AS::InferSubcommands) { + if let Some(sc) = find_subcmd!(self, arg_os) { + return (true, Some(&sc.p.meta.name)); + } + } else { + let v = self.subcommands + .iter() + .filter(|s| { + starts(&s.p.meta.name[..], &*arg_os) || + (s.p + .meta + .aliases + .is_some() && + s.p + .meta + .aliases + .as_ref() + .unwrap() + .iter() + .filter(|&&(a, _)| starts(a, &*arg_os)) + .count() == 1) + }) + .map(|sc| &sc.p.meta.name) + .collect::>(); + + if v.len() == 1 { + return (true, Some(v[0])); + } + } + (false, None) } fn parse_help_subcommand(&self, it: &mut I) -> ClapResult<()> @@ -706,24 +629,21 @@ impl<'a, 'b> Parser<'a, 'b> help_help = true; } if let Some(c) = sc.subcommands - .iter() - .find(|s| &*s.p.meta.name == cmd) - .map(|sc| &sc.p) { + .iter() + .find(|s| &*s.p.meta.name == cmd) + .map(|sc| &sc.p) { sc = c; if i == cmds.len() - 1 { break; } } else if let Some(c) = sc.subcommands - .iter() - .find(|s| if let Some(ref als) = s.p - .meta - .aliases { - als.iter() - .any(|&(a, _)| &a == &&*cmd.to_string_lossy()) - } else { - false - }) - .map(|sc| &sc.p) { + .iter() + .find(|s| if let Some(ref als) = s.p.meta.aliases { + als.iter().any(|&(a, _)| &a == &&*cmd.to_string_lossy()) + } else { + false + }) + .map(|sc| &sc.p) { sc = c; if i == cmds.len() - 1 { break; @@ -786,29 +706,28 @@ impl<'a, 'b> Parser<'a, 'b> } else { false }; - debugln!("Parser::is_new_arg: Does arg allow leading hyphen...{:?}", + debugln!("Parser::is_new_arg: Arg::allow_leading_hyphen({:?})", arg_allows_tac); // Is this a new argument, or values from a previous option? - debug!("Parser::is_new_arg: Starts new arg..."); let mut ret = if arg_os.starts_with(b"--") { - sdebugln!("--"); + debugln!("Parser::is_new_arg: -- found"); if arg_os.len_() == 2 { return true; // We have to return true so override everything else } true } else if arg_os.starts_with(b"-") { - sdebugln!("-"); + debugln!("Parser::is_new_arg: - found"); // a singe '-' by itself is a value and typically means "stdin" on unix systems !(arg_os.len_() == 1) } else { - sdebugln!("Probable value"); + debugln!("Parser::is_new_arg: probably value"); false }; ret = ret && !arg_allows_tac; - debugln!("Parser::is_new_arg: Starts new arg...{:?}", ret); + debugln!("Parser::is_new_arg: starts_new_arg={:?}", ret); ret } @@ -823,7 +742,8 @@ impl<'a, 'b> Parser<'a, 'b> { debugln!("Parser::get_matches_with;"); // Verify all positional assertions pass - self.verify_positionals(); + debug_assert!(self.verify_positionals()); + let has_args = self.has_args(); // Next we create the `--help` and `--version` arguments and add them if // necessary @@ -845,12 +765,19 @@ impl<'a, 'b> Parser<'a, 'b> // Has the user already passed '--'? Meaning only positional args follow if !self.is_set(AS::TrailingValues) { // Does the arg match a subcommand name, or any of it's aliases (if defined) - if self.possible_subcommand(&arg_os) { - if &*arg_os == "help" && self.is_set(AS::NeedsSubcommandHelp) { - try!(self.parse_help_subcommand(it)); + { + let (is_match, sc_name) = self.possible_subcommand(&arg_os); + debugln!("Parser::get_matches_with: possible_sc={:?}, sc={:?}", + is_match, + sc_name); + if is_match { + let sc_name = sc_name.expect(INTERNAL_ERROR_MSG); + if sc_name == "help" && self.is_set(AS::NeedsSubcommandHelp) { + try!(self.parse_help_subcommand(it)); + } + subcmd_name = Some(sc_name.to_owned()); + break; } - subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned()); - break; } if !starts_new_arg { @@ -868,6 +795,8 @@ impl<'a, 'b> Parser<'a, 'b> if arg_os.len_() == 2 { // The user has passed '--' which means only positional args follow no // matter what they start with + debugln!("Parser::get_matches_with: found '--'"); + debugln!("Parser::get_matches_with: setting TrailingVals=true"); self.set(AS::TrailingValues); continue; } @@ -887,9 +816,9 @@ impl<'a, 'b> Parser<'a, 'b> if !(arg_os.to_string_lossy().parse::().is_ok() || arg_os.to_string_lossy().parse::().is_ok()) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), - "", - &*self.create_current_usage(matcher, None), - self.color())); + "", + &*usage::create_error_usage(self, matcher, None), + self.color())); } } else if !self.is_set(AS::AllowLeadingHyphen) { continue; @@ -900,11 +829,10 @@ impl<'a, 'b> Parser<'a, 'b> } } - if !self.is_set(AS::ArgsNegateSubcommands) { + if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound)) && + !self.is_set(AS::InferSubcommands) { if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), - self.subcommands - .iter() - .map(|s| &s.p.meta.name)) { + sc_names!(self)) { return Err(Error::invalid_subcommand(arg_os.to_string_lossy() .into_owned(), cdate, @@ -912,7 +840,8 @@ impl<'a, 'b> Parser<'a, 'b> .bin_name .as_ref() .unwrap_or(&self.meta.name), - &*self.create_current_usage(matcher, + &*usage::create_error_usage(self, + matcher, None), self.color())); } @@ -920,15 +849,13 @@ impl<'a, 'b> Parser<'a, 'b> } let low_index_mults = self.is_set(AS::LowIndexMultiplePositional) && - !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); let missing_pos = self.is_set(AS::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); + debugln!("Parser::get_matches_with: Low index multiples...{:?}", + low_index_mults); if low_index_mults || missing_pos { if let Some(na) = it.peek() { let n = (*na).clone().into(); @@ -941,12 +868,11 @@ impl<'a, 'b> Parser<'a, 'b> } else { None }; - if self.is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) || - suggestions::did_you_mean(&n.to_string_lossy(), - self.subcommands - .iter() - .map(|s| &s.p.meta.name)) - .is_some() { + let sc_match = { + self.possible_subcommand(&n).0 + }; + if self.is_new_arg(&n, needs_val_of) || sc_match || + suggestions::did_you_mean(&n.to_string_lossy(), sc_names!(self)).is_some() { debugln!("Parser::get_matches_with: Bumping the positional counter..."); pos_counter += 1; } @@ -954,8 +880,21 @@ impl<'a, 'b> Parser<'a, 'b> debugln!("Parser::get_matches_with: Bumping the positional counter..."); pos_counter += 1; } + } else if self.is_set(AS::ContainsLast) && self.is_set(AS::TrailingValues) { + // Came to -- and one postional has .last(true) set, so we go immediately + // to the last (highest index) positional + debugln!("Parser::get_matches_with: .last(true) and --, setting last pos"); + pos_counter = self.positionals.len(); } if let Some(p) = self.positionals.get(pos_counter) { + if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) { + return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), + "", + &*usage::create_error_usage(self, + matcher, + None), + self.color())); + } parse_positional!(self, p, arg_os, pos_counter, matcher); self.settings.set(AS::ValidArgFound); } else if self.is_set(AS::AllowExternalSubcommands) { @@ -964,7 +903,8 @@ impl<'a, 'b> Parser<'a, 'b> Some(s) => s.to_string(), None => { if !self.is_set(AS::StrictUtf8) { - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, + return Err(Error::invalid_utf8(&*usage::create_error_usage(self, + matcher, None), self.color())); } @@ -977,112 +917,85 @@ impl<'a, 'b> Parser<'a, 'b> while let Some(v) = it.next() { let a = v.into(); if a.to_str().is_none() && !self.is_set(AS::StrictUtf8) { - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), + return Err(Error::invalid_utf8(&*usage::create_error_usage(self, + matcher, + None), self.color())); } sc_m.add_val_to("", &a); } matcher.subcommand(SubCommand { - name: sc_name, - matches: sc_m.into(), - }); + name: sc_name, + matches: sc_m.into(), + }); } else if !(self.is_set(AS::AllowLeadingHyphen) || - self.is_set(AS::AllowNegativeNumbers)) { + self.is_set(AS::AllowNegativeNumbers)) && + !self.is_set(AS::InferSubcommands) { return Err(Error::unknown_argument(&*arg_os.to_string_lossy(), "", - &*self.create_current_usage(matcher, None), + &*usage::create_error_usage(self, + matcher, + None), self.color())); - } - } - - if let Some(ref pos_sc_name) = subcmd_name { - // is this is a real subcommand, or an alias - if self.subcommands.iter().any(|sc| &sc.p.meta.name == pos_sc_name) { - try!(self.parse_subcommand(&*pos_sc_name, matcher, it)); - } else { - let sc_name = &*self.subcommands - .iter() - .filter(|sc| sc.p.meta.aliases.is_some()) - .filter(|sc| { - sc.p - .meta - .aliases - .as_ref() - .expect(INTERNAL_ERROR_MSG) - .iter() - .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)); - } - } else if self.is_set(AS::SubcommandRequired) { - let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); - return Err(Error::missing_subcommand(bn, - &self.create_current_usage(matcher, None), - self.color())); - } else if self.is_set(AS::SubcommandRequiredElseHelp) { - debugln!("parser::get_matches_with: SubcommandRequiredElseHelp=true"); - let mut out = vec![]; - try!(self.write_help_err(&mut out)); - return Err(Error { - message: String::from_utf8_lossy(&*out).into_owned(), - kind: ErrorKind::MissingArgumentOrSubcommand, - info: None, - }); - } - - self.validate(needs_val_of, subcmd_name, matcher) - } - - fn validate(&mut self, - needs_val_of: Option<&'a str>, - subcmd_name: Option, - matcher: &mut ArgMatcher<'a>) - -> ClapResult<()> { - debugln!("Parser::validate;"); - let mut reqs_validated = false; - try!(self.add_defaults(matcher)); - if let Some(a) = needs_val_of { - debugln!("Parser::validate: needs_val_of={:?}", a); - if let Some(o) = find_by_name!(self, &a, opts, iter) { - try!(self.validate_required(matcher)); - reqs_validated = true; - let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { - v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) + } else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() { + 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().into_owned(), + cdate, + self.meta + .bin_name + .as_ref() + .unwrap_or(&self.meta.name), + &*usage::create_error_usage(self, + matcher, + None), + self.color())); } else { - true - }; - if should_err { - return Err(Error::empty_value(o, - &*self.create_current_usage(matcher, None), - self.color())); + return Err(Error::unrecognized_subcommand(arg_os.to_string_lossy() + .into_owned(), + self.meta + .bin_name + .as_ref() + .unwrap_or(&self.meta.name), + self.color())); } } } - try!(self.validate_blacklist(matcher)); - if !(self.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { - try!(self.validate_required(matcher)); - } - try!(self.validate_matched_args(matcher)); - matcher.usage(self.create_usage(&[])); - - if matcher.is_empty() && matcher.subcommand_name().is_none() && - self.is_set(AS::ArgRequiredElseHelp) { + if let Some(ref pos_sc_name) = subcmd_name { + let sc_name = { + find_subcmd!(self, pos_sc_name) + .expect(INTERNAL_ERROR_MSG) + .p + .meta + .name + .clone() + }; + 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); + return Err(Error::missing_subcommand(bn, + &usage::create_error_usage(self, matcher, None), + self.color())); + } else if self.is_set(AS::SubcommandRequiredElseHelp) { + debugln!("Parser::get_matches_with: SubcommandRequiredElseHelp=true"); let mut out = vec![]; try!(self.write_help_err(&mut out)); return Err(Error { - message: String::from_utf8_lossy(&*out).into_owned(), - kind: ErrorKind::MissingArgumentOrSubcommand, - info: None, - }); + message: String::from_utf8_lossy(&*out).into_owned(), + kind: ErrorKind::MissingArgumentOrSubcommand, + info: None, + }); } - Ok(()) + + Validator::new(self).validate(needs_val_of, subcmd_name, matcher) } + fn propogate_help_version(&mut self) { debugln!("Parser::propogate_help_version;"); self.create_help_and_version(); @@ -1095,7 +1008,10 @@ 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 @@ -1133,25 +1049,29 @@ 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); } - let reqs = self.get_required_from(&hs, Some(matcher), None); + let reqs = usage::get_required_usage_from(self, &hs, Some(matcher), None, false); for s in &reqs { write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG); } } 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 { @@ -1174,15 +1094,17 @@ impl<'a, 'b> Parser<'a, 'b> 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(), - matches: sc_matcher.into(), - }); + name: sc.p + .meta + .name + .clone(), + matches: sc_matcher.into(), + }); } Ok(()) } - - fn groups_for_arg(&self, name: &str) -> Option> { + pub fn groups_for_arg(&self, name: &str) -> Option> { debugln!("Parser::groups_for_arg: name={}", name); if self.groups.is_empty() { @@ -1206,18 +1128,20 @@ impl<'a, 'b> Parser<'a, 'b> Some(res) } - fn args_in_group(&self, group: &str) -> Vec { + pub fn args_in_group(&self, group: &str) -> Vec { let mut g_vec = vec![]; let mut args = vec![]; - for n in &self.groups.iter().find(|g| g.name == group).expect(INTERNAL_ERROR_MSG).args { + for n in &self.groups + .iter() + .find(|g| g.name == group) + .expect(INTERNAL_ERROR_MSG) + .args { if let Some(f) = self.flags.iter().find(|f| &f.b.name == n) { args.push(f.to_string()); } else if let Some(f) = self.opts.iter().find(|o| &o.b.name == n) { args.push(f.to_string()); - } else if let Some(p) = self.positionals - .values() - .find(|p| &p.b.name == n) { + } else if let Some(p) = self.positionals.values().find(|p| &p.b.name == n) { args.push(p.b.name.to_owned()); } else { g_vec.push(*n); @@ -1231,22 +1155,25 @@ impl<'a, 'b> Parser<'a, 'b> args.iter().map(ToOwned::to_owned).collect() } - fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { + pub fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { let mut g_vec = vec![]; let mut args = vec![]; - for n in &self.groups.iter().find(|g| g.name == group).expect(INTERNAL_ERROR_MSG).args { + for n in &self.groups + .iter() + .find(|g| g.name == group) + .expect(INTERNAL_ERROR_MSG) + .args { if self.groups.iter().any(|g| &g.name == &*n) { - args.extend(self.arg_names_in_group(&*n)); + args.extend(self.arg_names_in_group(n)); g_vec.push(*n); } else { - args.push(*n); + if !args.contains(n) { + args.push(*n); + } } } - // TODO: faster way to sort/dedup? - args.sort(); - args.dedup(); args.iter().map(|s| *s).collect() } @@ -1261,7 +1188,7 @@ impl<'a, 'b> Parser<'a, 'b> let arg = FlagBuilder { b: Base { name: "hclap_help", - help: Some(self.help_message.unwrap_or("Prints help information")), + help: self.help_message.or(Some("Prints help information")), ..Default::default() }, s: Switched { @@ -1281,7 +1208,7 @@ impl<'a, 'b> Parser<'a, 'b> let arg = FlagBuilder { b: Base { name: "vclap_version", - help: Some(self.version_message.unwrap_or("Prints version information")), + help: self.version_message.or(Some("Prints version information")), ..Default::default() }, s: Switched { @@ -1303,26 +1230,6 @@ impl<'a, 'b> Parser<'a, 'b> // Retrieves the names of all args the user has supplied thus far, except required ones // because those will be listed in self.required - pub fn create_current_usage(&self, matcher: &'b ArgMatcher<'a>, extra: Option<&str>) -> String { - let mut args: Vec<_> = matcher.arg_names() - .iter() - .filter(|n| { - if let Some(o) = find_by_name!(self, *n, opts, iter) { - !o.b.settings.is_set(ArgSettings::Required) - } else if let Some(p) = find_by_name!(self, *n, positionals, values) { - !p.b.settings.is_set(ArgSettings::Required) - } else { - true // flags can't be required, so they're always true - } - }) - .map(|&n| n) - .collect(); - if let Some(r) = extra { - args.push(r); - } - self.create_usage(&*args) - } - fn check_for_help_and_version_str(&self, arg: &OsStr) -> ClapResult<()> { debugln!("Parser::check_for_help_and_version_str;"); debug!("Parser::check_for_help_and_version_str: Checking if --{} is help or version...", @@ -1364,10 +1271,10 @@ impl<'a, 'b> Parser<'a, 'b> let mut buf = vec![]; try!(Help::write_parser_help(&mut buf, self)); Err(Error { - message: unsafe { String::from_utf8_unchecked(buf) }, - kind: ErrorKind::HelpDisplayed, - info: None, - }) + message: unsafe { String::from_utf8_unchecked(buf) }, + kind: ErrorKind::HelpDisplayed, + info: None, + }) } fn _version(&self) -> ClapResult<()> { @@ -1375,10 +1282,10 @@ impl<'a, 'b> Parser<'a, 'b> let mut buf_w = BufWriter::new(out.lock()); try!(self.print_version(&mut buf_w)); Err(Error { - message: String::new(), - kind: ErrorKind::VersionDisplayed, - info: None, - }) + message: String::new(), + kind: ErrorKind::VersionDisplayed, + info: None, + }) } fn parse_long_arg(&mut self, @@ -1404,7 +1311,10 @@ impl<'a, 'b> Parser<'a, 'b> opt.to_string()); self.settings.set(AS::ValidArgFound); let ret = try!(self.parse_opt(val, opt, val.is_some(), matcher)); - arg_post_processing!(self, opt, matcher); + if self.cache.map_or(true, |name| name != opt.b.name) { + arg_post_processing!(self, opt, matcher); + self.cache = Some(opt.b.name); + } return Ok(ret); } else if let Some(flag) = find_flag_by_long!(@os self, &arg) { @@ -1418,7 +1328,10 @@ impl<'a, 'b> Parser<'a, 'b> try!(self.parse_flag(flag, matcher)); // Handle conflicts, requirements, etc. - arg_post_processing!(self, flag, matcher); + if self.cache.map_or(true, |name| name != flag.b.name) { + arg_post_processing!(self, flag, matcher); + self.cache = Some(flag.b.name); + } return Ok(None); } else if self.is_set(AS::AllowLeadingHyphen) { @@ -1431,11 +1344,12 @@ impl<'a, 'b> Parser<'a, 'b> self.did_you_mean_error(arg.to_str().expect(INVALID_UTF8), matcher).map(|_| None) } + #[cfg_attr(feature = "lints", allow(len_zero))] fn parse_short_arg(&mut self, matcher: &mut ArgMatcher<'a>, full_arg: &OsStr) -> ClapResult> { - debugln!("Parser::parse_short_arg;"); + debugln!("Parser::parse_short_arg: full_arg={:?}", full_arg); let arg_os = full_arg.trim_left_matches(b'-'); let arg = arg_os.to_string_lossy(); @@ -1443,7 +1357,7 @@ impl<'a, 'b> Parser<'a, 'b> // `-v` `-a` `-l` assuming `v` `a` and `l` are all, or mostly, valid shorts. if self.is_set(AS::AllowLeadingHyphen) { if arg.chars().any(|c| !self.contains_short(c)) { - debugln!("Parser::parse_short_arg: Leading Hyphen Allowed and -{} isn't a valid short", + debugln!("Parser::parse_short_arg: LeadingHyphenAllowed yet -{} isn't valid", arg); return Ok(None); } @@ -1455,28 +1369,24 @@ impl<'a, 'b> Parser<'a, 'b> } for c in arg.chars() { - debugln!("Parser::parse_short_arg:iter: short={}", c); + debugln!("Parser::parse_short_arg:iter:{}", c); // Check for matching short options, and return the name if there is no trailing // concatenated value: -oval // Option: -o // Value: val if let Some(opt) = find_opt_by_short!(self, c) { - debugln!("Parser::parse_short_arg:iter: Found valid short opt -{} in '{}'", - c, - arg); + debugln!("Parser::parse_short_arg:iter:{}: Found valid opt", c); self.settings.set(AS::ValidArgFound); // Check for trailing concatenated value let p: Vec<_> = arg.splitn(2, c).collect(); - debugln!("Parser::parse_short_arg:iter: arg: {:?}, arg_os: {:?}, full_arg: {:?}", - arg, - arg_os, - full_arg); - debugln!("Parser::parse_short_arg:iter: p[0]: {:?}, p[1]: {:?}", + debugln!("Parser::parse_short_arg:iter:{}: p[0]={:?}, p[1]={:?}", + c, p[0].as_bytes(), p[1].as_bytes()); let i = p[0].as_bytes().len() + 1; let val = if p[1].as_bytes().len() > 0 { - debugln!("Parser::parse_short_arg:iter: setting val: {:?} (bytes), {:?} (ascii)", + debugln!("Parser::parse_short_arg:iter:{}: val={:?} (bytes), val={:?} (ascii)", + c, arg_os.split_at(i).1.as_bytes(), arg_os.split_at(i).1); Some(arg_os.split_at(i).1) @@ -1487,24 +1397,32 @@ impl<'a, 'b> Parser<'a, 'b> // Default to "we're expecting a value later" let ret = try!(self.parse_opt(val, opt, false, matcher)); - arg_post_processing!(self, opt, matcher); + if self.cache.map_or(true, |name| name != opt.b.name) { + arg_post_processing!(self, opt, matcher); + self.cache = Some(opt.b.name); + } return Ok(ret); } else if let Some(flag) = find_flag_by_short!(self, c) { - debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}", - c); + debugln!("Parser::parse_short_arg:iter:{}: Found valid flag", c); self.settings.set(AS::ValidArgFound); // Only flags can be help or version try!(self.check_for_help_and_version_char(c)); try!(self.parse_flag(flag, matcher)); + // Handle conflicts, requirements, overrides, etc. // Must be called here due to mutablilty - arg_post_processing!(self, flag, matcher); + if self.cache.map_or(true, |name| name != flag.b.name) { + arg_post_processing!(self, flag, matcher); + self.cache = Some(flag.b.name); + } } else { let arg = format!("-{}", c); return Err(Error::unknown_argument(&*arg, "", - &*self.create_current_usage(matcher, None), + &*usage::create_error_usage(self, + matcher, + None), self.color())); } } @@ -1529,7 +1447,7 @@ impl<'a, 'b> Parser<'a, 'b> (v.len_() == 0 || (opt.is_set(ArgSettings::RequireEquals) && !has_eq)) { sdebugln!("Found Empty - Error"); return Err(Error::empty_value(opt, - &*self.create_current_usage(matcher, None), + &*usage::create_error_usage(self, matcher, None), self.color())); } sdebugln!("Found - {:?}, len: {}", v, v.len_()); @@ -1540,7 +1458,7 @@ impl<'a, 'b> Parser<'a, 'b> } else if opt.is_set(ArgSettings::RequireEquals) && !opt.is_set(ArgSettings::EmptyValues) { sdebugln!("None, but requires equals...Error"); return Err(Error::empty_value(opt, - &*self.create_current_usage(matcher, None), + &*usage::create_error_usage(self, matcher, None), self.color())); } else { @@ -1626,62 +1544,6 @@ impl<'a, 'b> Parser<'a, 'b> Ok(None) } - fn validate_values(&self, - arg: &A, - ma: &MatchedArg, - matcher: &ArgMatcher<'a>) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_values: arg={:?}", arg.name()); - for (_, val) in &ma.vals { - if self.is_set(AS::StrictUtf8) && val.to_str().is_none() { - debugln!("Parser::validate_values: invalid UTF-8 found in val {:?}", - val); - return Err(Error::invalid_utf8(&*self.create_current_usage(matcher, None), - self.color())); - } - if let Some(p_vals) = arg.possible_vals() { - debugln!("Parser::validate_values: possible_vals={:?}", p_vals); - let val_str = val.to_string_lossy(); - if !p_vals.contains(&&*val_str) { - return Err(Error::invalid_value(val_str, - p_vals, - arg, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && - matcher.contains(&*arg.name()) { - debugln!("Parser::validate_values: illegal empty val found"); - return Err(Error::empty_value(arg, - &*self.create_current_usage(matcher, None), - self.color())); - } - if let Some(vtor) = arg.validator() { - debug!("Parser::validate_values: checking validator..."); - if let Err(e) = vtor(val.to_string_lossy().into_owned()) { - sdebugln!("error"); - return Err(Error::value_validation(Some(arg), e, self.color())); - } else { - sdebugln!("good"); - } - } - if let Some(vtor) = arg.validator_os() { - debug!("Parser::validate_values: checking validator_os..."); - if let Err(e) = vtor(&val) { - sdebugln!("error"); - return Err(Error::value_validation(Some(arg), - (*e).to_string_lossy().to_string(), - self.color())); - } else { - sdebugln!("good"); - } - } - } - Ok(()) - } fn parse_flag(&self, flag: &FlagBuilder<'a, 'b>, @@ -1696,312 +1558,6 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { - debugln!("Parser::validate_blacklist: blacklist={:?}", self.blacklist); - macro_rules! build_err { - ($me:ident, $name:expr, $matcher:ident) => ({ - debugln!("build_err!: name={}", $name); - let mut c_with = find_from!($me, $name, blacklist, &$matcher); - c_with = c_with.or( - $me.find_any_arg($name).map_or(None, |aa| aa.blacklist()) - .map_or(None, |bl| bl.iter().find(|arg| $matcher.contains(arg))) - .map_or(None, |an| $me.find_any_arg(an)) - .map_or(None, |aa| Some(format!("{}", aa))) - ); - debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, $name); - $matcher.remove($name); - let usg = $me.create_current_usage($matcher, None); - if let Some(f) = find_by_name!($me, $name, flags, iter) { - debugln!("build_err!: It was a flag..."); - Error::argument_conflict(f, c_with, &*usg, self.color()) - } else if let Some(o) = find_by_name!($me, $name, opts, iter) { - debugln!("build_err!: It was an option..."); - Error::argument_conflict(o, c_with, &*usg, self.color()) - } else { - match find_by_name!($me, $name, positionals, values) { - Some(p) => { - debugln!("build_err!: It was a positional..."); - Error::argument_conflict(p, c_with, &*usg, self.color()) - }, - None => panic!(INTERNAL_ERROR_MSG) - } - } - }); - } - - for name in &self.blacklist { - debugln!("Parser::validate_blacklist:iter: Checking blacklisted name: {}", - name); - if self.groups.iter().any(|g| &g.name == name) { - debugln!("Parser::validate_blacklist:iter: groups contains it..."); - for n in self.arg_names_in_group(name) { - debugln!("Parser::validate_blacklist:iter:iter: Checking arg '{}' in group...", - n); - if matcher.contains(n) { - debugln!("Parser::validate_blacklist:iter:iter: matcher contains it..."); - return Err(build_err!(self, &n, matcher)); - } - } - } else if matcher.contains(name) { - debugln!("Parser::validate_blacklist:iter: matcher contains it..."); - return Err(build_err!(self, name, matcher)); - } - } - Ok(()) - } - - fn validate_matched_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { - debugln!("Parser::validate_matched_args;"); - for (name, ma) in matcher.iter() { - debugln!("Parser::validate_matched_args:iter:{}: vals={:#?}", - name, - ma.vals); - if let Some(opt) = find_by_name!(self, name, opts, iter) { - try!(self.validate_arg_num_vals(opt, ma, matcher)); - try!(self.validate_values(opt, ma, matcher)); - try!(self.validate_arg_requires(opt, ma, matcher)); - try!(self.validate_arg_num_occurs(opt, ma, matcher)); - } else if let Some(flag) = find_by_name!(self, name, flags, iter) { - try!(self.validate_arg_requires(flag, ma, matcher)); - try!(self.validate_arg_num_occurs(flag, ma, matcher)); - } else if let Some(pos) = find_by_name!(self, name, positionals, values) { - try!(self.validate_arg_num_vals(pos, ma, matcher)); - try!(self.validate_arg_num_occurs(pos, ma, matcher)); - try!(self.validate_values(pos, ma, matcher)); - try!(self.validate_arg_requires(pos, ma, matcher)); - } else { - let grp = self.groups.iter().find(|g| &g.name == name).expect(INTERNAL_ERROR_MSG); - if let Some(ref g_reqs) = grp.requires { - if g_reqs.iter().any(|&n| !matcher.contains(n)) { - return self.missing_required_error(matcher, None); - } - } - } - } - Ok(()) - } - - fn validate_arg_num_occurs(&self, - a: &A, - ma: &MatchedArg, - matcher: &ArgMatcher) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_arg_num_occurs: a={};", a.name()); - if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) { - // Not the first time, and we don't allow multiples - return Err(Error::unexpected_multiple_usage(a, - &*self.create_current_usage(matcher, None), - self.color())); - } - Ok(()) - } - - fn validate_arg_num_vals(&self, - a: &A, - ma: &MatchedArg, - matcher: &ArgMatcher) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_arg_num_vals;"); - if let Some(num) = a.num_vals() { - debugln!("Parser::validate_arg_num_vals: num_vals set...{}", num); - let should_err = if a.is_set(ArgSettings::Multiple) { - ((ma.vals.len() as u64) % num) != 0 - } else { - num != (ma.vals.len() as u64) - }; - if should_err { - debugln!("Parser::validate_arg_num_vals: Sending error WrongNumberOfValues"); - return Err(Error::wrong_number_of_values(a, - num, - if a.is_set(ArgSettings::Multiple) { - (ma.vals.len() % num as usize) - } else { - ma.vals.len() - }, - if ma.vals.len() == 1 || - (a.is_set(ArgSettings::Multiple) && - (ma.vals.len() % num as usize) == - 1) { - "as" - } else { - "ere" - }, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if let Some(num) = a.max_vals() { - debugln!("Parser::validate_arg_num_vals: max_vals set...{}", num); - if (ma.vals.len() as u64) > num { - debugln!("Parser::validate_arg_num_vals: Sending error TooManyValues"); - return Err(Error::too_many_values(ma.vals - .get(ma.vals - .keys() - .last() - .expect(INTERNAL_ERROR_MSG)) - .expect(INTERNAL_ERROR_MSG) - .to_str() - .expect(INVALID_UTF8), - a, - &*self.create_current_usage(matcher, None), - self.color())); - } - } - if let Some(num) = a.min_vals() { - debugln!("Parser::validate_arg_num_vals: min_vals set: {}", num); - if (ma.vals.len() as u64) < num { - debugln!("Parser::validate_arg_num_vals: Sending error TooFewValues"); - return Err(Error::too_few_values(a, - num, - ma.vals.len(), - &*self.create_current_usage(matcher, None), - self.color())); - } - } - // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) - if a.takes_value() && !a.is_set(ArgSettings::EmptyValues) && ma.vals.is_empty() { - return Err(Error::empty_value(a, - &*self.create_current_usage(matcher, None), - self.color())); - } - Ok(()) - } - - fn validate_arg_requires(&self, - a: &A, - ma: &MatchedArg, - matcher: &ArgMatcher) - -> ClapResult<()> - where A: AnyArg<'a, 'b> + Display - { - debugln!("Parser::validate_arg_requires;"); - if let Some(a_reqs) = a.requires() { - for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { - if ma.vals - .values() - .any(|v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name)) { - return self.missing_required_error(matcher, None); - } - } - } - Ok(()) - } - - #[inline] - fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { - debugln!("Parser::missing_required_error: extra={:?}", extra); - let c = Colorizer { - use_stderr: true, - when: self.color(), - }; - let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); - if let Some(r) = extra { - reqs.push(r); - } - reqs.retain(|n| !matcher.contains(n)); - reqs.dedup(); - debugln!("Parser::missing_required_error: reqs={:#?}", reqs); - Err(Error::missing_required_argument(&*self.get_required_from(&reqs[..], - Some(matcher), - extra) - .iter() - .fold(String::new(), |acc, s| { - acc + &format!("\n {}", c.error(s))[..] - }), - &*self.create_current_usage(matcher, extra), - self.color())) - } - - fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { - debugln!("Parser::validate_required: required={:?};", self.required); - 'outer: for name in &self.required { - debugln!("Parser::validate_required:iter:{}:", name); - if matcher.contains(name) { - continue 'outer; - } - if let Some(a) = find_by_name!(self, name, flags, iter) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } else if let Some(a) = find_by_name!(self, name, opts, iter) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } else if let Some(a) = find_by_name!(self, name, positionals, values) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } - return self.missing_required_error(matcher, None); - } - - // Validate the conditionally required args - for &(a, v, r) in &self.r_ifs { - if let Some(ma) = matcher.get(a) { - for val in ma.vals.values() { - if v == val && matcher.get(r).is_none() { - return self.missing_required_error(matcher, Some(r)); - } - } - } - } - Ok(()) - } - - fn check_conflicts(&self, a: &A, matcher: &ArgMatcher) -> Option - where A: AnyArg<'a, 'b> - { - debugln!("Parser::check_conflicts: a={:?};", a.name()); - a.blacklist().map(|bl| { - bl.iter().any(|conf| { - matcher.contains(conf) || - self.groups - .iter() - .find(|g| &g.name == conf) - .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) - }) - }) - } - - fn check_required_unless(&self, a: &A, matcher: &ArgMatcher) -> Option - where A: AnyArg<'a, 'b> - { - debugln!("Parser::check_required_unless: a={:?};", a.name()); - macro_rules! check { - ($how:ident, $_self:ident, $a:ident, $m:ident) => {{ - $a.required_unless().map(|ru| { - ru.iter().$how(|n| { - $m.contains(n) || { - if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { - grp.args.iter().any(|arg| $m.contains(arg)) - } else { - false - } - } - }) - }) - }}; - } - if a.is_set(ArgSettings::RequiredUnlessAll) { - check!(all, self, a, matcher) - } else { - check!(any, self, a, matcher) - } - } - - #[inline] - fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool - where A: AnyArg<'a, 'b> - { - debugln!("Parser::is_missing_required_ok: a={}", a.name()); - self.check_conflicts(a, matcher).unwrap_or(false) || - self.check_required_unless(a, matcher).unwrap_or(false) - } - fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { // Didn't match a flag or option...maybe it was a typo and close to one let suffix = @@ -2025,118 +1581,10 @@ impl<'a, 'b> Parser<'a, 'b> let used_arg = format!("--{}", arg); Err(Error::unknown_argument(&*used_arg, &*suffix.0, - &*self.create_current_usage(matcher, None), + &*usage::create_error_usage(self, matcher, None), self.color())) } - // Creates a usage string if one was not provided by the user manually. This happens just - // after all arguments were parsed, but before any subcommands have been parsed - // (so as to give subcommands their own usage recursively) - pub fn create_usage(&self, used: &[&str]) -> String { - debugln!("Parser::create_usage;"); - let mut usage = String::with_capacity(75); - usage.push_str("USAGE:\n "); - usage.push_str(&self.create_usage_no_title(used)); - usage - } - - // Creates a usage string (*without title*) if one was not provided by the user - // manually. This happens just - // after all arguments were parsed, but before any subcommands have been parsed - // (so as to give subcommands their own usage recursively) - pub fn create_usage_no_title(&self, used: &[&str]) -> String { - debugln!("Parser::create_usage_no_title;"); - let mut usage = String::with_capacity(75); - if let Some(u) = self.meta.usage_str { - usage.push_str(&*u); - } else if used.is_empty() { - usage.push_str(&*self.meta - .usage - .as_ref() - .unwrap_or_else(|| { - self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta.name) - })); - let mut reqs: Vec<&str> = self.required().map(|r| &**r).collect(); - reqs.dedup(); - let req_string = self.get_required_from(&reqs, None, None) - .iter() - .fold(String::new(), |a, s| a + &format!(" {}", s)[..]); - - let flags = self.needs_flags_tag(); - if flags && !self.is_set(AS::UnifiedHelpMessage) { - usage.push_str(" [FLAGS]"); - } else if flags { - usage.push_str(" [OPTIONS]"); - } - if !self.is_set(AS::UnifiedHelpMessage) && self.has_opts() && - self.opts.iter().any(|o| !o.b.settings.is_set(ArgSettings::Required)) { - usage.push_str(" [OPTIONS]"); - } - - usage.push_str(&req_string[..]); - - // places a '--' in the usage string if there are args and options - // supporting multiple values - if self.has_positionals() && - self.opts.iter().any(|o| o.b.settings.is_set(ArgSettings::Multiple)) && - self.positionals.values().any(|p| !p.b.settings.is_set(ArgSettings::Required)) && - !self.has_subcommands() { - usage.push_str(" [--]") - } - if self.has_positionals() && - self.positionals.values().any(|p| !p.b.settings.is_set(ArgSettings::Required)) { - if let Some(args_tag) = self.get_args_tag() { - usage.push_str(&*args_tag); - } else { - usage.push_str(" [ARGS]"); - } - } - - - if self.has_subcommands() && !self.is_set(AS::SubcommandRequired) { - usage.push_str(" [SUBCOMMAND]"); - } else if self.is_set(AS::SubcommandRequired) && self.has_subcommands() { - usage.push_str(" "); - } - } else { - self.smart_usage(&mut usage, used); - } - - usage.shrink_to_fit(); - usage - } - - // Creates a context aware usage string, or "smart usage" from currently used - // args, and requirements - fn smart_usage(&self, usage: &mut String, used: &[&str]) { - debugln!("Parser::smart_usage;"); - let mut hs: Vec<&str> = self.required().map(|s| &**s).collect(); - hs.extend_from_slice(used); - - let r_string = self.get_required_from(&hs, None, None) - .iter() - .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]); - - usage.push_str(&self.meta - .usage - .as_ref() - .unwrap_or_else(|| { - self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta - .name) - }) - [..]); - usage.push_str(&*r_string); - if self.is_set(AS::SubcommandRequired) { - usage.push_str(" "); - } - } - // Prints the version to the user and exits if quit=true fn print_version(&self, w: &mut W) -> ClapResult<()> { try!(self.write_version(w)); @@ -2179,13 +1627,17 @@ impl<'a, 'b> Parser<'a, 'b> Help::write_parser_help_to_stderr(w, self) } - fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + pub fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { macro_rules! add_val { (@default $_self:ident, $a:ident, $m:ident) => { if let Some(ref val) = $a.v.default_val { if $m.get($a.b.name).is_none() { try!($_self.add_val_to_arg($a, OsStr::new(val), $m)); - arg_post_processing!($_self, $a, $m); + + if $_self.cache.map_or(true, |name| name != $a.name()) { + arg_post_processing!($_self, $a, $m); + $_self.cache = Some($a.name()); + } } } }; @@ -2196,7 +1648,7 @@ impl<'a, 'b> Parser<'a, 'b> for &(arg, val, default) in vm.values() { let add = if let Some(a) = $m.get(arg) { if let Some(v) = val { - a.vals.values().any(|value| v == value) + a.vals.iter().any(|value| v == value) } else { true } @@ -2205,7 +1657,10 @@ impl<'a, 'b> Parser<'a, 'b> }; if add { try!($_self.add_val_to_arg($a, OsStr::new(default), $m)); - arg_post_processing!($_self, $a, $m); + if $_self.cache.map_or(true, |name| name != $a.name()) { + arg_post_processing!($_self, $a, $m); + $_self.cache = Some($a.name()); + } done = true; break; } @@ -2267,21 +1722,38 @@ impl<'a, 'b> Parser<'a, 'b> None } - #[cfg_attr(feature = "lints", allow(explicit_iter_loop))] + // Only used for completion scripts due to bin_name messiness + #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] 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 - .as_ref() - .unwrap() - .iter() - .any(|&(s, _)| s == sc.split(' ').rev().next().expect(INTERNAL_ERROR_MSG))) { + .meta + .aliases + .as_ref() + .unwrap() + .iter() + .any(|&(s, _)| { + s == + sc.split(' ') + .rev() + .next() + .expect(INTERNAL_ERROR_MSG) + })) { return Some(s); } if let Some(app) = s.p.find_subcommand(sc) { diff --git a/src/app/settings.rs b/src/app/settings.rs index 42edc610..abc544a7 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -5,44 +5,46 @@ use std::ops::BitOr; bitflags! { flags Flags: u64 { - const SC_NEGATE_REQS = 0b00000000000000000000000000000000000001, - const SC_REQUIRED = 0b00000000000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000000000100, - const GLOBAL_VERSION = 0b00000000000000000000000000000000001000, - const VERSIONLESS_SC = 0b00000000000000000000000000000000010000, - const UNIFIED_HELP = 0b00000000000000000000000000000000100000, - const WAIT_ON_ERROR = 0b00000000000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b00000000000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b00000000000000000000000000001000000000, - const NEEDS_SC_HELP = 0b00000000000000000000000000010000000000, - const DISABLE_VERSION = 0b00000000000000000000000000100000000000, - const HIDDEN = 0b00000000000000000000000001000000000000, - const TRAILING_VARARG = 0b00000000000000000000000010000000000000, - const NO_BIN_NAME = 0b00000000000000000000000100000000000000, - const ALLOW_UNK_SC = 0b00000000000000000000001000000000000000, - const UTF8_STRICT = 0b00000000000000000000010000000000000000, - const UTF8_NONE = 0b00000000000000000000100000000000000000, - const LEADING_HYPHEN = 0b00000000000000000001000000000000000000, - const NO_POS_VALUES = 0b00000000000000000010000000000000000000, - const NEXT_LINE_HELP = 0b00000000000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b00000000000000001000000000000000000000, - const COLORED_HELP = 0b00000000000000010000000000000000000000, - const COLOR_ALWAYS = 0b00000000000000100000000000000000000000, - const COLOR_AUTO = 0b00000000000001000000000000000000000000, - const COLOR_NEVER = 0b00000000000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b00000000000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b00000000001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b00000000010000000000000000000000000000, - const DISABLE_HELP_SC = 0b00000000100000000000000000000000000000, - const DONT_COLLAPSE_ARGS = 0b00000001000000000000000000000000000000, - const ARGS_NEGATE_SCS = 0b00000010000000000000000000000000000000, - const PROPAGATE_VALS_DOWN = 0b00000100000000000000000000000000000000, - const ALLOW_MISSING_POS = 0b00001000000000000000000000000000000000, - const TRAILING_VALUES = 0b00010000000000000000000000000000000000, - const VALID_NEG_NUM_FOUND = 0b00100000000000000000000000000000000000, - const PROPOGATED = 0b01000000000000000000000000000000000000, - const VALID_ARG_FOUND = 0b10000000000000000000000000000000000000 + const SC_NEGATE_REQS = 1 << 0, + const SC_REQUIRED = 1 << 1, + const A_REQUIRED_ELSE_HELP = 1 << 2, + const GLOBAL_VERSION = 1 << 3, + const VERSIONLESS_SC = 1 << 4, + const UNIFIED_HELP = 1 << 5, + const WAIT_ON_ERROR = 1 << 6, + const SC_REQUIRED_ELSE_HELP= 1 << 7, + const NEEDS_LONG_HELP = 1 << 8, + const NEEDS_LONG_VERSION = 1 << 9, + const NEEDS_SC_HELP = 1 << 10, + const DISABLE_VERSION = 1 << 11, + const HIDDEN = 1 << 12, + const TRAILING_VARARG = 1 << 13, + const NO_BIN_NAME = 1 << 14, + const ALLOW_UNK_SC = 1 << 15, + const UTF8_STRICT = 1 << 16, + const UTF8_NONE = 1 << 17, + const LEADING_HYPHEN = 1 << 18, + const NO_POS_VALUES = 1 << 19, + const NEXT_LINE_HELP = 1 << 20, + const DERIVE_DISP_ORDER = 1 << 21, + const COLORED_HELP = 1 << 22, + const COLOR_ALWAYS = 1 << 23, + const COLOR_AUTO = 1 << 24, + const COLOR_NEVER = 1 << 25, + const DONT_DELIM_TRAIL = 1 << 26, + const ALLOW_NEG_NUMS = 1 << 27, + const LOW_INDEX_MUL_POS = 1 << 28, + const DISABLE_HELP_SC = 1 << 29, + const DONT_COLLAPSE_ARGS = 1 << 30, + const ARGS_NEGATE_SCS = 1 << 31, + const PROPAGATE_VALS_DOWN = 1 << 32, + const ALLOW_MISSING_POS = 1 << 33, + const TRAILING_VALUES = 1 << 34, + const VALID_NEG_NUM_FOUND = 1 << 35, + const PROPOGATED = 1 << 36, + const VALID_ARG_FOUND = 1 << 37, + const INFER_SUBCOMMANDS = 1 << 38, + const CONTAINS_LAST = 1 << 39, } } @@ -102,7 +104,9 @@ impl AppFlags { TrailingValues => TRAILING_VALUES, ValidNegNumFound => VALID_NEG_NUM_FOUND, Propogated => PROPOGATED, - ValidArgFound => VALID_ARG_FOUND + ValidArgFound => VALID_ARG_FOUND, + InferSubcommands => INFER_SUBCOMMANDS, + ContainsLast => CONTAINS_LAST } } @@ -525,6 +529,36 @@ pub enum AppSettings { /// This can be useful if there are many values, or they are explained elsewhere. HidePossibleValuesInHelp, + /// Tries to match unknown args to partial [`subcommands`] or their [aliases]. For example to + /// match a subcommand named `test`, one could use `t`, `te`, `tes`, and `test`. + /// + /// **NOTE:** The match *must not* be ambiguous at all in order to succeed. i.e. to match `te` + /// to `test` there could not also be a subcommand or alias `temp` because both start with `te` + /// + /// **CAUTION:** This setting can interfere with [positional/free arguments], take care when + /// designing CLIs which allow inferred subcommands and have potential positional/free + /// arguments who's values could start with the same characters as subcommands. If this is the + /// case, it's recommended to use settings such as [`AppSeettings::ArgsNegateSubcommands`] in + /// conjuction with this setting. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// let m = App::new("prog") + /// .setting(AppSettings::InferSubcommands) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from(vec![ + /// "prog", "te" + /// ]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + /// [`subcommands`]: ./struct.SubCommand.html + /// [positional/free arguments]: ./struct.Arg.html#method.index + /// [aliases]: ./struct.App.html#method.alias + /// [`AppSeettings::ArgsNegateSubcommands`]: ./enum.AppSettings.html#variant.ArgsNegateSubcommands + InferSubcommands, + /// Specifies that the parser should not assume the first argument passed is the binary name. /// This is normally the case when using a "daemon" style mode, or an interactive CLI where one /// one would not normally type the binary or program name for each command. @@ -827,6 +861,9 @@ pub enum AppSettings { #[doc(hidden)] ValidArgFound, + + #[doc(hidden)] + ContainsLast, } impl FromStr for AppSettings { @@ -851,6 +888,7 @@ impl FromStr for AppSettings { "globalversion" => Ok(AppSettings::GlobalVersion), "hidden" => Ok(AppSettings::Hidden), "hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp), + "infersubcommands" => Ok(AppSettings::InferSubcommands), "lowindexmultiplepositional" => Ok(AppSettings::LowIndexMultiplePositional), "nobinaryname" => Ok(AppSettings::NoBinaryName), "nextlinehelp" => Ok(AppSettings::NextLineHelp), @@ -943,6 +981,8 @@ mod test { AppSettings::Propogated); assert_eq!("trailingvalues".parse::().unwrap(), AppSettings::TrailingValues); + assert_eq!("infersubcommands".parse::().unwrap(), + AppSettings::InferSubcommands); assert!("hahahaha".parse::().is_err()); } } diff --git a/src/app/usage.rs b/src/app/usage.rs new file mode 100644 index 00000000..b083ef4f --- /dev/null +++ b/src/app/usage.rs @@ -0,0 +1,437 @@ +// std +use std::collections::{BTreeMap, VecDeque}; + +// Internal +use INTERNAL_ERROR_MSG; +use args::{AnyArg, ArgMatcher, PosBuilder}; +use args::settings::ArgSettings; +use app::settings::AppSettings as AS; +use app::parser::Parser; + +// Creates a usage string for display. This happens just after all arguments were parsed, but before +// any subcommands have been parsed (so as to give subcommands their own usage recursively) +pub fn create_usage_with_title(p: &Parser, used: &[&str]) -> String { + debugln!("usage::create_usage_with_title;"); + let mut usage = String::with_capacity(75); + usage.push_str("USAGE:\n "); + usage.push_str(&*create_usage_no_title(p, used)); + usage +} + +// Creates a usage string to be used in error message (i.e. one with currently used args) +pub fn create_error_usage<'a, 'b>(p: &Parser<'a, 'b>, + matcher: &'b ArgMatcher<'a>, + extra: Option<&str>) + -> String { + let mut args: Vec<_> = matcher.arg_names() + .iter() + .filter(|n| { + if let Some(o) = find_by_name!(p, *n, opts, iter) { + !o.b.is_set(ArgSettings::Required) && !o.b.is_set(ArgSettings::Hidden) + } else if let Some(p) = find_by_name!(p, *n, positionals, values) { + !p.b.is_set(ArgSettings::Required) && p.b.is_set(ArgSettings::Hidden) + } else { + true // flags can't be required, so they're always true + } + }) + .map(|&n| n) + .collect(); + if let Some(r) = extra { + args.push(r); + } + create_usage_with_title(p, &*args) +} + +// Creates a usage string (*without title*) if one was not provided by the user manually. +fn create_usage_no_title(p: &Parser, used: &[&str]) -> String { + debugln!("usage::create_usage_no_title;"); + if let Some(u) = p.meta.usage_str { + String::from(&*u) + } else if used.is_empty() { + create_help_usage(p, true) + } else { + create_smart_usage(p, used) + } +} + +// Creates a usage string for display in help messages (i.e. not for errors) +pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String { + let mut usage = String::with_capacity(75); + let name = p.meta + .usage + .as_ref() + .unwrap_or_else(|| { + p.meta + .bin_name + .as_ref() + .unwrap_or(&p.meta.name) + }); + usage.push_str(&*name); + let req_string = if incl_reqs { + let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect(); + reqs.sort(); + reqs.dedup(); + get_required_usage_from(p, &reqs, None, None, false).iter().fold(String::new(), |a, s| { + a + &format!(" {}", s)[..] + }) + } else { + String::new() + }; + + let flags = needs_flags_tag(p); + if flags && !p.is_set(AS::UnifiedHelpMessage) { + usage.push_str(" [FLAGS]"); + } else if flags { + usage.push_str(" [OPTIONS]"); + } + if !p.is_set(AS::UnifiedHelpMessage) && + p.opts.iter().any(|o| !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden)) { + usage.push_str(" [OPTIONS]"); + } + + usage.push_str(&req_string[..]); + + let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last)); + // places a '--' in the usage string if there are args and options + // supporting multiple values + if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple)) && + p.positionals.values().any(|p| !p.is_set(ArgSettings::Required)) && + !p.has_visible_subcommands() && !has_last { + usage.push_str(" [--]"); + } + let not_req_or_hidden = + |p: &PosBuilder| !p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Hidden); + if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) { + if let Some(args_tag) = get_args_tag(p, incl_reqs) { + usage.push_str(&*args_tag); + } else { + usage.push_str(" [ARGS]"); + } + if has_last && incl_reqs { + let pos = p.positionals + .values() + .find(|p| p.b.is_set(ArgSettings::Last)) + .expect(INTERNAL_ERROR_MSG); + debugln!("usage::create_help_usage: {} has .last(true)", pos.name()); + let req = pos.is_set(ArgSettings::Required); + if req { + usage.push_str(" -- <"); + } else { + usage.push_str(" [-- <"); + } + usage.push_str(pos.name()); + usage.push_str(">"); + usage.push_str(pos.multiple_str()); + if !req { + usage.push_str("]"); + } + } + } + + // incl_reqs is only false when this function is called recursively + if p.has_visible_subcommands() && incl_reqs { + if p.is_set(AS::SubcommandsNegateReqs) || p.is_set(AS::ArgsNegateSubcommands) { + if !p.is_set(AS::ArgsNegateSubcommands) { + usage.push_str("\n "); + usage.push_str(&*create_help_usage(p, false)); + usage.push_str(" "); + } else { + usage.push_str("\n "); + usage.push_str(&*name); + usage.push_str(" "); + } + } else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) { + usage.push_str(" "); + } else { + usage.push_str(" [SUBCOMMAND]"); + } + } + usage.shrink_to_fit(); + debugln!("usage::create_help_usage: usage={}", usage); + usage +} + +// Creates a context aware usage string, or "smart usage" from currently used +// args, and requirements +fn create_smart_usage(p: &Parser, used: &[&str]) -> String { + debugln!("usage::smart_usage;"); + let mut usage = String::with_capacity(75); + let mut hs: Vec<&str> = p.required().map(|s| &**s).collect(); + hs.extend_from_slice(used); + + let r_string = + get_required_usage_from(p, &hs, None, None, false).iter().fold(String::new(), |acc, s| { + acc + &format!(" {}", s)[..] + }); + + usage.push_str(&p.meta + .usage + .as_ref() + .unwrap_or_else(|| { + p.meta + .bin_name + .as_ref() + .unwrap_or(&p.meta.name) + }) + [..]); + usage.push_str(&*r_string); + if p.is_set(AS::SubcommandRequired) { + usage.push_str(" "); + } + usage.shrink_to_fit(); + usage +} + +// Gets the `[ARGS]` tag for the usage string +fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option { + debugln!("usage::get_args_tag;"); + let mut count = 0; + 'outer: for pos in p.positionals + .values() + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) { + debugln!("usage::get_args_tag:iter:{}:", pos.b.name); + if let Some(g_vec) = p.groups_for_arg(pos.b.name) { + for grp_s in &g_vec { + debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s); + // if it's part of a required group we don't want to count it + if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) { + continue 'outer; + } + } + } + count += 1; + debugln!("usage::get_args_tag:iter: {} Args not required or hidden", + count); + } + if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 { + debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]"); + return None; // [ARGS] + } else if count == 1 && incl_reqs { + let pos = p.positionals + .values() + .find(|pos| { + !pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Hidden) && + !pos.is_set(ArgSettings::Last) + }) + .expect(INTERNAL_ERROR_MSG); + debugln!("usage::get_args_tag:iter: Exactly one, returning {}", + pos.name()); + return Some(format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())); + } else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs { + debugln!("usage::get_args_tag:iter: Don't collapse returning all"); + return Some(p.positionals + .values() + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + .map(|pos| { + format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + }) + .collect::>() + .join("")); + } else if !incl_reqs { + debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); + let highest_req_pos = p.positionals + .iter() + .filter_map(|(idx, pos)| if pos.b.is_set(ArgSettings::Required) && + !pos.b.is_set(ArgSettings::Last) { + Some(idx) + } else { + None + }) + .max() + .unwrap_or(p.positionals.len()); + return Some(p.positionals + .iter() + .filter_map(|(idx, pos)| if idx <= highest_req_pos { + Some(pos) + } else { + None + }) + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + .map(|pos| { + format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + }) + .collect::>() + .join("")); + } + Some("".into()) +} + +// Determines if we need the `[FLAGS]` tag in the usage string +fn needs_flags_tag(p: &Parser) -> bool { + debugln!("usage::needs_flags_tag;"); + 'outer: for f in &p.flags { + debugln!("usage::needs_flags_tag:iter: f={};", f.b.name); + if let Some(l) = f.s.long { + if l == "help" || l == "version" { + // Don't print `[FLAGS]` just for help or version + continue; + } + } + if let Some(g_vec) = p.groups_for_arg(f.b.name) { + for grp_s in &g_vec { + debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s); + if p.groups.iter().any(|g| &g.name == grp_s && g.required) { + debugln!("usage::needs_flags_tag:iter:iter: Group is required"); + continue 'outer; + } + } + } + if f.is_set(ArgSettings::Hidden) { + continue; + } + debugln!("usage::needs_flags_tag:iter: [FLAGS] required"); + return true; + } + + debugln!("usage::needs_flags_tag: [FLAGS] not required"); + false +} + +// Returns the required args in usage string form by fully unrolling all groups +pub fn get_required_usage_from<'a, 'b>(p: &Parser<'a, 'b>, + reqs: &[&'a str], + matcher: Option<&ArgMatcher<'a>>, + extra: Option<&str>, + incl_last: bool) + -> VecDeque { + debugln!("usage::get_required_usage_from: reqs={:?}, extra={:?}", + reqs, + extra); + let mut desc_reqs: Vec<&str> = vec![]; + desc_reqs.extend(extra); + let mut new_reqs: Vec<&str> = vec![]; + macro_rules! get_requires { + (@group $a: ident, $v:ident, $p:ident) => {{ + if let Some(rl) = p.groups.iter() + .filter(|g| g.requires.is_some()) + .find(|g| &g.name == $a) + .map(|g| g.requires.as_ref().unwrap()) { + for r in rl { + if !$p.contains(&r) { + debugln!("usage::get_required_usage_from:iter:{}: adding group req={:?}", + $a, r); + $v.push(r); + } + } + } + }}; + ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{ + if let Some(rl) = p.$what.$how() + .filter(|a| a.b.requires.is_some()) + .find(|arg| &arg.b.name == $a) + .map(|a| a.b.requires.as_ref().unwrap()) { + for &(_, r) in rl.iter() { + if !$p.contains(&r) { + debugln!("usage::get_required_usage_from:iter:{}: adding arg req={:?}", + $a, r); + $v.push(r); + } + } + } + }}; + } + // initialize new_reqs + for a in reqs { + get_requires!(a, flags, iter, new_reqs, reqs); + get_requires!(a, opts, iter, new_reqs, reqs); + get_requires!(a, positionals, values, new_reqs, reqs); + get_requires!(@group a, new_reqs, reqs); + } + desc_reqs.extend_from_slice(&*new_reqs); + debugln!("usage::get_required_usage_from: after init desc_reqs={:?}", + desc_reqs); + loop { + let mut tmp = vec![]; + for a in &new_reqs { + get_requires!(a, flags, iter, tmp, desc_reqs); + get_requires!(a, opts, iter, tmp, desc_reqs); + get_requires!(a, positionals, values, tmp, desc_reqs); + get_requires!(@group a, tmp, desc_reqs); + } + if tmp.is_empty() { + debugln!("usage::get_required_usage_from: no more children"); + break; + } else { + debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp); + debugln!("usage::get_required_usage_from: after iter new_reqs={:?}", + new_reqs); + desc_reqs.extend_from_slice(&*new_reqs); + new_reqs.clear(); + new_reqs.extend_from_slice(&*tmp); + debugln!("usage::get_required_usage_from: after iter desc_reqs={:?}", + desc_reqs); + } + } + desc_reqs.extend_from_slice(reqs); + desc_reqs.sort(); + desc_reqs.dedup(); + debugln!("usage::get_required_usage_from: final desc_reqs={:?}", + desc_reqs); + let mut ret_val = VecDeque::new(); + let args_in_groups = p.groups + .iter() + .filter(|gn| desc_reqs.contains(&gn.name)) + .flat_map(|g| p.arg_names_in_group(&g.name)) + .collect::>(); + + let pmap = if let Some(ref m) = matcher { + desc_reqs.iter() + .filter(|a| p.positionals.values().any(|p| &&p.b.name == a)) + .filter(|&pos| !m.contains(pos)) + .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos)) + .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) + .filter(|pos| !args_in_groups.contains(&pos.b.name)) + .map(|pos| (pos.index, pos)) + .collect::>() // sort by index + } else { + desc_reqs.iter() + .filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a)) + .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos)) + .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) + .filter(|pos| !args_in_groups.contains(&pos.b.name)) + .map(|pos| (pos.index, pos)) + .collect::>() // sort by index + }; + debugln!("usage::get_required_usage_from: args_in_groups={:?}", + args_in_groups); + for &p in pmap.values() { + let s = p.to_string(); + if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) { + ret_val.push_back(s); + } + } + for a in desc_reqs.iter() + .filter(|name| !p.positionals.values().any(|p| &&p.b.name == name)) + .filter(|name| !p.groups.iter().any(|g| &&g.name == name)) + .filter(|name| !args_in_groups.contains(name)) + .filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name))) { + debugln!("usage::get_required_usage_from:iter:{}:", a); + let arg = find_by_name!(p, a, flags, iter) + .map(|f| f.to_string()) + .unwrap_or_else(|| { + find_by_name!(p, a, opts, iter) + .map(|o| o.to_string()) + .expect(INTERNAL_ERROR_MSG) + }); + ret_val.push_back(arg); + } + let mut g_vec: Vec = vec![]; + for g in desc_reqs.iter().filter(|n| p.groups.iter().any(|g| &&g.name == n)) { + let g_string = p.args_in_group(g).join("|"); + let elem = format!("<{}>", &g_string[..g_string.len()]); + if !g_vec.contains(&elem) { + g_vec.push(elem); + } + } + for g in g_vec { + ret_val.push_back(g); + } + + ret_val +} diff --git a/src/app/validator.rs b/src/app/validator.rs new file mode 100644 index 00000000..f874a8dc --- /dev/null +++ b/src/app/validator.rs @@ -0,0 +1,454 @@ +// std +use std::fmt::Display; + +// Internal +use INTERNAL_ERROR_MSG; +use INVALID_UTF8; +use args::{AnyArg, ArgMatcher, MatchedArg}; +use args::settings::ArgSettings; +use errors::{Error, ErrorKind}; +use errors::Result as ClapResult; +use osstringext::OsStrExt2; +use app::settings::AppSettings as AS; +use app::parser::Parser; +use fmt::Colorizer; +use app::usage; + +pub struct Validator<'a, 'b, 'z>(&'z mut Parser<'a, 'b>) + where 'a: 'b, + 'b: 'z; + +impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { + pub fn new(p: &'z mut Parser<'a, 'b>) -> Self { Validator(p) } + + pub fn validate(&mut self, + needs_val_of: Option<&'a str>, + subcmd_name: Option, + matcher: &mut ArgMatcher<'a>) + -> ClapResult<()> { + debugln!("Validator::validate;"); + let mut reqs_validated = false; + try!(self.0.add_defaults(matcher)); + if let Some(a) = needs_val_of { + debugln!("Validator::validate: needs_val_of={:?}", a); + if let Some(o) = find_by_name!(self.0, &a, opts, iter) { + try!(self.validate_required(matcher)); + reqs_validated = true; + let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { + v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) + } else { + true + }; + if should_err { + return Err(Error::empty_value(o, + &*usage::create_error_usage(self.0, + matcher, + None), + self.0.color())); + } + } + } + + try!(self.validate_blacklist(matcher)); + if !(self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { + try!(self.validate_required(matcher)); + } + try!(self.validate_matched_args(matcher)); + matcher.usage(usage::create_usage_with_title(self.0, &[])); + + if matcher.is_empty() && matcher.subcommand_name().is_none() && + self.0.is_set(AS::ArgRequiredElseHelp) { + let mut out = vec![]; + try!(self.0.write_help_err(&mut out)); + return Err(Error { + message: String::from_utf8_lossy(&*out).into_owned(), + kind: ErrorKind::MissingArgumentOrSubcommand, + info: None, + }); + } + Ok(()) + } + + fn validate_values(&self, + arg: &A, + ma: &MatchedArg, + matcher: &ArgMatcher<'a>) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_values: arg={:?}", arg.name()); + for val in &ma.vals { + if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() { + debugln!("Validator::validate_values: invalid UTF-8 found in val {:?}", + val); + return Err(Error::invalid_utf8(&*usage::create_error_usage(self.0, matcher, None), + self.0.color())); + } + if let Some(p_vals) = arg.possible_vals() { + debugln!("Validator::validate_values: possible_vals={:?}", p_vals); + let val_str = val.to_string_lossy(); + if !p_vals.contains(&&*val_str) { + return Err(Error::invalid_value(val_str, + p_vals, + arg, + &*usage::create_error_usage(self.0, + matcher, + None), + self.0.color())); + } + } + if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && + matcher.contains(&*arg.name()) { + debugln!("Validator::validate_values: illegal empty val found"); + return Err(Error::empty_value(arg, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color())); + } + if let Some(vtor) = arg.validator() { + debug!("Validator::validate_values: checking validator..."); + if let Err(e) = vtor(val.to_string_lossy().into_owned()) { + sdebugln!("error"); + return Err(Error::value_validation(Some(arg), e, self.0.color())); + } else { + sdebugln!("good"); + } + } + if let Some(vtor) = arg.validator_os() { + debug!("Validator::validate_values: checking validator_os..."); + if let Err(e) = vtor(val) { + sdebugln!("error"); + return Err(Error::value_validation(Some(arg), + (*e).to_string_lossy().to_string(), + self.0.color())); + } else { + sdebugln!("good"); + } + } + } + Ok(()) + } + + fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + debugln!("Validator::validate_blacklist: blacklist={:?}", + self.0.blacklist); + macro_rules! build_err { + ($p:expr, $name:expr, $matcher:ident) => ({ + debugln!("build_err!: name={}", $name); + let mut c_with = find_from!($p, $name, blacklist, &$matcher); + c_with = c_with.or( + $p.find_any_arg($name).map_or(None, |aa| aa.blacklist()) + .map_or(None, + |bl| bl.iter().find(|arg| $matcher.contains(arg))) + .map_or(None, |an| $p.find_any_arg(an)) + .map_or(None, |aa| Some(format!("{}", aa))) + ); + debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, $name); + $matcher.remove($name); + let usg = usage::create_error_usage($p, $matcher, None); + if let Some(f) = find_by_name!($p, $name, flags, iter) { + debugln!("build_err!: It was a flag..."); + Error::argument_conflict(f, c_with, &*usg, self.0.color()) + } else if let Some(o) = find_by_name!($p, $name, opts, iter) { + debugln!("build_err!: It was an option..."); + Error::argument_conflict(o, c_with, &*usg, self.0.color()) + } else { + match find_by_name!($p, $name, positionals, values) { + Some(p) => { + debugln!("build_err!: It was a positional..."); + Error::argument_conflict(p, c_with, &*usg, self.0.color()) + }, + None => panic!(INTERNAL_ERROR_MSG) + } + } + }); + } + + for name in &self.0.blacklist { + debugln!("Validator::validate_blacklist:iter: Checking blacklisted name: {}", + name); + if self.0 + .groups + .iter() + .any(|g| &g.name == name) { + debugln!("Validator::validate_blacklist:iter: groups contains it..."); + for n in self.0.arg_names_in_group(name) { + debugln!("Validator::validate_blacklist:iter:iter: Checking arg '{}' in group...", + n); + if matcher.contains(n) { + debugln!("Validator::validate_blacklist:iter:iter: matcher contains it..."); + return Err(build_err!(self.0, &n, matcher)); + } + } + } else if matcher.contains(name) { + debugln!("Validator::validate_blacklist:iter: matcher contains it..."); + return Err(build_err!(self.0, name, matcher)); + } + } + Ok(()) + } + + fn validate_matched_args(&self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + debugln!("Validator::validate_matched_args;"); + for (name, ma) in matcher.iter() { + debugln!("Validator::validate_matched_args:iter:{}: vals={:#?}", + name, + ma.vals); + if let Some(opt) = find_by_name!(self.0, name, opts, iter) { + try!(self.validate_arg_num_vals(opt, ma, matcher)); + try!(self.validate_values(opt, ma, matcher)); + try!(self.validate_arg_requires(opt, ma, matcher)); + try!(self.validate_arg_num_occurs(opt, ma, matcher)); + } else if let Some(flag) = find_by_name!(self.0, name, flags, iter) { + try!(self.validate_arg_requires(flag, ma, matcher)); + try!(self.validate_arg_num_occurs(flag, ma, matcher)); + } else if let Some(pos) = find_by_name!(self.0, name, positionals, values) { + try!(self.validate_arg_num_vals(pos, ma, matcher)); + try!(self.validate_arg_num_occurs(pos, ma, matcher)); + try!(self.validate_values(pos, ma, matcher)); + try!(self.validate_arg_requires(pos, ma, matcher)); + } else { + let grp = self.0 + .groups + .iter() + .find(|g| &g.name == name) + .expect(INTERNAL_ERROR_MSG); + if let Some(ref g_reqs) = grp.requires { + if g_reqs.iter().any(|&n| !matcher.contains(n)) { + return self.missing_required_error(matcher, None); + } + } + } + } + Ok(()) + } + + fn validate_arg_num_occurs(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_arg_num_occurs: a={};", a.name()); + if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) { + // Not the first time, and we don't allow multiples + return Err(Error::unexpected_multiple_usage(a, + &*usage::create_error_usage(self.0, + matcher, + None), + self.0.color())); + } + Ok(()) + } + + fn validate_arg_num_vals(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_arg_num_vals;"); + if let Some(num) = a.num_vals() { + debugln!("Validator::validate_arg_num_vals: num_vals set...{}", num); + let should_err = if a.is_set(ArgSettings::Multiple) { + ((ma.vals.len() as u64) % num) != 0 + } else { + num != (ma.vals.len() as u64) + }; + if should_err { + debugln!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues"); + return Err(Error::wrong_number_of_values(a, + num, + if a.is_set(ArgSettings::Multiple) { + (ma.vals.len() % num as usize) + } else { + ma.vals.len() + }, + if ma.vals.len() == 1 || + (a.is_set(ArgSettings::Multiple) && + (ma.vals.len() % num as usize) == + 1) { + "as" + } else { + "ere" + }, + &*usage::create_error_usage(self.0, + matcher, + None), + self.0.color())); + } + } + if let Some(num) = a.max_vals() { + debugln!("Validator::validate_arg_num_vals: max_vals set...{}", num); + if (ma.vals.len() as u64) > num { + debugln!("Validator::validate_arg_num_vals: Sending error TooManyValues"); + return Err(Error::too_many_values(ma.vals + .iter() + .last() + .expect(INTERNAL_ERROR_MSG) + .to_str() + .expect(INVALID_UTF8), + a, + &*usage::create_error_usage(self.0, + matcher, + None), + self.0.color())); + } + } + if let Some(num) = a.min_vals() { + debugln!("Validator::validate_arg_num_vals: min_vals set: {}", num); + if (ma.vals.len() as u64) < num { + debugln!("Validator::validate_arg_num_vals: Sending error TooFewValues"); + return Err(Error::too_few_values(a, + num, + ma.vals.len(), + &*usage::create_error_usage(self.0, + matcher, + None), + self.0.color())); + } + } + // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) + if a.takes_value() && !a.is_set(ArgSettings::EmptyValues) && ma.vals.is_empty() { + return Err(Error::empty_value(a, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color())); + } + Ok(()) + } + + fn validate_arg_requires(&self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher) + -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("Validator::validate_arg_requires;"); + if let Some(a_reqs) = a.requires() { + for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { + let missing_req = + |v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name); + if ma.vals.iter().any(missing_req) { + return self.missing_required_error(matcher, None); + } + } + } + Ok(()) + } + + fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { + debugln!("Validator::validate_required: required={:?};", + self.0.required); + 'outer: for name in &self.0.required { + debugln!("Validator::validate_required:iter:{}:", name); + if matcher.contains(name) { + continue 'outer; + } + if let Some(a) = find_by_name!(self.0, name, flags, iter) { + if self.is_missing_required_ok(a, matcher) { + continue 'outer; + } + } else if let Some(a) = find_by_name!(self.0, name, opts, iter) { + if self.is_missing_required_ok(a, matcher) { + continue 'outer; + } + } else if let Some(a) = find_by_name!(self.0, name, positionals, values) { + if self.is_missing_required_ok(a, matcher) { + continue 'outer; + } + } + return self.missing_required_error(matcher, None); + } + + // Validate the conditionally required args + for &(a, v, r) in &self.0.r_ifs { + if let Some(ma) = matcher.get(a) { + if matcher.get(r).is_none() && ma.vals.iter().any(|val| val == v) { + return self.missing_required_error(matcher, Some(r)); + } + } + } + Ok(()) + } + + fn validate_conflicts(&self, a: &A, matcher: &ArgMatcher) -> Option + where A: AnyArg<'a, 'b> + { + debugln!("Validator::validate_conflicts: a={:?};", a.name()); + a.blacklist().map(|bl| { + bl.iter().any(|conf| { + matcher.contains(conf) || + self.0 + .groups + .iter() + .find(|g| &g.name == conf) + .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) + }) + }) + } + + fn validate_required_unless(&self, a: &A, matcher: &ArgMatcher) -> Option + where A: AnyArg<'a, 'b> + { + debugln!("Validator::validate_required_unless: a={:?};", a.name()); + macro_rules! check { + ($how:ident, $_self:expr, $a:ident, $m:ident) => {{ + $a.required_unless().map(|ru| { + ru.iter().$how(|n| { + $m.contains(n) || { + if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { + grp.args.iter().any(|arg| $m.contains(arg)) + } else { + false + } + } + }) + }) + }}; + } + if a.is_set(ArgSettings::RequiredUnlessAll) { + check!(all, self.0, a, matcher) + } else { + check!(any, self.0, a, matcher) + } + } + + fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { + debugln!("Validator::missing_required_error: extra={:?}", extra); + let c = Colorizer { + use_stderr: true, + when: self.0.color(), + }; + let mut reqs = self.0 + .required + .iter() + .map(|&r| &*r) + .collect::>(); + if let Some(r) = extra { + reqs.push(r); + } + reqs.retain(|n| !matcher.contains(n)); + reqs.dedup(); + debugln!("Validator::missing_required_error: reqs={:#?}", reqs); + let req_args = + usage::get_required_usage_from(self.0, &reqs[..], Some(matcher), extra, true) + .iter() + .fold(String::new(), + |acc, s| acc + &format!("\n {}", c.error(s))[..]); + debugln!("Validator::missing_required_error: req_args={:#?}", req_args); + Err(Error::missing_required_argument(&*req_args, + &*usage::create_error_usage(self.0, matcher, extra), + self.0.color())) + } + + #[inline] + fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool + where A: AnyArg<'a, 'b> + { + debugln!("Validator::is_missing_required_ok: a={}", a.name()); + self.validate_conflicts(a, matcher).unwrap_or(false) || + self.validate_required_unless(a, matcher).unwrap_or(false) + } +} \ No newline at end of file diff --git a/src/args/arg.rs b/src/args/arg.rs index 53cd262f..8e3734a0 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -537,6 +537,86 @@ impl<'a, 'b> Arg<'a, 'b> { 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 + /// they will receive an [`UnknownArgument`] error. Setting an argument to `.last(true)` also + /// allows one to access this arg early using the `--` syntax. Accessing an arg early, even with + /// the `--` syntax is otherwise not possible. + /// + /// **NOTE:** This will change the usage string to look like `$ prog [FLAGS] [-- ]` if + /// `ARG` is marked as `.last(true)`. + /// + /// **NOTE:** This setting will imply [`AppSettings::DontCollapseArgsInUsage`] because failing + /// to set this can make the usage string very confusing. + /// + /// **NOTE**: This setting only applies to positional arguments, and has no affect on FLAGS / + /// OPTIONS + /// + /// **CAUTION:** Setting an argument to `.last(true)` *and* having child subcommands is not + /// recommended with the exception of *also* using [`AppSettings::ArgsNegateSubcommands`] + /// (or [`AppSettings::SubcommandsNegateReqs`] if the argument marked `.last(true)` is also + /// marked [`.required(true)`]) + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("args") + /// .last(true) + /// # ; + /// ``` + /// + /// Setting [`Arg::last(true)`] ensures the arg has the highest [index] of all positional args + /// and requires that the `--` syntax be used to access it early. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("first")) + /// .arg(Arg::with_name("second")) + /// .arg(Arg::with_name("third").last(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "one", "--", "three" + /// ]); + /// + /// assert!(res.is_ok()); + /// let m = res.unwrap(); + /// assert_eq!(m.value_of("third"), Some("three")); + /// assert!(m.value_of("second").is_none()); + /// ``` + /// + /// Even if the positional argument marked `.last(true)` is the only argument left to parse, + /// failing to use the `--` syntax results in an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("first")) + /// .arg(Arg::with_name("second")) + /// .arg(Arg::with_name("third").last(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "one", "two", "three" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [`Arg::last(true)`]: ./struct.Arg.html#method.last + /// [index]: ./struct.Arg.html#method.index + /// [`AppSettings::DontCollapseArgsInUsage`]: ./enum.AppSettings.html#variant.DontCollapseArgsInUsage + /// [`AppSettings::ArgsNegateSubcommands`]: ./enum.AppSettings.html#variant.ArgsNegateSubcommands + /// [`AppSettings::SubcommandsNegateReqs`]: ./enum.AppSettings.html#variant.SubcommandsNegateReqs + /// [`.required(true)`]: ./struct.Arg.html#method.required + /// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument + pub fn last(self, l: bool) -> Self { + if l { + self.set(ArgSettings::Last) + } else { + self.unset(ArgSettings::Last) + } + } + /// Sets whether or not the argument is required by default. Required by default means it is /// required, when no other conflicting rules have been evaluated. Conflicting rules take /// precedence over being required. **Default:** `false` @@ -3247,3 +3327,9 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { } } } + +impl<'n, 'e> PartialEq for Arg<'n, 'e> { + fn eq(&self, other: &Arg<'n, 'e>) -> bool { + self.b == other.b + } +} \ No newline at end of file diff --git a/src/args/arg_builder/base.rs b/src/args/arg_builder/base.rs index 1b5e534c..8b566e38 100644 --- a/src/args/arg_builder/base.rs +++ b/src/args/arg_builder/base.rs @@ -26,3 +26,9 @@ impl<'n, 'e> Base<'n, 'e> { impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Base<'n, 'e> { fn from(a: &'z Arg<'n, 'e>) -> Self { a.b.clone() } } + +impl<'n, 'e> PartialEq for Base<'n, 'e> { + fn eq(&self, other: &Base<'n, 'e>) -> bool { + self.name == other.name + } +} \ No newline at end of file diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 6dc37243..d1c16690 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -105,6 +105,12 @@ impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> { fn disp_ord(&self) -> usize { self.s.disp_ord } } +impl<'n, 'e> PartialEq for FlagBuilder<'n, 'e> { + fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool { + self.b == other.b + } +} + #[cfg(test)] mod test { use args::settings::ArgSettings; diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 75f14a73..91c2ee6f 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -149,6 +149,12 @@ impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> { fn disp_ord(&self) -> usize { self.s.disp_ord } } +impl<'n, 'e> PartialEq for OptBuilder<'n, 'e> { + fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool { + self.b == other.b + } +} + #[cfg(test)] mod test { use args::settings::ArgSettings; diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 73fde4c1..ddbd8964 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -138,6 +138,12 @@ impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> { fn disp_ord(&self) -> usize { self.index as usize } } +impl<'n, 'e> PartialEq for PosBuilder<'n, 'e> { + fn eq(&self, other: &PosBuilder<'n, 'e>) -> bool { + self.b == other.b + } +} + #[cfg(test)] mod test { use args::settings::ArgSettings; diff --git a/src/args/arg_matcher.rs b/src/args/arg_matcher.rs index 3052acd9..12e58fc1 100644 --- a/src/args/arg_matcher.rs +++ b/src/args/arg_matcher.rs @@ -4,9 +4,6 @@ use std::ffi::OsStr; use std::ops::Deref; use std::mem; -// Third Party -use vec_map::VecMap; - // Internal use args::{ArgMatches, MatchedArg, SubCommand}; use args::AnyArg; @@ -25,7 +22,7 @@ impl<'a> ArgMatcher<'a> { pub fn propagate(&mut self, arg: &'a str) { debugln!("ArgMatcher::propagate: arg={}", arg); - let vals: VecMap<_> = if let Some(ma) = self.get(arg) { + let vals: Vec<_> = if let Some(ma) = self.get(arg) { ma.vals.clone() } else { debugln!("ArgMatcher::propagate: arg wasn't used"); @@ -36,15 +33,11 @@ impl<'a> ArgMatcher<'a> { let sma = (*sc).matches.args.entry(arg).or_insert_with(|| { let mut gma = MatchedArg::new(); gma.occurs += 1; - for (i, v) in &vals { - gma.vals.insert(i, v.clone()); - } + gma.vals = vals.clone(); gma }); if sma.vals.is_empty() { - for (i, v) in &vals { - sma.vals.insert(i, v.clone()); - } + sma.vals = vals.clone(); } } let mut am = ArgMatcher(mem::replace(&mut sc.matches, ArgMatches::new())); @@ -105,10 +98,10 @@ impl<'a> ArgMatcher<'a> { pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) { let ma = self.entry(arg).or_insert(MatchedArg { occurs: 0, - vals: VecMap::new(), + vals: Vec::with_capacity(1), }); - let len = ma.vals.len() + 1; - ma.vals.insert(len, val.to_owned()); + // let len = ma.vals.len() + 1; + ma.vals.push(val.to_owned()); } pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool diff --git a/src/args/arg_matches.rs b/src/args/arg_matches.rs index 12364fdc..aec7f4da 100644 --- a/src/args/arg_matches.rs +++ b/src/args/arg_matches.rs @@ -3,10 +3,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::iter::Map; -use std::slice; - -// Third Party -use vec_map; +use std::slice::Iter; // Internal use INVALID_UTF8; @@ -113,7 +110,7 @@ impl<'a> ArgMatches<'a> { /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html pub fn value_of>(&self, name: S) -> Option<&str> { if let Some(arg) = self.args.get(name.as_ref()) { - if let Some(v) = arg.vals.values().nth(0) { + if let Some(v) = arg.vals.get(0) { return Some(v.to_str().expect(INVALID_UTF8)); } } @@ -145,7 +142,7 @@ impl<'a> ArgMatches<'a> { /// [`Arg::values_of_lossy`]: ./struct.ArgMatches.html#method.values_of_lossy pub fn value_of_lossy>(&'a self, name: S) -> Option> { if let Some(arg) = self.args.get(name.as_ref()) { - if let Some(v) = arg.vals.values().nth(0) { + if let Some(v) = arg.vals.get(0) { return Some(v.to_string_lossy()); } } @@ -182,7 +179,7 @@ impl<'a> ArgMatches<'a> { pub fn value_of_os>(&self, name: S) -> Option<&OsStr> { self.args .get(name.as_ref()) - .map_or(None, |arg| arg.vals.values().nth(0).map(|v| v.as_os_str())) + .map_or(None, |arg| arg.vals.get(0).map(|v| v.as_os_str())) } /// Gets a [`Values`] struct which implements [`Iterator`] for values of a specific argument @@ -214,7 +211,7 @@ impl<'a> ArgMatches<'a> { if let Some(arg) = self.args.get(name.as_ref()) { fn to_str_slice(o: &OsString) -> &str { o.to_str().expect(INVALID_UTF8) } let to_str_slice: fn(&OsString) -> &str = to_str_slice; // coerce to fn pointer - return Some(Values { iter: arg.vals.values().map(to_str_slice) }); + return Some(Values { iter: arg.vals.iter().map(to_str_slice) }); } None } @@ -246,7 +243,7 @@ impl<'a> ArgMatches<'a> { pub fn values_of_lossy>(&'a self, name: S) -> Option> { if let Some(arg) = self.args.get(name.as_ref()) { return Some(arg.vals - .values() + .iter() .map(|v| v.to_string_lossy().into_owned()) .collect()); } @@ -288,7 +285,7 @@ impl<'a> ArgMatches<'a> { fn to_str_slice(o: &OsString) -> &OsStr { &*o } let to_str_slice: fn(&'a OsString) -> &'a OsStr = to_str_slice; // coerce to fn pointer if let Some(arg) = self.args.get(name.as_ref()) { - return Some(OsValues { iter: arg.vals.values().map(to_str_slice) }); + return Some(OsValues { iter: arg.vals.iter().map(to_str_slice) }); } None } @@ -554,7 +551,7 @@ impl<'a> ArgMatches<'a> { #[derive(Clone)] #[allow(missing_debug_implementations)] pub struct Values<'a> { - iter: Map, fn(&'a OsString) -> &'a str>, + iter: Map, fn(&'a OsString) -> &'a str>, } impl<'a> Iterator for Values<'a> { @@ -570,51 +567,6 @@ impl<'a> DoubleEndedIterator for Values<'a> { impl<'a> ExactSizeIterator for Values<'a> {} -/// An iterator over the key-value pairs of a map. -#[derive(Clone)] -pub struct Iter<'a, V: 'a> { - front: usize, - back: usize, - iter: slice::Iter<'a, Option>, -} - -impl<'a, V> Iterator for Iter<'a, V> { - type Item = &'a V; - - #[inline] - fn next(&mut self) -> Option<&'a V> { - while self.front < self.back { - if let Some(elem) = self.iter.next() { - if let Some(x) = elem.as_ref() { - self.front += 1; - return Some(x); - } - } - self.front += 1; - } - None - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { (0, Some(self.back - self.front)) } -} - -impl<'a, V> DoubleEndedIterator for Iter<'a, V> { - #[inline] - fn next_back(&mut self) -> Option<&'a V> { - while self.front < self.back { - if let Some(elem) = self.iter.next_back() { - if let Some(x) = elem.as_ref() { - self.back -= 1; - return Some(x); - } - } - self.back -= 1; - } - None - } -} - /// An iterator for getting multiple values out of an argument via the [`ArgMatches::values_of_os`] /// method. Usage of this iterator allows values which contain invalid UTF-8 code points unlike /// [`Values`]. @@ -639,7 +591,7 @@ impl<'a, V> DoubleEndedIterator for Iter<'a, V> { #[derive(Clone)] #[allow(missing_debug_implementations)] pub struct OsValues<'a> { - iter: Map, fn(&'a OsString) -> &'a OsStr>, + iter: Map, fn(&'a OsString) -> &'a OsStr>, } impl<'a> Iterator for OsValues<'a> { diff --git a/src/args/group.rs b/src/args/group.rs index d6cb9eb4..4ff2638a 100644 --- a/src/args/group.rs +++ b/src/args/group.rs @@ -152,6 +152,7 @@ impl<'a> ArgGroup<'a> { /// assert!(m.is_present("flag")); /// ``` /// [argument]: ./struct.Arg.html + #[cfg_attr(feature = "lints", allow(should_assert_eq))] pub fn arg(mut self, n: &'a str) -> Self { assert!(self.name != n, "ArgGroup '{}' can not have same name as arg inside it", diff --git a/src/args/matched_arg.rs b/src/args/matched_arg.rs index e852f26c..9a73af9d 100644 --- a/src/args/matched_arg.rs +++ b/src/args/matched_arg.rs @@ -1,23 +1,20 @@ // Std use std::ffi::OsString; -// Third Party -use vec_map::VecMap; - #[doc(hidden)] #[derive(Debug, Clone)] pub struct MatchedArg { #[doc(hidden)] pub occurs: u64, #[doc(hidden)] - pub vals: VecMap, + pub vals: Vec, } impl Default for MatchedArg { fn default() -> Self { MatchedArg { occurs: 1, - vals: VecMap::new(), + vals: Vec::with_capacity(1), } } } diff --git a/src/args/settings.rs b/src/args/settings.rs index d2a85561..82d36f57 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -4,20 +4,21 @@ use std::str::FromStr; bitflags! { flags Flags: u16 { - const REQUIRED = 0b00000000000001, - const MULTIPLE = 0b00000000000010, - const EMPTY_VALS = 0b00000000000100, - const GLOBAL = 0b00000000001000, - const HIDDEN = 0b00000000010000, - const TAKES_VAL = 0b00000000100000, - const USE_DELIM = 0b00000001000000, - const NEXT_LINE_HELP = 0b00000010000000, - const R_UNLESS_ALL = 0b00000100000000, - const REQ_DELIM = 0b00001000000000, - const DELIM_NOT_SET = 0b00010000000000, - const HIDE_POS_VALS = 0b00100000000000, - const ALLOW_TAC_VALS = 0b01000000000000, - const REQUIRE_EQUALS = 0b10000000000000, + const REQUIRED = 1 << 0, + const MULTIPLE = 1 << 1, + const EMPTY_VALS = 1 << 2, + const GLOBAL = 1 << 3, + const HIDDEN = 1 << 4, + const TAKES_VAL = 1 << 5, + const USE_DELIM = 1 << 6, + const NEXT_LINE_HELP = 1 << 7, + const R_UNLESS_ALL = 1 << 8, + const REQ_DELIM = 1 << 9, + const DELIM_NOT_SET = 1 << 10, + const HIDE_POS_VALS = 1 << 11, + const ALLOW_TAC_VALS = 1 << 12, + const REQUIRE_EQUALS = 1 << 13, + const LAST = 1 << 14, } } @@ -42,7 +43,8 @@ impl ArgFlags { ValueDelimiterNotSet => DELIM_NOT_SET, HidePossibleValues => HIDE_POS_VALS, AllowLeadingHyphen => ALLOW_TAC_VALS, - RequireEquals => REQUIRE_EQUALS + RequireEquals => REQUIRE_EQUALS, + Last => LAST } } @@ -82,6 +84,9 @@ pub enum ArgSettings { AllowLeadingHyphen, /// Require options use `--option=val` syntax RequireEquals, + /// Specifies that the arg is the last positional argument and may be accessed early via `--` + /// syntax + Last, #[doc(hidden)] RequiredUnlessAll, #[doc(hidden)] @@ -106,6 +111,7 @@ impl FromStr for ArgSettings { "hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues), "allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen), "requireequals" => Ok(ArgSettings::RequireEquals), + "last" => Ok(ArgSettings::Last), _ => Err("unknown ArgSetting, cannot convert from str".to_owned()), } } @@ -145,6 +151,8 @@ mod test { ArgSettings::ValueDelimiterNotSet); assert_eq!("requireequals".parse::().unwrap(), ArgSettings::RequireEquals); + assert_eq!("last".parse::().unwrap(), + ArgSettings::Last); assert!("hahahaha".parse::().is_err()); } } diff --git a/src/lib.rs b/src/lib.rs index c39f232e..256c1420 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -527,6 +527,7 @@ #![cfg_attr(feature = "lints", deny(warnings))] #![cfg_attr(feature = "lints", allow(cyclomatic_complexity))] #![cfg_attr(feature = "lints", allow(doc_markdown))] +#![cfg_attr(feature = "lints", allow(explicit_iter_loop))] #[cfg(feature = "suggestions")] extern crate strsim; diff --git a/src/macros.rs b/src/macros.rs index 2ce6d679..6fc7763a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -847,7 +847,7 @@ macro_rules! vec_remove_all { }; } macro_rules! find_from { - ($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{ + ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ let mut ret = None; for k in $matcher.arg_names() { if let Some(f) = find_by_name!($_self, &k, flags, iter) { @@ -877,7 +877,7 @@ macro_rules! find_from { } macro_rules! find_name_from { - ($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{ + ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ let mut ret = None; for k in $matcher.arg_names() { if let Some(f) = find_by_name!($_self, &k, flags, iter) { @@ -908,8 +908,8 @@ macro_rules! find_name_from { // Finds an arg by name macro_rules! find_by_name { - ($_self:ident, $name:expr, $what:ident, $how:ident) => { - $_self.$what.$how().find(|o| &o.b.name == $name) + ($p:expr, $name:expr, $what:ident, $how:ident) => { + $p.$what.$how().find(|o| &o.b.name == $name) } } @@ -989,7 +989,7 @@ macro_rules! find_subcmd { $_self.subcommands .iter() .find(|s| { - s.p.meta.name == $sc || + &*s.p.meta.name == $sc || (s.p.meta.aliases.is_some() && s.p .meta @@ -1029,12 +1029,18 @@ macro_rules! _shorts_longs { macro_rules! arg_names { ($_self:ident) => {{ - _names!($_self) + _names!(@args $_self) + }}; +} + +macro_rules! sc_names { + ($_self:ident) => {{ + _names!(@sc $_self) }}; } macro_rules! _names { - ($_self:ident) => {{ + (@args $_self:ident) => {{ $_self.flags .iter() .map(|f| &*f.b.name) @@ -1043,4 +1049,14 @@ macro_rules! _names { .chain($_self.positionals.values() .map(|p| &*p.b.name))) }}; + (@sc $_self:ident) => {{ + $_self.subcommands + .iter() + .map(|s| &*s.p.meta.name) + .chain($_self.subcommands + .iter() + .filter(|s| s.p.meta.aliases.is_some()) + .flat_map(|s| s.p.meta.aliases.as_ref().unwrap().iter().map(|&(n, _)| n))) + + }} } diff --git a/src/suggestions.rs b/src/suggestions.rs index d11c6646..5c7cade5 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -11,7 +11,7 @@ use fmt::Format; /// `Some("foo")`, whereas "blark" would yield `None`. #[cfg(feature = "suggestions")] #[cfg_attr(feature = "lints", allow(needless_lifetimes))] -pub fn did_you_mean<'a, T, I>(v: &str, possible_values: I) -> Option<&'a str> +pub fn did_you_mean<'a, T: ?Sized, I>(v: &str, possible_values: I) -> Option<&'a str> where T: AsRef + 'a, I: IntoIterator { @@ -31,7 +31,7 @@ pub fn did_you_mean<'a, T, I>(v: &str, possible_values: I) -> Option<&'a str> } #[cfg(not(feature = "suggestions"))] -pub fn did_you_mean<'a, T, I>(_: &str, _: I) -> Option<&'a str> +pub fn did_you_mean<'a, T: ?Sized, I>(_: &str, _: I) -> Option<&'a str> where T: AsRef + 'a, I: IntoIterator { @@ -68,6 +68,7 @@ pub fn did_you_mean_suffix<'z, T, I>(arg: &str, } /// A helper to determine message formatting +#[derive(Copy, Clone, Debug)] pub enum DidYouMeanMessageStyle { /// Suggested value is a long flag LongFlag, diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 17aa8bd8..644a863b 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -110,6 +110,113 @@ fn arg_required_else_help() { assert_eq!(err.kind, ErrorKind::MissingArgumentOrSubcommand); } +#[cfg(not(feature = "suggestions"))] +#[test] +fn infer_subcommands_fail_no_args() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from_safe(vec![ + "prog", "te" + ]); + assert!(m.is_err(), "{:#?}", m.unwrap()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand); +} + +#[cfg(feature = "suggestions")] +#[test] +fn infer_subcommands_fail_no_args() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from_safe(vec![ + "prog", "te" + ]); + assert!(m.is_err(), "{:#?}", m.unwrap()); + assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidSubcommand); +} + +#[test] +fn infer_subcommands_fail_with_args() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .arg(Arg::with_name("some")) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from_safe(vec![ + "prog", "t" + ]); + assert!(m.is_ok(), "{:?}", m.unwrap_err().kind); + assert_eq!(m.unwrap().value_of("some"), Some("t")); +} + +#[test] +fn infer_subcommands_fail_with_args2() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .arg(Arg::with_name("some")) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from_safe(vec![ + "prog", "te" + ]); + assert!(m.is_ok(), "{:?}", m.unwrap_err().kind); + assert_eq!(m.unwrap().value_of("some"), Some("te")); +} + +#[test] +fn infer_subcommands_pass() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .subcommand(SubCommand::with_name("test")) + .get_matches_from(vec![ + "prog", "te" + ]); + assert_eq!(m.subcommand_name(), Some("test")); +} + +#[test] +fn infer_subcommands_pass_close() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from(vec![ + "prog", "tes" + ]); + assert_eq!(m.subcommand_name(), Some("test")); +} + +#[cfg(feature = "suggestions")] +#[test] +fn infer_subcommands_fail_suggestions() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from_safe(vec![ + "prog", "temps" + ]); + assert!(m.is_err(), "{:#?}", m.unwrap()); + assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidSubcommand); +} + +#[cfg(not(feature = "suggestions"))] +#[test] +fn infer_subcommands_fail_suggestions() { + let m = App::new("prog") + .setting(AppSettings::InferSubcommands) + .subcommand(SubCommand::with_name("test")) + .subcommand(SubCommand::with_name("temp")) + .get_matches_from_safe(vec![ + "prog", "temps" + ]); + assert!(m.is_err(), "{:#?}", m.unwrap()); + assert_eq!(m.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand); +} + #[test] fn no_bin_name() { let result = App::new("arg_required") @@ -452,7 +559,9 @@ fn propagate_vals_down() { .setting(AppSettings::PropagateGlobalValuesDown) .arg(Arg::from_usage("[cmd] 'command to run'").global(true)) .subcommand(SubCommand::with_name("foo")) - .get_matches_from(vec!["myprog", "set", "foo"]); + .get_matches_from_safe(vec!["myprog", "set", "foo"]); + assert!(m.is_ok(), "{:?}", m.unwrap_err().kind); + let m = m.unwrap(); 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")); @@ -464,7 +573,9 @@ fn allow_missing_positional() { .setting(AppSettings::AllowMissingPositional) .arg(Arg::from_usage("[src] 'some file'").default_value("src")) .arg_from_usage(" 'some file'") - .get_matches_from(vec!["test", "file"]); + .get_matches_from_safe(vec!["test", "file"]); + assert!(m.is_ok(), "{:?}", m.unwrap_err().kind); + let m = m.unwrap(); 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/help.rs b/tests/help.rs index 63266442..cd49ecf9 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -36,6 +36,47 @@ SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) subcmd tests subcommands"; +static SC_NEGATES_REQS: &'static str = "prog 1.0 + +USAGE: + prog --opt [PATH] + prog [PATH] + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -o, --opt tests options + +ARGS: + + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + test"; + +static ARGS_NEGATE_SC: &'static str = "prog 1.0 + +USAGE: + prog [FLAGS] [OPTIONS] [PATH] + prog + +FLAGS: + -f, --flag testing flags + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -o, --opt tests options + +ARGS: + + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + test"; + static AFTER_HELP: &'static str = "some text that comes before the help clap-test v1.4.8 @@ -50,6 +91,19 @@ FLAGS: some text that comes after the help"; +static HIDDEN_ARGS: &'static str = "prog 1.0 + +USAGE: + prog [FLAGS] [OPTIONS] + +FLAGS: + -f, --flag testing flags + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -o, --opt tests options"; + static SC_HELP: &'static str = "clap-test-subcmd 0.1 Kevin K. tests subcommands @@ -236,6 +290,72 @@ FLAGS: -H, --help Print help information -v, --version Print version information"; +static LAST_ARG: &'static str = "last 0.1 + +USAGE: + last [CORPUS] [-- ...] + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + some + some + ... some"; + +static LAST_ARG_SC: &'static str = "last 0.1 + +USAGE: + last [CORPUS] [-- ...] + last + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + some + some + ... some + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + test some"; + +static LAST_ARG_REQ: &'static str = "last 0.1 + +USAGE: + last [CORPUS] -- ... + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + some + some + ... some"; + +static LAST_ARG_REQ_SC: &'static str = "last 0.1 + +USAGE: + last [CORPUS] -- ... + last + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + some + some + ... some + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + test some"; + #[test] fn help_short() { let m = App::new("test") @@ -531,6 +651,40 @@ fn issue_760() { .takes_value(true)); assert!(test::compare_output(app, "ctest --help", ISSUE_760, false)); } + +#[test] +fn hidden_args() { + let app = App::new("prog") + .version("1.0") + .args_from_usage("-f, --flag 'testing flags' + -o, --opt [FILE] 'tests options'") + .arg(Arg::with_name("pos").hidden(true)); + assert!(test::compare_output(app, "prog --help", HIDDEN_ARGS, false)); +} + +#[test] +fn sc_negates_reqs() { + let app = App::new("prog") + .version("1.0") + .setting(AppSettings::SubcommandsNegateReqs) + .arg_from_usage("-o, --opt 'tests options'") + .arg(Arg::with_name("PATH")) + .subcommand(SubCommand::with_name("test")); + assert!(test::compare_output(app, "prog --help", SC_NEGATES_REQS, false)); +} + +#[test] +fn args_negate_sc() { + let app = App::new("prog") + .version("1.0") + .setting(AppSettings::ArgsNegateSubcommands) + .args_from_usage("-f, --flag 'testing flags' + -o, --opt [FILE] 'tests options'") + .arg(Arg::with_name("PATH")) + .subcommand(SubCommand::with_name("test")); + assert!(test::compare_output(app, "prog --help", ARGS_NEGATE_SC, false)); +} + #[test] fn issue_777_wrap_all_things() { let app = App::new("A app with a crazy very long long long name hahaha") @@ -553,3 +707,47 @@ fn customize_version_and_help() { .version_message("Print version information"); assert!(test::compare_output(app, "customize --help", CUSTOM_VERSION_AND_HELP, false)); } + +#[test] +fn last_arg_mult_usage() { + let app = App::new("last") + .version("0.1") + .arg(Arg::with_name("TARGET").required(true).help("some")) + .arg(Arg::with_name("CORPUS").help("some")) + .arg(Arg::with_name("ARGS").multiple(true).last(true).help("some")); + assert!(test::compare_output(app, "last --help", LAST_ARG, false)); +} + +#[test] +fn last_arg_mult_usage_req() { + let app = App::new("last") + .version("0.1") + .arg(Arg::with_name("TARGET").required(true).help("some")) + .arg(Arg::with_name("CORPUS").help("some")) + .arg(Arg::with_name("ARGS").multiple(true).last(true).required(true).help("some")); + assert!(test::compare_output(app, "last --help", LAST_ARG_REQ, false)); +} + +#[test] +fn last_arg_mult_usage_req_with_sc() { + let app = App::new("last") + .version("0.1") + .setting(AppSettings::SubcommandsNegateReqs) + .arg(Arg::with_name("TARGET").required(true).help("some")) + .arg(Arg::with_name("CORPUS").help("some")) + .arg(Arg::with_name("ARGS").multiple(true).last(true).required(true).help("some")) + .subcommand(SubCommand::with_name("test").about("some")); + assert!(test::compare_output(app, "last --help", LAST_ARG_REQ_SC, false)); +} + +#[test] +fn last_arg_mult_usage_with_sc() { + let app = App::new("last") + .version("0.1") + .setting(AppSettings::ArgsNegateSubcommands) + .arg(Arg::with_name("TARGET").required(true).help("some")) + .arg(Arg::with_name("CORPUS").help("some")) + .arg(Arg::with_name("ARGS").multiple(true).last(true).help("some")) + .subcommand(SubCommand::with_name("test").about("some")); + assert!(test::compare_output(app, "last --help", LAST_ARG_SC, false)); +} \ No newline at end of file diff --git a/tests/hidden_args.rs b/tests/hidden_args.rs index f73c4986..cbc9225b 100644 --- a/tests/hidden_args.rs +++ b/tests/hidden_args.rs @@ -5,7 +5,7 @@ use clap::{App, Arg}; include!("../clap-test.rs"); -static HIDDEN_ARGS: &'static str = "test 1.3 +static HIDDEN_ARGS: &'static str = "test 1.4 Kevin K. tests stuff @@ -25,9 +25,10 @@ fn hidden_args() { let app = App::new("test") .author("Kevin K.") .about("tests stuff") - .version("1.3") + .version("1.4") .args(&[Arg::from_usage("-f, --flag 'some flag'").hidden(true), Arg::from_usage("-F, --flag2 'some other flag'"), - Arg::from_usage("--option [opt] 'some option'")]); + Arg::from_usage("--option [opt] 'some option'"), + Arg::with_name("DUMMY").required(false).hidden(true)]); assert!(test::compare_output(app, "test --help", HIDDEN_ARGS, false)); } diff --git a/tests/positionals.rs b/tests/positionals.rs index e8a0cd7b..cf7cf468 100644 --- a/tests/positionals.rs +++ b/tests/positionals.rs @@ -180,7 +180,7 @@ fn multiple_positional_one_required_usage_string() { .arg_from_usage(" 'some file'") .arg_from_usage("[FILES]... 'some file'") .get_matches_from(vec!["test", "file"]); - assert_eq!(m.usage(), "USAGE:\n test [ARGS]"); + assert_eq!(m.usage(), "USAGE:\n test [FILES]..."); } #[test] @@ -211,3 +211,26 @@ fn missing_required_2() { assert!(r.is_err()); assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument); } + +#[test] +fn last_positional() { + let r = App::new("test") + .arg_from_usage(" 'some target'") + .arg_from_usage("[CORPUS] 'some corpus'") + .arg(Arg::from_usage("[ARGS]... 'some file'").last(true)) + .get_matches_from_safe(vec!["test", "tgt", "--", "arg"]); + assert!(r.is_ok()); + let m = r.unwrap(); + assert_eq!(m.values_of("ARGS").unwrap().collect::>(), &["arg"]); +} + +#[test] +fn last_positional_no_double_dash() { + let r = App::new("test") + .arg_from_usage(" 'some target'") + .arg_from_usage("[CORPUS] 'some corpus'") + .arg(Arg::from_usage("[ARGS]... 'some file'").last(true)) + .get_matches_from_safe(vec!["test", "tgt", "crp", "arg"]); + assert!(r.is_err()); + assert_eq!(r.unwrap_err().kind, ErrorKind::UnknownArgument); +} \ No newline at end of file