Auto merge of #893 - kbknapp:issues-863,perf-imp2,883,882,871, r=kbknapp

Issues 863,perf imp2,883,882,871
This commit is contained in:
Homu 2017-03-12 03:31:19 +09:00
commit 814b12644e
28 changed files with 2106 additions and 1286 deletions

View file

@ -1,3 +1,46 @@
<a name="v2.21.0"></a>
## 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))
<a name="v2.20.5"></a> <a name="v2.20.5"></a>
### v2.20.5 (2017-02-18) ### v2.20.5 (2017-02-18)

View file

@ -22,9 +22,9 @@ unicode-width = "0.1.4"
unicode-segmentation = "1.0.1" unicode-segmentation = "1.0.1"
strsim = { version = "0.6.0", optional = true } strsim = { version = "0.6.0", optional = true }
ansi_term = { version = "0.9.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 } 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 } atty = { version = "0.2.2", optional = true }
[dev-dependencies] [dev-dependencies]
@ -65,7 +65,7 @@ debug = true
rpath = false rpath = false
lto = false lto = false
debug-assertions = true debug-assertions = true
codegen-units = 2 codegen-units = 4
[profile.bench] [profile.bench]
opt-level = 3 opt-level = 3

137
README.md
View file

@ -45,131 +45,24 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
## What's New ## 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. * 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 * 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] <required>` 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 <files>... <target>`)
* 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 `<cmd> help help` and displaying incorrect help message
* Subcommand help messages requested via `<cmd> help <sub>` now correctly match `<cmd> <sub> --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) For full details, see [CHANGELOG.md](https://github.com/kbknapp/clap-rs/blob/master/CHANGELOG.md)

View file

@ -3,7 +3,7 @@
extern crate clap; extern crate clap;
extern crate test; extern crate test;
use clap::App; use clap::{App, Arg};
use test::Bencher; use test::Bencher;
@ -25,6 +25,69 @@ fn build_app(b: &mut Bencher) {
b.iter(|| create_app!()); 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 <FILE> '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 <FILE> '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] #[bench]
fn parse_clean(b: &mut Bencher) { fn parse_clean(b: &mut Bencher) {
b.iter(|| create_app!().get_matches_from(vec![""])); b.iter(|| create_app!().get_matches_from(vec![""]));

View file

@ -11,6 +11,7 @@ use app::parser::Parser;
use args::{AnyArg, ArgSettings, DispOrder}; use args::{AnyArg, ArgSettings, DispOrder};
use errors::{Error, Result as ClapResult}; use errors::{Error, Result as ClapResult};
use fmt::{Format, Colorizer}; use fmt::{Format, Colorizer};
use app::usage;
// Third Party // Third Party
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -195,9 +196,7 @@ impl<'a> Help<'a> {
if arg.longest_filter() { if arg.longest_filter() {
self.longest = cmp::max(self.longest, arg.to_string().len()); 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; let mut first = true;
for arg in arg_v { for arg in arg_v {
@ -541,7 +540,7 @@ impl<'a> Help<'a> {
pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> {
debugln!("Help::write_all_args;"); debugln!("Help::write_all_args;");
let flags = parser.has_flags(); 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 opts = parser.has_opts();
let subcmds = parser.has_subcommands(); let subcmds = parser.has_subcommands();
@ -684,7 +683,7 @@ impl<'a> Help<'a> {
try!(write!(self.writer, try!(write!(self.writer,
"\n{}{}\n\n", "\n{}{}\n\n",
TAB, TAB,
parser.create_usage_no_title(&[]))); usage::create_help_usage(parser, true)));
let flags = parser.has_flags(); let flags = parser.has_flags();
let pos = parser.has_positionals(); let pos = parser.has_positionals();
@ -881,7 +880,7 @@ impl<'a> Help<'a> {
parser.meta.about.unwrap_or("unknown about"))); parser.meta.about.unwrap_or("unknown about")));
} }
b"usage" => { b"usage" => {
try!(write!(self.writer, "{}", parser.create_usage_no_title(&[]))); try!(write!(self.writer, "{}", usage::create_help_usage(parser, true)));
} }
b"all-args" => { b"all-args" => {
try!(self.write_all_args(&parser)); try!(self.write_all_args(&parser));

View file

@ -148,7 +148,12 @@ macro_rules! parse_positional {
$matcher.inc_occurrence_of($p.b.name); $matcher.inc_occurrence_of($p.b.name);
let _ = $_self.groups_for_arg($p.b.name) let _ = $_self.groups_for_arg($p.b.name)
.and_then(|vec| Some($matcher.inc_occurrences_of(&*vec))); .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 // Only increment the positional counter if it doesn't allow multiples
if !$p.b.settings.is_set(ArgSettings::Multiple) { if !$p.b.settings.is_set(ArgSettings::Multiple) {
$pos_counter += 1; $pos_counter += 1;

View file

@ -4,6 +4,8 @@ mod macros;
pub mod parser; pub mod parser;
mod meta; mod meta;
mod help; mod help;
mod validator;
mod usage;
// Std // Std
use std::env; use std::env;
@ -405,7 +407,7 @@ impl<'a, 'b> App<'a, 'b> {
/// # ; /// # ;
/// ``` /// ```
pub fn help_message<S: Into<&'a str>>(mut self, s: S) -> Self { pub fn help_message<S: Into<&'a str>>(mut self, s: S) -> Self {
self.p.help_message(s.into()); self.p.help_message = Some(s.into());
self self
} }
@ -423,7 +425,7 @@ impl<'a, 'b> App<'a, 'b> {
/// # ; /// # ;
/// ``` /// ```
pub fn version_message<S: Into<&'a str>>(mut self, s: S) -> Self { pub fn version_message<S: Into<&'a str>>(mut self, s: S) -> Self {
self.p.version_message(s.into()); self.p.version_message = Some(s.into());
self self
} }

File diff suppressed because it is too large Load diff

View file

@ -5,44 +5,46 @@ use std::ops::BitOr;
bitflags! { bitflags! {
flags Flags: u64 { flags Flags: u64 {
const SC_NEGATE_REQS = 0b00000000000000000000000000000000000001, const SC_NEGATE_REQS = 1 << 0,
const SC_REQUIRED = 0b00000000000000000000000000000000000010, const SC_REQUIRED = 1 << 1,
const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000000000100, const A_REQUIRED_ELSE_HELP = 1 << 2,
const GLOBAL_VERSION = 0b00000000000000000000000000000000001000, const GLOBAL_VERSION = 1 << 3,
const VERSIONLESS_SC = 0b00000000000000000000000000000000010000, const VERSIONLESS_SC = 1 << 4,
const UNIFIED_HELP = 0b00000000000000000000000000000000100000, const UNIFIED_HELP = 1 << 5,
const WAIT_ON_ERROR = 0b00000000000000000000000000000001000000, const WAIT_ON_ERROR = 1 << 6,
const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000000000010000000, const SC_REQUIRED_ELSE_HELP= 1 << 7,
const NEEDS_LONG_HELP = 0b00000000000000000000000000000100000000, const NEEDS_LONG_HELP = 1 << 8,
const NEEDS_LONG_VERSION = 0b00000000000000000000000000001000000000, const NEEDS_LONG_VERSION = 1 << 9,
const NEEDS_SC_HELP = 0b00000000000000000000000000010000000000, const NEEDS_SC_HELP = 1 << 10,
const DISABLE_VERSION = 0b00000000000000000000000000100000000000, const DISABLE_VERSION = 1 << 11,
const HIDDEN = 0b00000000000000000000000001000000000000, const HIDDEN = 1 << 12,
const TRAILING_VARARG = 0b00000000000000000000000010000000000000, const TRAILING_VARARG = 1 << 13,
const NO_BIN_NAME = 0b00000000000000000000000100000000000000, const NO_BIN_NAME = 1 << 14,
const ALLOW_UNK_SC = 0b00000000000000000000001000000000000000, const ALLOW_UNK_SC = 1 << 15,
const UTF8_STRICT = 0b00000000000000000000010000000000000000, const UTF8_STRICT = 1 << 16,
const UTF8_NONE = 0b00000000000000000000100000000000000000, const UTF8_NONE = 1 << 17,
const LEADING_HYPHEN = 0b00000000000000000001000000000000000000, const LEADING_HYPHEN = 1 << 18,
const NO_POS_VALUES = 0b00000000000000000010000000000000000000, const NO_POS_VALUES = 1 << 19,
const NEXT_LINE_HELP = 0b00000000000000000100000000000000000000, const NEXT_LINE_HELP = 1 << 20,
const DERIVE_DISP_ORDER = 0b00000000000000001000000000000000000000, const DERIVE_DISP_ORDER = 1 << 21,
const COLORED_HELP = 0b00000000000000010000000000000000000000, const COLORED_HELP = 1 << 22,
const COLOR_ALWAYS = 0b00000000000000100000000000000000000000, const COLOR_ALWAYS = 1 << 23,
const COLOR_AUTO = 0b00000000000001000000000000000000000000, const COLOR_AUTO = 1 << 24,
const COLOR_NEVER = 0b00000000000010000000000000000000000000, const COLOR_NEVER = 1 << 25,
const DONT_DELIM_TRAIL = 0b00000000000100000000000000000000000000, const DONT_DELIM_TRAIL = 1 << 26,
const ALLOW_NEG_NUMS = 0b00000000001000000000000000000000000000, const ALLOW_NEG_NUMS = 1 << 27,
const LOW_INDEX_MUL_POS = 0b00000000010000000000000000000000000000, const LOW_INDEX_MUL_POS = 1 << 28,
const DISABLE_HELP_SC = 0b00000000100000000000000000000000000000, const DISABLE_HELP_SC = 1 << 29,
const DONT_COLLAPSE_ARGS = 0b00000001000000000000000000000000000000, const DONT_COLLAPSE_ARGS = 1 << 30,
const ARGS_NEGATE_SCS = 0b00000010000000000000000000000000000000, const ARGS_NEGATE_SCS = 1 << 31,
const PROPAGATE_VALS_DOWN = 0b00000100000000000000000000000000000000, const PROPAGATE_VALS_DOWN = 1 << 32,
const ALLOW_MISSING_POS = 0b00001000000000000000000000000000000000, const ALLOW_MISSING_POS = 1 << 33,
const TRAILING_VALUES = 0b00010000000000000000000000000000000000, const TRAILING_VALUES = 1 << 34,
const VALID_NEG_NUM_FOUND = 0b00100000000000000000000000000000000000, const VALID_NEG_NUM_FOUND = 1 << 35,
const PROPOGATED = 0b01000000000000000000000000000000000000, const PROPOGATED = 1 << 36,
const VALID_ARG_FOUND = 0b10000000000000000000000000000000000000 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, TrailingValues => TRAILING_VALUES,
ValidNegNumFound => VALID_NEG_NUM_FOUND, ValidNegNumFound => VALID_NEG_NUM_FOUND,
Propogated => PROPOGATED, 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. /// This can be useful if there are many values, or they are explained elsewhere.
HidePossibleValuesInHelp, 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. /// 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 /// 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. /// one would not normally type the binary or program name for each command.
@ -827,6 +861,9 @@ pub enum AppSettings {
#[doc(hidden)] #[doc(hidden)]
ValidArgFound, ValidArgFound,
#[doc(hidden)]
ContainsLast,
} }
impl FromStr for AppSettings { impl FromStr for AppSettings {
@ -851,6 +888,7 @@ impl FromStr for AppSettings {
"globalversion" => Ok(AppSettings::GlobalVersion), "globalversion" => Ok(AppSettings::GlobalVersion),
"hidden" => Ok(AppSettings::Hidden), "hidden" => Ok(AppSettings::Hidden),
"hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp), "hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp),
"infersubcommands" => Ok(AppSettings::InferSubcommands),
"lowindexmultiplepositional" => Ok(AppSettings::LowIndexMultiplePositional), "lowindexmultiplepositional" => Ok(AppSettings::LowIndexMultiplePositional),
"nobinaryname" => Ok(AppSettings::NoBinaryName), "nobinaryname" => Ok(AppSettings::NoBinaryName),
"nextlinehelp" => Ok(AppSettings::NextLineHelp), "nextlinehelp" => Ok(AppSettings::NextLineHelp),
@ -943,6 +981,8 @@ mod test {
AppSettings::Propogated); AppSettings::Propogated);
assert_eq!("trailingvalues".parse::<AppSettings>().unwrap(), assert_eq!("trailingvalues".parse::<AppSettings>().unwrap(),
AppSettings::TrailingValues); AppSettings::TrailingValues);
assert_eq!("infersubcommands".parse::<AppSettings>().unwrap(),
AppSettings::InferSubcommands);
assert!("hahahaha".parse::<AppSettings>().is_err()); assert!("hahahaha".parse::<AppSettings>().is_err());
} }
} }

437
src/app/usage.rs Normal file
View file

@ -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(" <SUBCOMMAND>");
} else {
usage.push_str("\n ");
usage.push_str(&*name);
usage.push_str(" <SUBCOMMAND>");
}
} else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) {
usage.push_str(" <SUBCOMMAND>");
} 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(" <SUBCOMMAND>");
}
usage.shrink_to_fit();
usage
}
// Gets the `[ARGS]` tag for the usage string
fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option<String> {
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::<Vec<_>>()
.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::<Vec<_>>()
.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<String> {
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::<Vec<_>>();
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::<BTreeMap<u64, &PosBuilder>>() // 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::<BTreeMap<u64, &PosBuilder>>() // 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<String> = 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
}

454
src/app/validator.rs Normal file
View file

@ -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<String>,
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<A>(&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<A>(&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<A>(&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<A>(&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<A>(&self, a: &A, matcher: &ArgMatcher) -> Option<bool>
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<A>(&self, a: &A, matcher: &ArgMatcher) -> Option<bool>
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::<Vec<_>>();
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<A>(&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)
}
}

View file

@ -537,6 +537,86 @@ impl<'a, 'b> Arg<'a, 'b> {
self 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] [-- <ARG>]` 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 /// 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 /// required, when no other conflicting rules have been evaluated. Conflicting rules take
/// precedence over being required. **Default:** `false` /// 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
}
}

View file

@ -26,3 +26,9 @@ impl<'n, 'e> Base<'n, 'e> {
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for 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() } 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
}
}

View file

@ -105,6 +105,12 @@ impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> {
fn disp_ord(&self) -> usize { self.s.disp_ord } 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)] #[cfg(test)]
mod test { mod test {
use args::settings::ArgSettings; use args::settings::ArgSettings;

View file

@ -149,6 +149,12 @@ impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> {
fn disp_ord(&self) -> usize { self.s.disp_ord } 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)] #[cfg(test)]
mod test { mod test {
use args::settings::ArgSettings; use args::settings::ArgSettings;

View file

@ -138,6 +138,12 @@ impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> {
fn disp_ord(&self) -> usize { self.index as usize } 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)] #[cfg(test)]
mod test { mod test {
use args::settings::ArgSettings; use args::settings::ArgSettings;

View file

@ -4,9 +4,6 @@ use std::ffi::OsStr;
use std::ops::Deref; use std::ops::Deref;
use std::mem; use std::mem;
// Third Party
use vec_map::VecMap;
// Internal // Internal
use args::{ArgMatches, MatchedArg, SubCommand}; use args::{ArgMatches, MatchedArg, SubCommand};
use args::AnyArg; use args::AnyArg;
@ -25,7 +22,7 @@ impl<'a> ArgMatcher<'a> {
pub fn propagate(&mut self, arg: &'a str) { pub fn propagate(&mut self, arg: &'a str) {
debugln!("ArgMatcher::propagate: arg={}", arg); 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() ma.vals.clone()
} else { } else {
debugln!("ArgMatcher::propagate: arg wasn't used"); 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 sma = (*sc).matches.args.entry(arg).or_insert_with(|| {
let mut gma = MatchedArg::new(); let mut gma = MatchedArg::new();
gma.occurs += 1; gma.occurs += 1;
for (i, v) in &vals { gma.vals = vals.clone();
gma.vals.insert(i, v.clone());
}
gma gma
}); });
if sma.vals.is_empty() { if sma.vals.is_empty() {
for (i, v) in &vals { sma.vals = vals.clone();
sma.vals.insert(i, v.clone());
}
} }
} }
let mut am = ArgMatcher(mem::replace(&mut sc.matches, ArgMatches::new())); 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) { pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) {
let ma = self.entry(arg).or_insert(MatchedArg { let ma = self.entry(arg).or_insert(MatchedArg {
occurs: 0, occurs: 0,
vals: VecMap::new(), vals: Vec::with_capacity(1),
}); });
let len = ma.vals.len() + 1; // let len = ma.vals.len() + 1;
ma.vals.insert(len, val.to_owned()); ma.vals.push(val.to_owned());
} }
pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool

View file

@ -3,10 +3,7 @@ use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::iter::Map; use std::iter::Map;
use std::slice; use std::slice::Iter;
// Third Party
use vec_map;
// Internal // Internal
use INVALID_UTF8; use INVALID_UTF8;
@ -113,7 +110,7 @@ impl<'a> ArgMatches<'a> {
/// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html
pub fn value_of<S: AsRef<str>>(&self, name: S) -> Option<&str> { pub fn value_of<S: AsRef<str>>(&self, name: S) -> Option<&str> {
if let Some(arg) = self.args.get(name.as_ref()) { 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)); 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 /// [`Arg::values_of_lossy`]: ./struct.ArgMatches.html#method.values_of_lossy
pub fn value_of_lossy<S: AsRef<str>>(&'a self, name: S) -> Option<Cow<'a, str>> { pub fn value_of_lossy<S: AsRef<str>>(&'a self, name: S) -> Option<Cow<'a, str>> {
if let Some(arg) = self.args.get(name.as_ref()) { 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()); return Some(v.to_string_lossy());
} }
} }
@ -182,7 +179,7 @@ impl<'a> ArgMatches<'a> {
pub fn value_of_os<S: AsRef<str>>(&self, name: S) -> Option<&OsStr> { pub fn value_of_os<S: AsRef<str>>(&self, name: S) -> Option<&OsStr> {
self.args self.args
.get(name.as_ref()) .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 /// 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()) { if let Some(arg) = self.args.get(name.as_ref()) {
fn to_str_slice(o: &OsString) -> &str { o.to_str().expect(INVALID_UTF8) } 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 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 None
} }
@ -246,7 +243,7 @@ impl<'a> ArgMatches<'a> {
pub fn values_of_lossy<S: AsRef<str>>(&'a self, name: S) -> Option<Vec<String>> { pub fn values_of_lossy<S: AsRef<str>>(&'a self, name: S) -> Option<Vec<String>> {
if let Some(arg) = self.args.get(name.as_ref()) { if let Some(arg) = self.args.get(name.as_ref()) {
return Some(arg.vals return Some(arg.vals
.values() .iter()
.map(|v| v.to_string_lossy().into_owned()) .map(|v| v.to_string_lossy().into_owned())
.collect()); .collect());
} }
@ -288,7 +285,7 @@ impl<'a> ArgMatches<'a> {
fn to_str_slice(o: &OsString) -> &OsStr { &*o } fn to_str_slice(o: &OsString) -> &OsStr { &*o }
let to_str_slice: fn(&'a OsString) -> &'a OsStr = to_str_slice; // coerce to fn pointer 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()) { 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 None
} }
@ -554,7 +551,7 @@ impl<'a> ArgMatches<'a> {
#[derive(Clone)] #[derive(Clone)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Values<'a> { pub struct Values<'a> {
iter: Map<vec_map::Values<'a, OsString>, fn(&'a OsString) -> &'a str>, iter: Map<Iter<'a, OsString>, fn(&'a OsString) -> &'a str>,
} }
impl<'a> Iterator for Values<'a> { impl<'a> Iterator for Values<'a> {
@ -570,51 +567,6 @@ impl<'a> DoubleEndedIterator for Values<'a> {
impl<'a> ExactSizeIterator 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<V>>,
}
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<usize>) { (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`] /// 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 /// method. Usage of this iterator allows values which contain invalid UTF-8 code points unlike
/// [`Values`]. /// [`Values`].
@ -639,7 +591,7 @@ impl<'a, V> DoubleEndedIterator for Iter<'a, V> {
#[derive(Clone)] #[derive(Clone)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct OsValues<'a> { pub struct OsValues<'a> {
iter: Map<vec_map::Values<'a, OsString>, fn(&'a OsString) -> &'a OsStr>, iter: Map<Iter<'a, OsString>, fn(&'a OsString) -> &'a OsStr>,
} }
impl<'a> Iterator for OsValues<'a> { impl<'a> Iterator for OsValues<'a> {

View file

@ -152,6 +152,7 @@ impl<'a> ArgGroup<'a> {
/// assert!(m.is_present("flag")); /// assert!(m.is_present("flag"));
/// ``` /// ```
/// [argument]: ./struct.Arg.html /// [argument]: ./struct.Arg.html
#[cfg_attr(feature = "lints", allow(should_assert_eq))]
pub fn arg(mut self, n: &'a str) -> Self { pub fn arg(mut self, n: &'a str) -> Self {
assert!(self.name != n, assert!(self.name != n,
"ArgGroup '{}' can not have same name as arg inside it", "ArgGroup '{}' can not have same name as arg inside it",

View file

@ -1,23 +1,20 @@
// Std // Std
use std::ffi::OsString; use std::ffi::OsString;
// Third Party
use vec_map::VecMap;
#[doc(hidden)] #[doc(hidden)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MatchedArg { pub struct MatchedArg {
#[doc(hidden)] #[doc(hidden)]
pub occurs: u64, pub occurs: u64,
#[doc(hidden)] #[doc(hidden)]
pub vals: VecMap<OsString>, pub vals: Vec<OsString>,
} }
impl Default for MatchedArg { impl Default for MatchedArg {
fn default() -> Self { fn default() -> Self {
MatchedArg { MatchedArg {
occurs: 1, occurs: 1,
vals: VecMap::new(), vals: Vec::with_capacity(1),
} }
} }
} }

View file

@ -4,20 +4,21 @@ use std::str::FromStr;
bitflags! { bitflags! {
flags Flags: u16 { flags Flags: u16 {
const REQUIRED = 0b00000000000001, const REQUIRED = 1 << 0,
const MULTIPLE = 0b00000000000010, const MULTIPLE = 1 << 1,
const EMPTY_VALS = 0b00000000000100, const EMPTY_VALS = 1 << 2,
const GLOBAL = 0b00000000001000, const GLOBAL = 1 << 3,
const HIDDEN = 0b00000000010000, const HIDDEN = 1 << 4,
const TAKES_VAL = 0b00000000100000, const TAKES_VAL = 1 << 5,
const USE_DELIM = 0b00000001000000, const USE_DELIM = 1 << 6,
const NEXT_LINE_HELP = 0b00000010000000, const NEXT_LINE_HELP = 1 << 7,
const R_UNLESS_ALL = 0b00000100000000, const R_UNLESS_ALL = 1 << 8,
const REQ_DELIM = 0b00001000000000, const REQ_DELIM = 1 << 9,
const DELIM_NOT_SET = 0b00010000000000, const DELIM_NOT_SET = 1 << 10,
const HIDE_POS_VALS = 0b00100000000000, const HIDE_POS_VALS = 1 << 11,
const ALLOW_TAC_VALS = 0b01000000000000, const ALLOW_TAC_VALS = 1 << 12,
const REQUIRE_EQUALS = 0b10000000000000, const REQUIRE_EQUALS = 1 << 13,
const LAST = 1 << 14,
} }
} }
@ -42,7 +43,8 @@ impl ArgFlags {
ValueDelimiterNotSet => DELIM_NOT_SET, ValueDelimiterNotSet => DELIM_NOT_SET,
HidePossibleValues => HIDE_POS_VALS, HidePossibleValues => HIDE_POS_VALS,
AllowLeadingHyphen => ALLOW_TAC_VALS, AllowLeadingHyphen => ALLOW_TAC_VALS,
RequireEquals => REQUIRE_EQUALS RequireEquals => REQUIRE_EQUALS,
Last => LAST
} }
} }
@ -82,6 +84,9 @@ pub enum ArgSettings {
AllowLeadingHyphen, AllowLeadingHyphen,
/// Require options use `--option=val` syntax /// Require options use `--option=val` syntax
RequireEquals, RequireEquals,
/// Specifies that the arg is the last positional argument and may be accessed early via `--`
/// syntax
Last,
#[doc(hidden)] #[doc(hidden)]
RequiredUnlessAll, RequiredUnlessAll,
#[doc(hidden)] #[doc(hidden)]
@ -106,6 +111,7 @@ impl FromStr for ArgSettings {
"hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues), "hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues),
"allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen), "allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen),
"requireequals" => Ok(ArgSettings::RequireEquals), "requireequals" => Ok(ArgSettings::RequireEquals),
"last" => Ok(ArgSettings::Last),
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()), _ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
} }
} }
@ -145,6 +151,8 @@ mod test {
ArgSettings::ValueDelimiterNotSet); ArgSettings::ValueDelimiterNotSet);
assert_eq!("requireequals".parse::<ArgSettings>().unwrap(), assert_eq!("requireequals".parse::<ArgSettings>().unwrap(),
ArgSettings::RequireEquals); ArgSettings::RequireEquals);
assert_eq!("last".parse::<ArgSettings>().unwrap(),
ArgSettings::Last);
assert!("hahahaha".parse::<ArgSettings>().is_err()); assert!("hahahaha".parse::<ArgSettings>().is_err());
} }
} }

View file

@ -527,6 +527,7 @@
#![cfg_attr(feature = "lints", deny(warnings))] #![cfg_attr(feature = "lints", deny(warnings))]
#![cfg_attr(feature = "lints", allow(cyclomatic_complexity))] #![cfg_attr(feature = "lints", allow(cyclomatic_complexity))]
#![cfg_attr(feature = "lints", allow(doc_markdown))] #![cfg_attr(feature = "lints", allow(doc_markdown))]
#![cfg_attr(feature = "lints", allow(explicit_iter_loop))]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
extern crate strsim; extern crate strsim;

View file

@ -847,7 +847,7 @@ macro_rules! vec_remove_all {
}; };
} }
macro_rules! find_from { 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; let mut ret = None;
for k in $matcher.arg_names() { for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, &k, flags, iter) { if let Some(f) = find_by_name!($_self, &k, flags, iter) {
@ -877,7 +877,7 @@ macro_rules! find_from {
} }
macro_rules! find_name_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; let mut ret = None;
for k in $matcher.arg_names() { for k in $matcher.arg_names() {
if let Some(f) = find_by_name!($_self, &k, flags, iter) { 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 // Finds an arg by name
macro_rules! find_by_name { macro_rules! find_by_name {
($_self:ident, $name:expr, $what:ident, $how:ident) => { ($p:expr, $name:expr, $what:ident, $how:ident) => {
$_self.$what.$how().find(|o| &o.b.name == $name) $p.$what.$how().find(|o| &o.b.name == $name)
} }
} }
@ -989,7 +989,7 @@ macro_rules! find_subcmd {
$_self.subcommands $_self.subcommands
.iter() .iter()
.find(|s| { .find(|s| {
s.p.meta.name == $sc || &*s.p.meta.name == $sc ||
(s.p.meta.aliases.is_some() && (s.p.meta.aliases.is_some() &&
s.p s.p
.meta .meta
@ -1029,12 +1029,18 @@ macro_rules! _shorts_longs {
macro_rules! arg_names { macro_rules! arg_names {
($_self:ident) => {{ ($_self:ident) => {{
_names!($_self) _names!(@args $_self)
}};
}
macro_rules! sc_names {
($_self:ident) => {{
_names!(@sc $_self)
}}; }};
} }
macro_rules! _names { macro_rules! _names {
($_self:ident) => {{ (@args $_self:ident) => {{
$_self.flags $_self.flags
.iter() .iter()
.map(|f| &*f.b.name) .map(|f| &*f.b.name)
@ -1043,4 +1049,14 @@ macro_rules! _names {
.chain($_self.positionals.values() .chain($_self.positionals.values()
.map(|p| &*p.b.name))) .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)))
}}
} }

View file

@ -11,7 +11,7 @@ use fmt::Format;
/// `Some("foo")`, whereas "blark" would yield `None`. /// `Some("foo")`, whereas "blark" would yield `None`.
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg_attr(feature = "lints", allow(needless_lifetimes))] #[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<str> + 'a, where T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T> I: IntoIterator<Item = &'a T>
{ {
@ -31,7 +31,7 @@ pub fn did_you_mean<'a, T, I>(v: &str, possible_values: I) -> Option<&'a str>
} }
#[cfg(not(feature = "suggestions"))] #[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<str> + 'a, where T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T> I: IntoIterator<Item = &'a T>
{ {
@ -68,6 +68,7 @@ pub fn did_you_mean_suffix<'z, T, I>(arg: &str,
} }
/// A helper to determine message formatting /// A helper to determine message formatting
#[derive(Copy, Clone, Debug)]
pub enum DidYouMeanMessageStyle { pub enum DidYouMeanMessageStyle {
/// Suggested value is a long flag /// Suggested value is a long flag
LongFlag, LongFlag,

View file

@ -110,6 +110,113 @@ fn arg_required_else_help() {
assert_eq!(err.kind, ErrorKind::MissingArgumentOrSubcommand); 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] #[test]
fn no_bin_name() { fn no_bin_name() {
let result = App::new("arg_required") let result = App::new("arg_required")
@ -452,7 +559,9 @@ fn propagate_vals_down() {
.setting(AppSettings::PropagateGlobalValuesDown) .setting(AppSettings::PropagateGlobalValuesDown)
.arg(Arg::from_usage("[cmd] 'command to run'").global(true)) .arg(Arg::from_usage("[cmd] 'command to run'").global(true))
.subcommand(SubCommand::with_name("foo")) .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")); assert_eq!(m.value_of("cmd"), Some("set"));
let sub_m = m.subcommand_matches("foo").unwrap(); let sub_m = m.subcommand_matches("foo").unwrap();
assert_eq!(sub_m.value_of("cmd"), Some("set")); assert_eq!(sub_m.value_of("cmd"), Some("set"));
@ -464,7 +573,9 @@ fn allow_missing_positional() {
.setting(AppSettings::AllowMissingPositional) .setting(AppSettings::AllowMissingPositional)
.arg(Arg::from_usage("[src] 'some file'").default_value("src")) .arg(Arg::from_usage("[src] 'some file'").default_value("src"))
.arg_from_usage("<dest> 'some file'") .arg_from_usage("<dest> '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("src"), Some("src"));
assert_eq!(m.value_of("dest"), Some("file")); assert_eq!(m.value_of("dest"), Some("file"));
} }

View file

@ -36,6 +36,47 @@ SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s) help Prints this message or the help of the given subcommand(s)
subcmd tests subcommands"; subcmd tests subcommands";
static SC_NEGATES_REQS: &'static str = "prog 1.0
USAGE:
prog --opt <FILE> [PATH]
prog [PATH] <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-o, --opt <FILE> tests options
ARGS:
<PATH>
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 <SUBCOMMAND>
FLAGS:
-f, --flag testing flags
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-o, --opt <FILE> tests options
ARGS:
<PATH>
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 static AFTER_HELP: &'static str = "some text that comes before the help
clap-test v1.4.8 clap-test v1.4.8
@ -50,6 +91,19 @@ FLAGS:
some text that comes after the help"; 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 <FILE> tests options";
static SC_HELP: &'static str = "clap-test-subcmd 0.1 static SC_HELP: &'static str = "clap-test-subcmd 0.1
Kevin K. <kbknapp@gmail.com> Kevin K. <kbknapp@gmail.com>
tests subcommands tests subcommands
@ -236,6 +290,72 @@ FLAGS:
-H, --help Print help information -H, --help Print help information
-v, --version Print version information"; -v, --version Print version information";
static LAST_ARG: &'static str = "last 0.1
USAGE:
last <TARGET> [CORPUS] [-- <ARGS>...]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<TARGET> some
<CORPUS> some
<ARGS>... some";
static LAST_ARG_SC: &'static str = "last 0.1
USAGE:
last <TARGET> [CORPUS] [-- <ARGS>...]
last <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<TARGET> some
<CORPUS> some
<ARGS>... 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 <TARGET> [CORPUS] -- <ARGS>...
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<TARGET> some
<CORPUS> some
<ARGS>... some";
static LAST_ARG_REQ_SC: &'static str = "last 0.1
USAGE:
last <TARGET> [CORPUS] -- <ARGS>...
last <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<TARGET> some
<CORPUS> some
<ARGS>... some
SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
test some";
#[test] #[test]
fn help_short() { fn help_short() {
let m = App::new("test") let m = App::new("test")
@ -531,6 +651,40 @@ fn issue_760() {
.takes_value(true)); .takes_value(true));
assert!(test::compare_output(app, "ctest --help", ISSUE_760, false)); 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 <FILE> '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] #[test]
fn issue_777_wrap_all_things() { fn issue_777_wrap_all_things() {
let app = App::new("A app with a crazy very long long long name hahaha") 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"); .version_message("Print version information");
assert!(test::compare_output(app, "customize --help", CUSTOM_VERSION_AND_HELP, false)); 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));
}

View file

@ -5,7 +5,7 @@ use clap::{App, Arg};
include!("../clap-test.rs"); include!("../clap-test.rs");
static HIDDEN_ARGS: &'static str = "test 1.3 static HIDDEN_ARGS: &'static str = "test 1.4
Kevin K. Kevin K.
tests stuff tests stuff
@ -25,9 +25,10 @@ fn hidden_args() {
let app = App::new("test") let app = App::new("test")
.author("Kevin K.") .author("Kevin K.")
.about("tests stuff") .about("tests stuff")
.version("1.3") .version("1.4")
.args(&[Arg::from_usage("-f, --flag 'some flag'").hidden(true), .args(&[Arg::from_usage("-f, --flag 'some flag'").hidden(true),
Arg::from_usage("-F, --flag2 'some other flag'"), 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)); assert!(test::compare_output(app, "test --help", HIDDEN_ARGS, false));
} }

View file

@ -180,7 +180,7 @@ fn multiple_positional_one_required_usage_string() {
.arg_from_usage("<FILE> 'some file'") .arg_from_usage("<FILE> 'some file'")
.arg_from_usage("[FILES]... 'some file'") .arg_from_usage("[FILES]... 'some file'")
.get_matches_from(vec!["test", "file"]); .get_matches_from(vec!["test", "file"]);
assert_eq!(m.usage(), "USAGE:\n test <FILE> [ARGS]"); assert_eq!(m.usage(), "USAGE:\n test <FILE> [FILES]...");
} }
#[test] #[test]
@ -211,3 +211,26 @@ fn missing_required_2() {
assert!(r.is_err()); assert!(r.is_err());
assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument); assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
} }
#[test]
fn last_positional() {
let r = App::new("test")
.arg_from_usage("<TARGET> '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::<Vec<_>>(), &["arg"]);
}
#[test]
fn last_positional_no_double_dash() {
let r = App::new("test")
.arg_from_usage("<TARGET> '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);
}