mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 14:22:34 +00:00
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:
commit
814b12644e
28 changed files with 2106 additions and 1286 deletions
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -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>
|
||||
### v2.20.5 (2017-02-18)
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@ unicode-width = "0.1.4"
|
|||
unicode-segmentation = "1.0.1"
|
||||
strsim = { version = "0.6.0", optional = true }
|
||||
ansi_term = { version = "0.9.0", optional = true }
|
||||
term_size = { version = "0.2.2", optional = true }
|
||||
term_size = { version = "0.2.3", optional = true }
|
||||
yaml-rust = { version = "0.3.5", optional = true }
|
||||
clippy = { version = "~0.0.112", optional = true }
|
||||
clippy = { version = "~0.0.118", optional = true }
|
||||
atty = { version = "0.2.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -65,7 +65,7 @@ debug = true
|
|||
rpath = false
|
||||
lto = false
|
||||
debug-assertions = true
|
||||
codegen-units = 2
|
||||
codegen-units = 4
|
||||
|
||||
[profile.bench]
|
||||
opt-level = 3
|
||||
|
|
137
README.md
137
README.md
|
@ -45,131 +45,24 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
|
|||
|
||||
## What's New
|
||||
|
||||
Here's the highlights for v2.20.5
|
||||
Here's the highlights for v2.21.0
|
||||
|
||||
* adds the ability to mark a positional argument as 'last' which means it should be used with `--` syntax and can be accessed early to effectivly skip other positional args
|
||||
* Some performance improvements by reducing the ammount of duplicate work, cloning, and allocations in all cases.
|
||||
* Some massive perfomance gains when using many args (i.e. things like shell glob expansions)
|
||||
* adds a setting to allow one to infer shortened subcommands or aliases (i.e. for subcommmand "test", "t", "te", or "tes" would be allowed assuming no other ambiguities)
|
||||
* when `AppSettings::SubcommandsNegateReqs` and `ArgsNegateSubcommands` are used, a new more accurate double line usage string is shown
|
||||
* provides `default_value_os` and `default_value_if[s]_os`
|
||||
* provides `App::help_message` and `App::version_message` which allows one to override the auto-generated help/version flag associated help
|
||||
* adds the ability to require the equals syntax with options `--opt=val`
|
||||
* doesn't print the argument sections in the help message if all args in that section are hidden
|
||||
* doesn't include the various `[ARGS]` `[FLAGS]` or `[OPTIONS]` if the only ones available are hidden
|
||||
* now correctly shows subcommand as required in the usage string when AppSettings::SubcommandRequiredElseHelp is used
|
||||
* fixes some "memory leaks" when an error is detected and clap exits
|
||||
* fixes a trait that's marked private accidentlly, but should be crate internal public
|
||||
* fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts
|
||||
* Fixes a critical bug in the `clap_app!` macro of a missing fragment specifier when using `!property` style tags.
|
||||
|
||||
|
||||
Here's the highlights from v2.0.0 to v2.20.4
|
||||
|
||||
* Fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts
|
||||
* Fix examples link in CONTRIBUTING.md
|
||||
* **Completions**: fixes bash completions for commands that have an underscore in the name
|
||||
* **Completions**: fixes a bug where ZSH completions would panic if the binary name had an underscore in it
|
||||
* allow final word to be wrapped in wrap_help
|
||||
* **Completions**: fixes a bug where global args weren't included in the generated completion scripts
|
||||
* **Macros Documentation:** adds a warning about changing values in Cargo.toml not triggering a rebuild automatically
|
||||
* Fixes a critical bug where subcommand settings were being propogated too far
|
||||
* Adds ArgGroup::multiple to the supported YAML fields for building ArgGroups from YAML
|
||||
* Fixes a bug where the final word wasn't wrapped in help messages
|
||||
* Fixes finding required arguments in group arguments
|
||||
* **ArgsNegateSubcommands:** disables args being allowed between subcommands
|
||||
* **DontCollapseArgsInUsage:** disables the collapsing of positional args into `[ARGS]` in the usage string
|
||||
* **DisableHelpSubcommand:** disables building the `help` subcommand
|
||||
* **AllowMissingPositional:** allows one to implement `$ prog [optional] <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)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
extern crate clap;
|
||||
extern crate test;
|
||||
|
||||
use clap::App;
|
||||
use clap::{App, Arg};
|
||||
|
||||
use test::Bencher;
|
||||
|
||||
|
@ -25,6 +25,69 @@ fn build_app(b: &mut Bencher) {
|
|||
b.iter(|| create_app!());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn add_flag(b: &mut Bencher) {
|
||||
fn build_app() -> App<'static, 'static> {
|
||||
App::new("claptests")
|
||||
}
|
||||
|
||||
b.iter(|| build_app().arg(Arg::from_usage("-s, --some 'something'")));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn add_flag_ref(b: &mut Bencher) {
|
||||
fn build_app() -> App<'static, 'static> {
|
||||
App::new("claptests")
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let arg = Arg::from_usage("-s, --some 'something'");
|
||||
build_app().arg(&arg)
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn add_opt(b: &mut Bencher) {
|
||||
fn build_app() -> App<'static, 'static> {
|
||||
App::new("claptests")
|
||||
}
|
||||
|
||||
b.iter(|| build_app().arg(Arg::from_usage("-s, --some <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]
|
||||
fn parse_clean(b: &mut Bencher) {
|
||||
b.iter(|| create_app!().get_matches_from(vec![""]));
|
||||
|
|
|
@ -11,6 +11,7 @@ use app::parser::Parser;
|
|||
use args::{AnyArg, ArgSettings, DispOrder};
|
||||
use errors::{Error, Result as ClapResult};
|
||||
use fmt::{Format, Colorizer};
|
||||
use app::usage;
|
||||
|
||||
// Third Party
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
@ -195,9 +196,7 @@ impl<'a> Help<'a> {
|
|||
if arg.longest_filter() {
|
||||
self.longest = cmp::max(self.longest, arg.to_string().len());
|
||||
}
|
||||
if !arg.is_set(ArgSettings::Hidden) {
|
||||
arg_v.push(arg)
|
||||
}
|
||||
arg_v.push(arg)
|
||||
}
|
||||
let mut first = true;
|
||||
for arg in arg_v {
|
||||
|
@ -541,7 +540,7 @@ impl<'a> Help<'a> {
|
|||
pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> {
|
||||
debugln!("Help::write_all_args;");
|
||||
let flags = parser.has_flags();
|
||||
let pos = parser.has_positionals();
|
||||
let pos = parser.positionals().filter(|arg| !arg.is_set(ArgSettings::Hidden)).count() > 0;
|
||||
let opts = parser.has_opts();
|
||||
let subcmds = parser.has_subcommands();
|
||||
|
||||
|
@ -684,7 +683,7 @@ impl<'a> Help<'a> {
|
|||
try!(write!(self.writer,
|
||||
"\n{}{}\n\n",
|
||||
TAB,
|
||||
parser.create_usage_no_title(&[])));
|
||||
usage::create_help_usage(parser, true)));
|
||||
|
||||
let flags = parser.has_flags();
|
||||
let pos = parser.has_positionals();
|
||||
|
@ -881,7 +880,7 @@ impl<'a> Help<'a> {
|
|||
parser.meta.about.unwrap_or("unknown about")));
|
||||
}
|
||||
b"usage" => {
|
||||
try!(write!(self.writer, "{}", parser.create_usage_no_title(&[])));
|
||||
try!(write!(self.writer, "{}", usage::create_help_usage(parser, true)));
|
||||
}
|
||||
b"all-args" => {
|
||||
try!(self.write_all_args(&parser));
|
||||
|
|
|
@ -148,7 +148,12 @@ macro_rules! parse_positional {
|
|||
$matcher.inc_occurrence_of($p.b.name);
|
||||
let _ = $_self.groups_for_arg($p.b.name)
|
||||
.and_then(|vec| Some($matcher.inc_occurrences_of(&*vec)));
|
||||
arg_post_processing!($_self, $p, $matcher);
|
||||
if $_self.cache.map_or(true, |name| name != $p.b.name) {
|
||||
arg_post_processing!($_self, $p, $matcher);
|
||||
$_self.cache = Some($p.b.name);
|
||||
}
|
||||
|
||||
$_self.settings.set(AS::ValidArgFound);
|
||||
// Only increment the positional counter if it doesn't allow multiples
|
||||
if !$p.b.settings.is_set(ArgSettings::Multiple) {
|
||||
$pos_counter += 1;
|
||||
|
|
|
@ -4,6 +4,8 @@ mod macros;
|
|||
pub mod parser;
|
||||
mod meta;
|
||||
mod help;
|
||||
mod validator;
|
||||
mod usage;
|
||||
|
||||
// Std
|
||||
use std::env;
|
||||
|
@ -405,7 +407,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
/// # ;
|
||||
/// ```
|
||||
pub fn help_message<S: Into<&'a str>>(mut self, s: S) -> Self {
|
||||
self.p.help_message(s.into());
|
||||
self.p.help_message = Some(s.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -423,7 +425,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
/// # ;
|
||||
/// ```
|
||||
pub fn version_message<S: Into<&'a str>>(mut self, s: S) -> Self {
|
||||
self.p.version_message(s.into());
|
||||
self.p.version_message = Some(s.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
1486
src/app/parser.rs
1486
src/app/parser.rs
File diff suppressed because it is too large
Load diff
|
@ -5,44 +5,46 @@ use std::ops::BitOr;
|
|||
|
||||
bitflags! {
|
||||
flags Flags: u64 {
|
||||
const SC_NEGATE_REQS = 0b00000000000000000000000000000000000001,
|
||||
const SC_REQUIRED = 0b00000000000000000000000000000000000010,
|
||||
const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000000000100,
|
||||
const GLOBAL_VERSION = 0b00000000000000000000000000000000001000,
|
||||
const VERSIONLESS_SC = 0b00000000000000000000000000000000010000,
|
||||
const UNIFIED_HELP = 0b00000000000000000000000000000000100000,
|
||||
const WAIT_ON_ERROR = 0b00000000000000000000000000000001000000,
|
||||
const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000000000010000000,
|
||||
const NEEDS_LONG_HELP = 0b00000000000000000000000000000100000000,
|
||||
const NEEDS_LONG_VERSION = 0b00000000000000000000000000001000000000,
|
||||
const NEEDS_SC_HELP = 0b00000000000000000000000000010000000000,
|
||||
const DISABLE_VERSION = 0b00000000000000000000000000100000000000,
|
||||
const HIDDEN = 0b00000000000000000000000001000000000000,
|
||||
const TRAILING_VARARG = 0b00000000000000000000000010000000000000,
|
||||
const NO_BIN_NAME = 0b00000000000000000000000100000000000000,
|
||||
const ALLOW_UNK_SC = 0b00000000000000000000001000000000000000,
|
||||
const UTF8_STRICT = 0b00000000000000000000010000000000000000,
|
||||
const UTF8_NONE = 0b00000000000000000000100000000000000000,
|
||||
const LEADING_HYPHEN = 0b00000000000000000001000000000000000000,
|
||||
const NO_POS_VALUES = 0b00000000000000000010000000000000000000,
|
||||
const NEXT_LINE_HELP = 0b00000000000000000100000000000000000000,
|
||||
const DERIVE_DISP_ORDER = 0b00000000000000001000000000000000000000,
|
||||
const COLORED_HELP = 0b00000000000000010000000000000000000000,
|
||||
const COLOR_ALWAYS = 0b00000000000000100000000000000000000000,
|
||||
const COLOR_AUTO = 0b00000000000001000000000000000000000000,
|
||||
const COLOR_NEVER = 0b00000000000010000000000000000000000000,
|
||||
const DONT_DELIM_TRAIL = 0b00000000000100000000000000000000000000,
|
||||
const ALLOW_NEG_NUMS = 0b00000000001000000000000000000000000000,
|
||||
const LOW_INDEX_MUL_POS = 0b00000000010000000000000000000000000000,
|
||||
const DISABLE_HELP_SC = 0b00000000100000000000000000000000000000,
|
||||
const DONT_COLLAPSE_ARGS = 0b00000001000000000000000000000000000000,
|
||||
const ARGS_NEGATE_SCS = 0b00000010000000000000000000000000000000,
|
||||
const PROPAGATE_VALS_DOWN = 0b00000100000000000000000000000000000000,
|
||||
const ALLOW_MISSING_POS = 0b00001000000000000000000000000000000000,
|
||||
const TRAILING_VALUES = 0b00010000000000000000000000000000000000,
|
||||
const VALID_NEG_NUM_FOUND = 0b00100000000000000000000000000000000000,
|
||||
const PROPOGATED = 0b01000000000000000000000000000000000000,
|
||||
const VALID_ARG_FOUND = 0b10000000000000000000000000000000000000
|
||||
const SC_NEGATE_REQS = 1 << 0,
|
||||
const SC_REQUIRED = 1 << 1,
|
||||
const A_REQUIRED_ELSE_HELP = 1 << 2,
|
||||
const GLOBAL_VERSION = 1 << 3,
|
||||
const VERSIONLESS_SC = 1 << 4,
|
||||
const UNIFIED_HELP = 1 << 5,
|
||||
const WAIT_ON_ERROR = 1 << 6,
|
||||
const SC_REQUIRED_ELSE_HELP= 1 << 7,
|
||||
const NEEDS_LONG_HELP = 1 << 8,
|
||||
const NEEDS_LONG_VERSION = 1 << 9,
|
||||
const NEEDS_SC_HELP = 1 << 10,
|
||||
const DISABLE_VERSION = 1 << 11,
|
||||
const HIDDEN = 1 << 12,
|
||||
const TRAILING_VARARG = 1 << 13,
|
||||
const NO_BIN_NAME = 1 << 14,
|
||||
const ALLOW_UNK_SC = 1 << 15,
|
||||
const UTF8_STRICT = 1 << 16,
|
||||
const UTF8_NONE = 1 << 17,
|
||||
const LEADING_HYPHEN = 1 << 18,
|
||||
const NO_POS_VALUES = 1 << 19,
|
||||
const NEXT_LINE_HELP = 1 << 20,
|
||||
const DERIVE_DISP_ORDER = 1 << 21,
|
||||
const COLORED_HELP = 1 << 22,
|
||||
const COLOR_ALWAYS = 1 << 23,
|
||||
const COLOR_AUTO = 1 << 24,
|
||||
const COLOR_NEVER = 1 << 25,
|
||||
const DONT_DELIM_TRAIL = 1 << 26,
|
||||
const ALLOW_NEG_NUMS = 1 << 27,
|
||||
const LOW_INDEX_MUL_POS = 1 << 28,
|
||||
const DISABLE_HELP_SC = 1 << 29,
|
||||
const DONT_COLLAPSE_ARGS = 1 << 30,
|
||||
const ARGS_NEGATE_SCS = 1 << 31,
|
||||
const PROPAGATE_VALS_DOWN = 1 << 32,
|
||||
const ALLOW_MISSING_POS = 1 << 33,
|
||||
const TRAILING_VALUES = 1 << 34,
|
||||
const VALID_NEG_NUM_FOUND = 1 << 35,
|
||||
const PROPOGATED = 1 << 36,
|
||||
const VALID_ARG_FOUND = 1 << 37,
|
||||
const INFER_SUBCOMMANDS = 1 << 38,
|
||||
const CONTAINS_LAST = 1 << 39,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +104,9 @@ impl AppFlags {
|
|||
TrailingValues => TRAILING_VALUES,
|
||||
ValidNegNumFound => VALID_NEG_NUM_FOUND,
|
||||
Propogated => PROPOGATED,
|
||||
ValidArgFound => VALID_ARG_FOUND
|
||||
ValidArgFound => VALID_ARG_FOUND,
|
||||
InferSubcommands => INFER_SUBCOMMANDS,
|
||||
ContainsLast => CONTAINS_LAST
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,6 +529,36 @@ pub enum AppSettings {
|
|||
/// This can be useful if there are many values, or they are explained elsewhere.
|
||||
HidePossibleValuesInHelp,
|
||||
|
||||
/// Tries to match unknown args to partial [`subcommands`] or their [aliases]. For example to
|
||||
/// match a subcommand named `test`, one could use `t`, `te`, `tes`, and `test`.
|
||||
///
|
||||
/// **NOTE:** The match *must not* be ambiguous at all in order to succeed. i.e. to match `te`
|
||||
/// to `test` there could not also be a subcommand or alias `temp` because both start with `te`
|
||||
///
|
||||
/// **CAUTION:** This setting can interfere with [positional/free arguments], take care when
|
||||
/// designing CLIs which allow inferred subcommands and have potential positional/free
|
||||
/// arguments who's values could start with the same characters as subcommands. If this is the
|
||||
/// case, it's recommended to use settings such as [`AppSeettings::ArgsNegateSubcommands`] in
|
||||
/// conjuction with this setting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, SubCommand, AppSettings};
|
||||
/// let m = App::new("prog")
|
||||
/// .setting(AppSettings::InferSubcommands)
|
||||
/// .subcommand(SubCommand::with_name("test"))
|
||||
/// .get_matches_from(vec![
|
||||
/// "prog", "te"
|
||||
/// ]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
/// [`subcommands`]: ./struct.SubCommand.html
|
||||
/// [positional/free arguments]: ./struct.Arg.html#method.index
|
||||
/// [aliases]: ./struct.App.html#method.alias
|
||||
/// [`AppSeettings::ArgsNegateSubcommands`]: ./enum.AppSettings.html#variant.ArgsNegateSubcommands
|
||||
InferSubcommands,
|
||||
|
||||
/// Specifies that the parser should not assume the first argument passed is the binary name.
|
||||
/// This is normally the case when using a "daemon" style mode, or an interactive CLI where one
|
||||
/// one would not normally type the binary or program name for each command.
|
||||
|
@ -827,6 +861,9 @@ pub enum AppSettings {
|
|||
|
||||
#[doc(hidden)]
|
||||
ValidArgFound,
|
||||
|
||||
#[doc(hidden)]
|
||||
ContainsLast,
|
||||
}
|
||||
|
||||
impl FromStr for AppSettings {
|
||||
|
@ -851,6 +888,7 @@ impl FromStr for AppSettings {
|
|||
"globalversion" => Ok(AppSettings::GlobalVersion),
|
||||
"hidden" => Ok(AppSettings::Hidden),
|
||||
"hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp),
|
||||
"infersubcommands" => Ok(AppSettings::InferSubcommands),
|
||||
"lowindexmultiplepositional" => Ok(AppSettings::LowIndexMultiplePositional),
|
||||
"nobinaryname" => Ok(AppSettings::NoBinaryName),
|
||||
"nextlinehelp" => Ok(AppSettings::NextLineHelp),
|
||||
|
@ -943,6 +981,8 @@ mod test {
|
|||
AppSettings::Propogated);
|
||||
assert_eq!("trailingvalues".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::TrailingValues);
|
||||
assert_eq!("infersubcommands".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::InferSubcommands);
|
||||
assert!("hahahaha".parse::<AppSettings>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
437
src/app/usage.rs
Normal file
437
src/app/usage.rs
Normal 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
454
src/app/validator.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -537,6 +537,86 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Specifies that this arg is the last, or final, positional argument (i.e. has the highest
|
||||
/// index) and is *only* able to be accessed via the `--` syntax (i.e. `$ prog args --
|
||||
/// last_arg`). Even, if no other arguments are left to parse, if the user omits the `--` syntax
|
||||
/// they will receive an [`UnknownArgument`] error. Setting an argument to `.last(true)` also
|
||||
/// allows one to access this arg early using the `--` syntax. Accessing an arg early, even with
|
||||
/// the `--` syntax is otherwise not possible.
|
||||
///
|
||||
/// **NOTE:** This will change the usage string to look like `$ prog [FLAGS] [-- <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
|
||||
/// required, when no other conflicting rules have been evaluated. Conflicting rules take
|
||||
/// precedence over being required. **Default:** `false`
|
||||
|
@ -3247,3 +3327,9 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, 'e> PartialEq for Arg<'n, 'e> {
|
||||
fn eq(&self, other: &Arg<'n, 'e>) -> bool {
|
||||
self.b == other.b
|
||||
}
|
||||
}
|
|
@ -26,3 +26,9 @@ impl<'n, 'e> Base<'n, 'e> {
|
|||
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Base<'n, 'e> {
|
||||
fn from(a: &'z Arg<'n, 'e>) -> Self { a.b.clone() }
|
||||
}
|
||||
|
||||
impl<'n, 'e> PartialEq for Base<'n, 'e> {
|
||||
fn eq(&self, other: &Base<'n, 'e>) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
|
@ -105,6 +105,12 @@ impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> {
|
|||
fn disp_ord(&self) -> usize { self.s.disp_ord }
|
||||
}
|
||||
|
||||
impl<'n, 'e> PartialEq for FlagBuilder<'n, 'e> {
|
||||
fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool {
|
||||
self.b == other.b
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use args::settings::ArgSettings;
|
||||
|
|
|
@ -149,6 +149,12 @@ impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> {
|
|||
fn disp_ord(&self) -> usize { self.s.disp_ord }
|
||||
}
|
||||
|
||||
impl<'n, 'e> PartialEq for OptBuilder<'n, 'e> {
|
||||
fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool {
|
||||
self.b == other.b
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use args::settings::ArgSettings;
|
||||
|
|
|
@ -138,6 +138,12 @@ impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> {
|
|||
fn disp_ord(&self) -> usize { self.index as usize }
|
||||
}
|
||||
|
||||
impl<'n, 'e> PartialEq for PosBuilder<'n, 'e> {
|
||||
fn eq(&self, other: &PosBuilder<'n, 'e>) -> bool {
|
||||
self.b == other.b
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use args::settings::ArgSettings;
|
||||
|
|
|
@ -4,9 +4,6 @@ use std::ffi::OsStr;
|
|||
use std::ops::Deref;
|
||||
use std::mem;
|
||||
|
||||
// Third Party
|
||||
use vec_map::VecMap;
|
||||
|
||||
// Internal
|
||||
use args::{ArgMatches, MatchedArg, SubCommand};
|
||||
use args::AnyArg;
|
||||
|
@ -25,7 +22,7 @@ impl<'a> ArgMatcher<'a> {
|
|||
|
||||
pub fn propagate(&mut self, arg: &'a str) {
|
||||
debugln!("ArgMatcher::propagate: arg={}", arg);
|
||||
let vals: VecMap<_> = if let Some(ma) = self.get(arg) {
|
||||
let vals: Vec<_> = if let Some(ma) = self.get(arg) {
|
||||
ma.vals.clone()
|
||||
} else {
|
||||
debugln!("ArgMatcher::propagate: arg wasn't used");
|
||||
|
@ -36,15 +33,11 @@ impl<'a> ArgMatcher<'a> {
|
|||
let sma = (*sc).matches.args.entry(arg).or_insert_with(|| {
|
||||
let mut gma = MatchedArg::new();
|
||||
gma.occurs += 1;
|
||||
for (i, v) in &vals {
|
||||
gma.vals.insert(i, v.clone());
|
||||
}
|
||||
gma.vals = vals.clone();
|
||||
gma
|
||||
});
|
||||
if sma.vals.is_empty() {
|
||||
for (i, v) in &vals {
|
||||
sma.vals.insert(i, v.clone());
|
||||
}
|
||||
sma.vals = vals.clone();
|
||||
}
|
||||
}
|
||||
let mut am = ArgMatcher(mem::replace(&mut sc.matches, ArgMatches::new()));
|
||||
|
@ -105,10 +98,10 @@ impl<'a> ArgMatcher<'a> {
|
|||
pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) {
|
||||
let ma = self.entry(arg).or_insert(MatchedArg {
|
||||
occurs: 0,
|
||||
vals: VecMap::new(),
|
||||
vals: Vec::with_capacity(1),
|
||||
});
|
||||
let len = ma.vals.len() + 1;
|
||||
ma.vals.insert(len, val.to_owned());
|
||||
// let len = ma.vals.len() + 1;
|
||||
ma.vals.push(val.to_owned());
|
||||
}
|
||||
|
||||
pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool
|
||||
|
|
|
@ -3,10 +3,7 @@ use std::borrow::Cow;
|
|||
use std::collections::HashMap;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::iter::Map;
|
||||
use std::slice;
|
||||
|
||||
// Third Party
|
||||
use vec_map;
|
||||
use std::slice::Iter;
|
||||
|
||||
// Internal
|
||||
use INVALID_UTF8;
|
||||
|
@ -113,7 +110,7 @@ impl<'a> ArgMatches<'a> {
|
|||
/// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html
|
||||
pub fn value_of<S: AsRef<str>>(&self, name: S) -> Option<&str> {
|
||||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
if let Some(v) = arg.vals.values().nth(0) {
|
||||
if let Some(v) = arg.vals.get(0) {
|
||||
return Some(v.to_str().expect(INVALID_UTF8));
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +142,7 @@ impl<'a> ArgMatches<'a> {
|
|||
/// [`Arg::values_of_lossy`]: ./struct.ArgMatches.html#method.values_of_lossy
|
||||
pub fn value_of_lossy<S: AsRef<str>>(&'a self, name: S) -> Option<Cow<'a, str>> {
|
||||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
if let Some(v) = arg.vals.values().nth(0) {
|
||||
if let Some(v) = arg.vals.get(0) {
|
||||
return Some(v.to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +179,7 @@ impl<'a> ArgMatches<'a> {
|
|||
pub fn value_of_os<S: AsRef<str>>(&self, name: S) -> Option<&OsStr> {
|
||||
self.args
|
||||
.get(name.as_ref())
|
||||
.map_or(None, |arg| arg.vals.values().nth(0).map(|v| v.as_os_str()))
|
||||
.map_or(None, |arg| arg.vals.get(0).map(|v| v.as_os_str()))
|
||||
}
|
||||
|
||||
/// Gets a [`Values`] struct which implements [`Iterator`] for values of a specific argument
|
||||
|
@ -214,7 +211,7 @@ impl<'a> ArgMatches<'a> {
|
|||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
fn to_str_slice(o: &OsString) -> &str { o.to_str().expect(INVALID_UTF8) }
|
||||
let to_str_slice: fn(&OsString) -> &str = to_str_slice; // coerce to fn pointer
|
||||
return Some(Values { iter: arg.vals.values().map(to_str_slice) });
|
||||
return Some(Values { iter: arg.vals.iter().map(to_str_slice) });
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -246,7 +243,7 @@ impl<'a> ArgMatches<'a> {
|
|||
pub fn values_of_lossy<S: AsRef<str>>(&'a self, name: S) -> Option<Vec<String>> {
|
||||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
return Some(arg.vals
|
||||
.values()
|
||||
.iter()
|
||||
.map(|v| v.to_string_lossy().into_owned())
|
||||
.collect());
|
||||
}
|
||||
|
@ -288,7 +285,7 @@ impl<'a> ArgMatches<'a> {
|
|||
fn to_str_slice(o: &OsString) -> &OsStr { &*o }
|
||||
let to_str_slice: fn(&'a OsString) -> &'a OsStr = to_str_slice; // coerce to fn pointer
|
||||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
return Some(OsValues { iter: arg.vals.values().map(to_str_slice) });
|
||||
return Some(OsValues { iter: arg.vals.iter().map(to_str_slice) });
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -554,7 +551,7 @@ impl<'a> ArgMatches<'a> {
|
|||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Values<'a> {
|
||||
iter: Map<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> {
|
||||
|
@ -570,51 +567,6 @@ impl<'a> DoubleEndedIterator for Values<'a> {
|
|||
|
||||
impl<'a> ExactSizeIterator for Values<'a> {}
|
||||
|
||||
/// An iterator over the key-value pairs of a map.
|
||||
#[derive(Clone)]
|
||||
pub struct Iter<'a, V: 'a> {
|
||||
front: usize,
|
||||
back: usize,
|
||||
iter: slice::Iter<'a, Option<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`]
|
||||
/// method. Usage of this iterator allows values which contain invalid UTF-8 code points unlike
|
||||
/// [`Values`].
|
||||
|
@ -639,7 +591,7 @@ impl<'a, V> DoubleEndedIterator for Iter<'a, V> {
|
|||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct OsValues<'a> {
|
||||
iter: Map<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> {
|
||||
|
|
|
@ -152,6 +152,7 @@ impl<'a> ArgGroup<'a> {
|
|||
/// assert!(m.is_present("flag"));
|
||||
/// ```
|
||||
/// [argument]: ./struct.Arg.html
|
||||
#[cfg_attr(feature = "lints", allow(should_assert_eq))]
|
||||
pub fn arg(mut self, n: &'a str) -> Self {
|
||||
assert!(self.name != n,
|
||||
"ArgGroup '{}' can not have same name as arg inside it",
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
// Std
|
||||
use std::ffi::OsString;
|
||||
|
||||
// Third Party
|
||||
use vec_map::VecMap;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MatchedArg {
|
||||
#[doc(hidden)]
|
||||
pub occurs: u64,
|
||||
#[doc(hidden)]
|
||||
pub vals: VecMap<OsString>,
|
||||
pub vals: Vec<OsString>,
|
||||
}
|
||||
|
||||
impl Default for MatchedArg {
|
||||
fn default() -> Self {
|
||||
MatchedArg {
|
||||
occurs: 1,
|
||||
vals: VecMap::new(),
|
||||
vals: Vec::with_capacity(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,20 +4,21 @@ use std::str::FromStr;
|
|||
|
||||
bitflags! {
|
||||
flags Flags: u16 {
|
||||
const REQUIRED = 0b00000000000001,
|
||||
const MULTIPLE = 0b00000000000010,
|
||||
const EMPTY_VALS = 0b00000000000100,
|
||||
const GLOBAL = 0b00000000001000,
|
||||
const HIDDEN = 0b00000000010000,
|
||||
const TAKES_VAL = 0b00000000100000,
|
||||
const USE_DELIM = 0b00000001000000,
|
||||
const NEXT_LINE_HELP = 0b00000010000000,
|
||||
const R_UNLESS_ALL = 0b00000100000000,
|
||||
const REQ_DELIM = 0b00001000000000,
|
||||
const DELIM_NOT_SET = 0b00010000000000,
|
||||
const HIDE_POS_VALS = 0b00100000000000,
|
||||
const ALLOW_TAC_VALS = 0b01000000000000,
|
||||
const REQUIRE_EQUALS = 0b10000000000000,
|
||||
const REQUIRED = 1 << 0,
|
||||
const MULTIPLE = 1 << 1,
|
||||
const EMPTY_VALS = 1 << 2,
|
||||
const GLOBAL = 1 << 3,
|
||||
const HIDDEN = 1 << 4,
|
||||
const TAKES_VAL = 1 << 5,
|
||||
const USE_DELIM = 1 << 6,
|
||||
const NEXT_LINE_HELP = 1 << 7,
|
||||
const R_UNLESS_ALL = 1 << 8,
|
||||
const REQ_DELIM = 1 << 9,
|
||||
const DELIM_NOT_SET = 1 << 10,
|
||||
const HIDE_POS_VALS = 1 << 11,
|
||||
const ALLOW_TAC_VALS = 1 << 12,
|
||||
const REQUIRE_EQUALS = 1 << 13,
|
||||
const LAST = 1 << 14,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +43,8 @@ impl ArgFlags {
|
|||
ValueDelimiterNotSet => DELIM_NOT_SET,
|
||||
HidePossibleValues => HIDE_POS_VALS,
|
||||
AllowLeadingHyphen => ALLOW_TAC_VALS,
|
||||
RequireEquals => REQUIRE_EQUALS
|
||||
RequireEquals => REQUIRE_EQUALS,
|
||||
Last => LAST
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,6 +84,9 @@ pub enum ArgSettings {
|
|||
AllowLeadingHyphen,
|
||||
/// Require options use `--option=val` syntax
|
||||
RequireEquals,
|
||||
/// Specifies that the arg is the last positional argument and may be accessed early via `--`
|
||||
/// syntax
|
||||
Last,
|
||||
#[doc(hidden)]
|
||||
RequiredUnlessAll,
|
||||
#[doc(hidden)]
|
||||
|
@ -106,6 +111,7 @@ impl FromStr for ArgSettings {
|
|||
"hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues),
|
||||
"allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen),
|
||||
"requireequals" => Ok(ArgSettings::RequireEquals),
|
||||
"last" => Ok(ArgSettings::Last),
|
||||
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +151,8 @@ mod test {
|
|||
ArgSettings::ValueDelimiterNotSet);
|
||||
assert_eq!("requireequals".parse::<ArgSettings>().unwrap(),
|
||||
ArgSettings::RequireEquals);
|
||||
assert_eq!("last".parse::<ArgSettings>().unwrap(),
|
||||
ArgSettings::Last);
|
||||
assert!("hahahaha".parse::<ArgSettings>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -527,6 +527,7 @@
|
|||
#![cfg_attr(feature = "lints", deny(warnings))]
|
||||
#![cfg_attr(feature = "lints", allow(cyclomatic_complexity))]
|
||||
#![cfg_attr(feature = "lints", allow(doc_markdown))]
|
||||
#![cfg_attr(feature = "lints", allow(explicit_iter_loop))]
|
||||
|
||||
#[cfg(feature = "suggestions")]
|
||||
extern crate strsim;
|
||||
|
|
|
@ -847,7 +847,7 @@ macro_rules! vec_remove_all {
|
|||
};
|
||||
}
|
||||
macro_rules! find_from {
|
||||
($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
let mut ret = None;
|
||||
for k in $matcher.arg_names() {
|
||||
if let Some(f) = find_by_name!($_self, &k, flags, iter) {
|
||||
|
@ -877,7 +877,7 @@ macro_rules! find_from {
|
|||
}
|
||||
|
||||
macro_rules! find_name_from {
|
||||
($_self:ident, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
let mut ret = None;
|
||||
for k in $matcher.arg_names() {
|
||||
if let Some(f) = find_by_name!($_self, &k, flags, iter) {
|
||||
|
@ -908,8 +908,8 @@ macro_rules! find_name_from {
|
|||
|
||||
// Finds an arg by name
|
||||
macro_rules! find_by_name {
|
||||
($_self:ident, $name:expr, $what:ident, $how:ident) => {
|
||||
$_self.$what.$how().find(|o| &o.b.name == $name)
|
||||
($p:expr, $name:expr, $what:ident, $how:ident) => {
|
||||
$p.$what.$how().find(|o| &o.b.name == $name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -989,7 +989,7 @@ macro_rules! find_subcmd {
|
|||
$_self.subcommands
|
||||
.iter()
|
||||
.find(|s| {
|
||||
s.p.meta.name == $sc ||
|
||||
&*s.p.meta.name == $sc ||
|
||||
(s.p.meta.aliases.is_some() &&
|
||||
s.p
|
||||
.meta
|
||||
|
@ -1029,12 +1029,18 @@ macro_rules! _shorts_longs {
|
|||
|
||||
macro_rules! arg_names {
|
||||
($_self:ident) => {{
|
||||
_names!($_self)
|
||||
_names!(@args $_self)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! sc_names {
|
||||
($_self:ident) => {{
|
||||
_names!(@sc $_self)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! _names {
|
||||
($_self:ident) => {{
|
||||
(@args $_self:ident) => {{
|
||||
$_self.flags
|
||||
.iter()
|
||||
.map(|f| &*f.b.name)
|
||||
|
@ -1043,4 +1049,14 @@ macro_rules! _names {
|
|||
.chain($_self.positionals.values()
|
||||
.map(|p| &*p.b.name)))
|
||||
}};
|
||||
(@sc $_self:ident) => {{
|
||||
$_self.subcommands
|
||||
.iter()
|
||||
.map(|s| &*s.p.meta.name)
|
||||
.chain($_self.subcommands
|
||||
.iter()
|
||||
.filter(|s| s.p.meta.aliases.is_some())
|
||||
.flat_map(|s| s.p.meta.aliases.as_ref().unwrap().iter().map(|&(n, _)| n)))
|
||||
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use fmt::Format;
|
|||
/// `Some("foo")`, whereas "blark" would yield `None`.
|
||||
#[cfg(feature = "suggestions")]
|
||||
#[cfg_attr(feature = "lints", allow(needless_lifetimes))]
|
||||
pub fn did_you_mean<'a, T, I>(v: &str, possible_values: I) -> Option<&'a str>
|
||||
pub fn did_you_mean<'a, T: ?Sized, I>(v: &str, possible_values: I) -> Option<&'a str>
|
||||
where T: AsRef<str> + 'a,
|
||||
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"))]
|
||||
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,
|
||||
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
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum DidYouMeanMessageStyle {
|
||||
/// Suggested value is a long flag
|
||||
LongFlag,
|
||||
|
|
|
@ -110,6 +110,113 @@ fn arg_required_else_help() {
|
|||
assert_eq!(err.kind, ErrorKind::MissingArgumentOrSubcommand);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "suggestions"))]
|
||||
#[test]
|
||||
fn infer_subcommands_fail_no_args() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert!(m.is_err(), "{:#?}", m.unwrap());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand);
|
||||
}
|
||||
|
||||
#[cfg(feature = "suggestions")]
|
||||
#[test]
|
||||
fn infer_subcommands_fail_no_args() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert!(m.is_err(), "{:#?}", m.unwrap());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidSubcommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_fail_with_args() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.arg(Arg::with_name("some"))
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "t"
|
||||
]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
assert_eq!(m.unwrap().value_of("some"), Some("t"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_fail_with_args2() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.arg(Arg::with_name("some"))
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
assert_eq!(m.unwrap().value_of("some"), Some("te"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_pass() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.get_matches_from(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_pass_close() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from(vec![
|
||||
"prog", "tes"
|
||||
]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "suggestions")]
|
||||
#[test]
|
||||
fn infer_subcommands_fail_suggestions() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "temps"
|
||||
]);
|
||||
assert!(m.is_err(), "{:#?}", m.unwrap());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidSubcommand);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "suggestions"))]
|
||||
#[test]
|
||||
fn infer_subcommands_fail_suggestions() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "temps"
|
||||
]);
|
||||
assert!(m.is_err(), "{:#?}", m.unwrap());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_bin_name() {
|
||||
let result = App::new("arg_required")
|
||||
|
@ -452,7 +559,9 @@ fn propagate_vals_down() {
|
|||
.setting(AppSettings::PropagateGlobalValuesDown)
|
||||
.arg(Arg::from_usage("[cmd] 'command to run'").global(true))
|
||||
.subcommand(SubCommand::with_name("foo"))
|
||||
.get_matches_from(vec!["myprog", "set", "foo"]);
|
||||
.get_matches_from_safe(vec!["myprog", "set", "foo"]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
let m = m.unwrap();
|
||||
assert_eq!(m.value_of("cmd"), Some("set"));
|
||||
let sub_m = m.subcommand_matches("foo").unwrap();
|
||||
assert_eq!(sub_m.value_of("cmd"), Some("set"));
|
||||
|
@ -464,7 +573,9 @@ fn allow_missing_positional() {
|
|||
.setting(AppSettings::AllowMissingPositional)
|
||||
.arg(Arg::from_usage("[src] 'some file'").default_value("src"))
|
||||
.arg_from_usage("<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("dest"), Some("file"));
|
||||
}
|
198
tests/help.rs
198
tests/help.rs
|
@ -36,6 +36,47 @@ SUBCOMMANDS:
|
|||
help Prints this message or the help of the given subcommand(s)
|
||||
subcmd tests subcommands";
|
||||
|
||||
static SC_NEGATES_REQS: &'static str = "prog 1.0
|
||||
|
||||
USAGE:
|
||||
prog --opt <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
|
||||
|
||||
clap-test v1.4.8
|
||||
|
@ -50,6 +91,19 @@ FLAGS:
|
|||
|
||||
some text that comes after the help";
|
||||
|
||||
static HIDDEN_ARGS: &'static str = "prog 1.0
|
||||
|
||||
USAGE:
|
||||
prog [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-f, --flag testing flags
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-o, --opt <FILE> tests options";
|
||||
|
||||
static SC_HELP: &'static str = "clap-test-subcmd 0.1
|
||||
Kevin K. <kbknapp@gmail.com>
|
||||
tests subcommands
|
||||
|
@ -236,6 +290,72 @@ FLAGS:
|
|||
-H, --help Print help information
|
||||
-v, --version Print version information";
|
||||
|
||||
static LAST_ARG: &'static str = "last 0.1
|
||||
|
||||
USAGE:
|
||||
last <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]
|
||||
fn help_short() {
|
||||
let m = App::new("test")
|
||||
|
@ -531,6 +651,40 @@ fn issue_760() {
|
|||
.takes_value(true));
|
||||
assert!(test::compare_output(app, "ctest --help", ISSUE_760, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_args() {
|
||||
let app = App::new("prog")
|
||||
.version("1.0")
|
||||
.args_from_usage("-f, --flag 'testing flags'
|
||||
-o, --opt [FILE] 'tests options'")
|
||||
.arg(Arg::with_name("pos").hidden(true));
|
||||
assert!(test::compare_output(app, "prog --help", HIDDEN_ARGS, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sc_negates_reqs() {
|
||||
let app = App::new("prog")
|
||||
.version("1.0")
|
||||
.setting(AppSettings::SubcommandsNegateReqs)
|
||||
.arg_from_usage("-o, --opt <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]
|
||||
fn issue_777_wrap_all_things() {
|
||||
let app = App::new("A app with a crazy very long long long name hahaha")
|
||||
|
@ -553,3 +707,47 @@ fn customize_version_and_help() {
|
|||
.version_message("Print version information");
|
||||
assert!(test::compare_output(app, "customize --help", CUSTOM_VERSION_AND_HELP, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_arg_mult_usage() {
|
||||
let app = App::new("last")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("TARGET").required(true).help("some"))
|
||||
.arg(Arg::with_name("CORPUS").help("some"))
|
||||
.arg(Arg::with_name("ARGS").multiple(true).last(true).help("some"));
|
||||
assert!(test::compare_output(app, "last --help", LAST_ARG, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_arg_mult_usage_req() {
|
||||
let app = App::new("last")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("TARGET").required(true).help("some"))
|
||||
.arg(Arg::with_name("CORPUS").help("some"))
|
||||
.arg(Arg::with_name("ARGS").multiple(true).last(true).required(true).help("some"));
|
||||
assert!(test::compare_output(app, "last --help", LAST_ARG_REQ, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_arg_mult_usage_req_with_sc() {
|
||||
let app = App::new("last")
|
||||
.version("0.1")
|
||||
.setting(AppSettings::SubcommandsNegateReqs)
|
||||
.arg(Arg::with_name("TARGET").required(true).help("some"))
|
||||
.arg(Arg::with_name("CORPUS").help("some"))
|
||||
.arg(Arg::with_name("ARGS").multiple(true).last(true).required(true).help("some"))
|
||||
.subcommand(SubCommand::with_name("test").about("some"));
|
||||
assert!(test::compare_output(app, "last --help", LAST_ARG_REQ_SC, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_arg_mult_usage_with_sc() {
|
||||
let app = App::new("last")
|
||||
.version("0.1")
|
||||
.setting(AppSettings::ArgsNegateSubcommands)
|
||||
.arg(Arg::with_name("TARGET").required(true).help("some"))
|
||||
.arg(Arg::with_name("CORPUS").help("some"))
|
||||
.arg(Arg::with_name("ARGS").multiple(true).last(true).help("some"))
|
||||
.subcommand(SubCommand::with_name("test").about("some"));
|
||||
assert!(test::compare_output(app, "last --help", LAST_ARG_SC, false));
|
||||
}
|
|
@ -5,7 +5,7 @@ use clap::{App, Arg};
|
|||
|
||||
include!("../clap-test.rs");
|
||||
|
||||
static HIDDEN_ARGS: &'static str = "test 1.3
|
||||
static HIDDEN_ARGS: &'static str = "test 1.4
|
||||
Kevin K.
|
||||
tests stuff
|
||||
|
||||
|
@ -25,9 +25,10 @@ fn hidden_args() {
|
|||
let app = App::new("test")
|
||||
.author("Kevin K.")
|
||||
.about("tests stuff")
|
||||
.version("1.3")
|
||||
.version("1.4")
|
||||
.args(&[Arg::from_usage("-f, --flag 'some flag'").hidden(true),
|
||||
Arg::from_usage("-F, --flag2 'some other flag'"),
|
||||
Arg::from_usage("--option [opt] 'some option'")]);
|
||||
Arg::from_usage("--option [opt] 'some option'"),
|
||||
Arg::with_name("DUMMY").required(false).hidden(true)]);
|
||||
assert!(test::compare_output(app, "test --help", HIDDEN_ARGS, false));
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ fn multiple_positional_one_required_usage_string() {
|
|||
.arg_from_usage("<FILE> 'some file'")
|
||||
.arg_from_usage("[FILES]... 'some 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]
|
||||
|
@ -211,3 +211,26 @@ fn missing_required_2() {
|
|||
assert!(r.is_err());
|
||||
assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_positional() {
|
||||
let r = App::new("test")
|
||||
.arg_from_usage("<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);
|
||||
}
|
Loading…
Reference in a new issue