1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-30 04:33:53 +00:00

To string and default option revamp (#242)

* First streaming version

* Using to_string instead

* Switching to new backend

* Moving to capture function for defaults

* Rename capture_default + _str

* defaultval -> default_str, added always_capture_default

* Fix style

* Adding tests and docs to readme

* Dropping macOS on Travis (supported through Azure)
This commit is contained in:
Henry Schreiner 2019-04-28 22:44:30 +02:00 committed by GitHub
parent ca4bc6a6fc
commit d81843002a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 527 additions and 146 deletions

View File

@ -118,16 +118,6 @@ matrix:
script: script:
- .ci/make_and_test.sh 11 - .ci/make_and_test.sh 11
# macOS and clang
- os: osx
compiler: clang
install:
- brew update
- echo 'brew "python"' > Brewfile
- echo 'brew "ccache"' >> Brewfile
- brew bundle
- python -m ensurepip --user
install: skip install: skip
script: script:

View File

@ -1,22 +1,30 @@
## Version 1.8: Sets and Flags (IN PROGRESS) ## Version 1.8: Sets and Flags (IN PROGRESS)
Set handling has been completely replaced by a new backend that works as a Validator. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. Set handling has been completely replaced by a new backend that works as a Validator. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. The final bool "defaults" flag when creating options has been replaced by `->capture_default_str()` (ending an old limitation in construction made this possible); the old method is still available but may be removed in future versions.
* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242]
* Added `.always_capture_default()` [#242]
* New `CLI::IsMember` validator replaces set validation [#222] * New `CLI::IsMember` validator replaces set validation [#222]
* IsMember also supports container of pairs, transform allows modification of result [#228] * IsMember also supports container of pairs, transform allows modification of result [#228]
* Much more powerful flags with different values [#211], general types [#235] * Much more powerful flags with different values [#211], general types [#235]
* `add_option` now supports bool due to unified bool handling [#211] * `add_option` now supports bool due to unified bool handling [#211]
* Support for composable unnamed subcommands [#216] * Support for composable unnamed subcommands [#216]
* Reparsing is better supported with `.remaining_for_passthrough()` [#265]
* Custom vector separator using `->delimiter(char)` [#209], [#221], [#240] * Custom vector separator using `->delimiter(char)` [#209], [#221], [#240]
* Validators added for IP4 addresses and positive numbers [#210] * Validators added for IP4 addresses and positive numbers [#210] and numbers [#262]
* Minimum required Boost for optional Optionals has been corrected to 1.61 [#226] * Minimum required Boost for optional Optionals has been corrected to 1.61 [#226]
* Positionals can stop options from being parsed with `app.positionals_at_end()` [#223] * Positionals can stop options from being parsed with `app.positionals_at_end()` [#223]
* Added `validate_positionals` [#262]
* Positional parsing is much more powerful [#251], duplicates supported []#247]
* Validators can be negated with `!` [#230], and now handle tname functions [#228] * Validators can be negated with `!` [#230], and now handle tname functions [#228]
* Better enum support and streaming helper [#233] and [#228] * Better enum support and streaming helper [#233] and [#228]
* Cleanup for shadow warnings [#232] * Cleanup for shadow warnings [#232]
* Better alignment on multiline descriptions [#269]
* Better support for aarch64 [#266]
> ### Converting from CLI11 1.7: > ### Converting from CLI11 1.7:
> >
> * `.add_option(..., true)` should be replaced by `.add_option(...)->capture_default_str()` or `app.option_defaults()->always_capture_default()` can be used
> * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))` > * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))`
> * The `_ignore_case` version of this can be replaced by adding `CLI::ignore_case` to the argument list in `IsMember` > * The `_ignore_case` version of this can be replaced by adding `CLI::ignore_case` to the argument list in `IsMember`
> * The `_ignore_underscore` version of this can be replaced by adding `CLI::ignore_underscore` to the argument list in `IsMember` > * The `_ignore_underscore` version of this can be replaced by adding `CLI::ignore_underscore` to the argument list in `IsMember`
@ -39,6 +47,13 @@ Set handling has been completely replaced by a new backend that works as a Valid
[#233]: https://github.com/CLIUtils/CLI11/pull/233 [#233]: https://github.com/CLIUtils/CLI11/pull/233
[#235]: https://github.com/CLIUtils/CLI11/pull/235 [#235]: https://github.com/CLIUtils/CLI11/pull/235
[#240]: https://github.com/CLIUtils/CLI11/pull/240 [#240]: https://github.com/CLIUtils/CLI11/pull/240
[#242]: https://github.com/CLIUtils/CLI11/pull/242
[#247]: https://github.com/CLIUtils/CLI11/pull/247
[#251]: https://github.com/CLIUtils/CLI11/pull/251
[#262]: https://github.com/CLIUtils/CLI11/pull/262
[#265]: https://github.com/CLIUtils/CLI11/pull/265
[#266]: https://github.com/CLIUtils/CLI11/pull/266
[#269]: https://github.com/CLIUtils/CLI11/pull/269
## Version 1.7.1: Quick patch ## Version 1.7.1: Quick patch

View File

@ -33,11 +33,11 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
- [Option types](#option-types) - [Option types](#option-types)
- [Example](#example) - [Example](#example)
- [Option options](#option-options) - [Option options](#option-options)
- [Validators](#validators) 🚧 - [Validators](#validators) 🚧
- [Transforming Validators](#transforming-validators)🚧 - [Transforming Validators](#transforming-validators) 🚧
- [Validator operations](#validator-operations)🚧 - [Validator operations](#validator-operations) 🚧
- [Custom Validators](#custom-validators)🚧 - [Custom Validators](#custom-validators) 🚧
- [Querying Validators](#querying-validators)🚧 - [Querying Validators](#querying-validators) 🚧
- [Getting Results](#getting-results) 🚧 - [Getting Results](#getting-results) 🚧
- [Subcommands](#subcommands) - [Subcommands](#subcommands)
- [Subcommand options](#subcommand-options) - [Subcommand options](#subcommand-options)
@ -74,7 +74,7 @@ An acceptable CLI parser library should be all of the following:
- Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. - Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability.
- C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. - C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+.
- Work on Linux, macOS, and Windows. - Work on Linux, macOS, and Windows.
- Well tested using [Travis][] (Linux and macOS) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][]. - Well tested using [Travis][] (Linux) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][].
- Clear help printing. - Clear help printing.
- Nice error messages. - Nice error messages.
- Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. - Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
@ -193,8 +193,7 @@ app.add_option(option_name, help_str="") // 🚧
app.add_option(option_name, app.add_option(option_name,
variable_to_bind_to, // bool, int, float, vector, 🚧 enum, or string-like, or anything with a defined conversion from a string variable_to_bind_to, // bool, int, float, vector, 🚧 enum, or string-like, or anything with a defined conversion from a string
help_string="", help_string="")
default=false)
app.add_option_function<type>(option_name, app.add_option_function<type>(option_name,
function <void(const type &value)>, // 🚧 int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string function <void(const type &value)>, // 🚧 int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string
@ -225,8 +224,7 @@ Option_group *app.add_option_group(name,description); // 🚧
-app.add_set(option_name, -app.add_set(option_name,
- variable_to_bind_to, // Same type as stored by set - variable_to_bind_to, // Same type as stored by set
- set_of_possible_options, // Set will be copied, ignores changes - set_of_possible_options, // Set will be copied, ignores changes
- help_string="", - help_string="")
- default=false)
-app.add_mutable_set(... // 🆕 Set can change later, keeps reference -app.add_mutable_set(... // 🆕 Set can change later, keeps reference
-app.add_set_ignore_case(... // String only -app.add_set_ignore_case(... // String only
-app.add_mutable_set_ignore_case(... // 🆕 String only -app.add_mutable_set_ignore_case(... // 🆕 String only
@ -236,7 +234,7 @@ Option_group *app.add_option_group(name,description); // 🚧
-app.add_mutable_set_ignore_case_underscore(... // 🆕 String only -app.add_mutable_set_ignore_case_underscore(... // 🆕 String only
``` ```
An option name must start with a alphabetic character, underscore, a number 🚧, '?'🚧, or '@'🚧. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`. An option name must start with a alphabetic character, underscore, a number 🚧, '?' 🚧, or '@' 🚧. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form.
The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid. The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid.
@ -285,16 +283,19 @@ Before parsing, you can set the following options:
- `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
- `->ignore_underscore()`: 🆕 Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character - `->ignore_underscore()`: 🆕 Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
- `->disable_flag_override()`: 🚧 from the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. - `->disable_flag_override()`: 🚧 From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->delimiter(char)`: 🚧 allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. - `->delimiter(char)`: 🚧 allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
- `->description(str)`: 🆕 Set/change the description. - `->description(str)`: 🆕 Set/change the description.
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy).
- `->check(std::string(const std::string &), validator_name="",validator_description="")`: 🚧 define a check function. The function should return a non empty string with the error message if the check fails - `->check(std::string(const std::string &), validator_name="",validator_description="")`: 🚧 Define a check function. The function should return a non empty string with the error message if the check fails
- `->check(Validator)`:🚧 Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. - `->check(Validator)`:🚧 Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
- `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. - `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
- `->transform(Validator)`: uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones. - `->transform(Validator)`: uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones.
- `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered. - `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered.
- `->configurable(false)`: Disable this option from being in a configuration file. - `->configurable(false)`: Disable this option from being in a configuration file.
`->capture_default_str()`: 🚧 Store the current value attached and display it in the help string.
`->default_function(std::string())`: 🚧 Advanced: Change the function that `capture_default_str()` uses.
`->always_capture_default()`: 🚧 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
@ -324,7 +325,7 @@ On the command line, options can be given as:
Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments.
If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included).
If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()`🚧 can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback. If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()` 🚧 can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback.
You can access a vector of pointers to the parsed options in the original order using `parse_order()`. You can access a vector of pointers to the parsed options in the original order using `parse_order()`.
If `--` is present in the command line that does not end an unlimited option, then If `--` is present in the command line that does not end an unlimited option, then
@ -349,20 +350,25 @@ CLI11 has several Validators built-in that perform some common checks
- `CLI::ValidIPV4`: 🚧 Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. - `CLI::ValidIPV4`: 🚧 Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
These Validators can be used by simply passing the name into the `check` or `transform` methods on an option These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
```cpp ```cpp
->check(CLI::ExistingFile); ->check(CLI::ExistingFile);
->check(CLI::Range(0,10)); ->check(CLI::Range(0,10));
``` ```
Validators can be merged using `&` and `|` and inverted using `!`🚧. For example Validators can be merged using `&` and `|` and inverted using `!` 🚧. For example:
```cpp ```cpp
->check(CLI::Range(0,10)|CLI::Range(20,30)); ->check(CLI::Range(0,10)|CLI::Range(20,30));
``` ```
will produce a check to ensure a value is between 0 and 10 or 20 and 30. will produce a check to ensure a value is between 0 and 10 or 20 and 30.
```cpp ```cpp
->check(!CLI::PositiveNumber); ->check(!CLI::PositiveNumber);
``` ```
will produce a check for a number less than 0;
will produce a check for a number less than 0.
##### Transforming Validators ##### Transforming Validators
There are a few built in Validators that let you transform values if used with the `transform` function. If they also do some checks then they can be used `check` but some may do nothing in that case. There are a few built in Validators that let you transform values if used with the `transform` function. If they also do some checks then they can be used `check` but some may do nothing in that case.
@ -390,7 +396,7 @@ of `Transformer`:
NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map` or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed. NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map` or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed.
##### Validator operations🚧 ##### Validator operations 🚧
Validators are copyable and have a few operations that can be performed on them to alter settings. Most of the built in Validators have a default description that is displayed in the help. This can be altered via `.description(validator_description)`. Validators are copyable and have a few operations that can be performed on them to alter settings. Most of the built in Validators have a default description that is displayed in the help. This can be altered via `.description(validator_description)`.
The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)` The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)`
The operation function of a Validator can be set via The operation function of a Validator can be set via
@ -406,7 +412,7 @@ The check can later be activated through
opt->get_validator("range")->active(); opt->get_validator("range")->active();
``` ```
##### Custom Validators🚧 ##### Custom Validators 🚧
A validator object with a custom function can be created via A validator object with a custom function can be created via
```cpp ```cpp
@ -427,10 +433,10 @@ opt->get_validator(name);
This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error. If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one. This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error. If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one.
Validators have a few functions to query the current values Validators have a few functions to query the current values
* `get_description()`:🚧 Will return a description string * `get_description()`: 🚧 Will return a description string
* `get_name()`:🚧 Will return the Validator name * `get_name()`: 🚧 Will return the Validator name
* `get_active()`:🚧 Will return the current active state, true if the Validator is active. * `get_active()`: 🚧 Will return the current active state, true if the Validator is active.
* `get_modifying()`: 🚧 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()`🚧 method, though it is recommended to let `check` and `transform` option methods manipulate it if needed. * `get_modifying()`: 🚧 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` 🚧 method, though it is recommended to let `check` and `transform` option methods manipulate it if needed.
#### Getting results #### Getting results
In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code: In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:
@ -467,19 +473,19 @@ There are several options that are supported on the main app and subcommands and
- `.allow_windows_style_options()`: 🆕 Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`. - `.allow_windows_style_options()`: 🆕 Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`.
- `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. - `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through.
- `.disable()`: 🚧 Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. - `.disable()`: 🚧 Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
- `.disabled_by_default()`:🚧 Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. - `.disabled_by_default()`: 🚧 Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others.
- `.enabled_by_default()`: 🚧 Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others. - `.enabled_by_default()`: 🚧 Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
- `.validate_positionals()`:🚧 Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments. - `.validate_positionals()`: 🚧 Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
- `.excludes(option_or_subcommand)`: 🚧 If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. - `.excludes(option_or_subcommand)`: 🚧 If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
- `.require_option()`: 🚧 Require 1 or more options or option groups be used. - `.require_option()`: 🚧 Require 1 or more options or option groups be used.
- `.require_option(N)`: 🚧 Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. - `.require_option(N)`: 🚧 Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
- `.require_option(min, max)`: 🚧 Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options. - `.require_option(min, max)`: 🚧 Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options.
- `.require_subcommand()`: Require 1 or more subcommands. - `.require_subcommand()`: Require 1 or more subcommands.
- `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
- `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
- `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand. - `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand.
- `.add_subcommand(shared_ptr<App>)`: 🚧 Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand. - `.add_subcommand(shared_ptr<App>)`: 🚧 Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
- `.remove_subcommand(App)`:🚧 Remove a subcommand from the app or subcommand. - `.remove_subcommand(App)`: 🚧 Remove a subcommand from the app or subcommand.
- `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
- `.get_subcommands(filter)`: The list of subcommands that match a particular filter function. - `.get_subcommands(filter)`: The list of subcommands that match a particular filter function.
- `.add_option_group(name="", description="")`: 🚧 Add an [option group](#option-groups) to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact. - `.add_option_group(name="", description="")`: 🚧 Add an [option group](#option-groups) to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact.
@ -579,7 +585,7 @@ This results in the subcommand being moved from its parent into the option group
Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups. Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups.
Option groups work well with `excludes` and `require_options` methods, as an Application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group. Option groups work well with `excludes` and `require_options` methods, as an Application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group.
The `CLI::TriggerOn`🚧 and `CLI::TriggerOff`🚧 methods are helper methods to allow the use of options/subcommands from one group to trigger another group on or off. The `CLI::TriggerOn` 🚧 and `CLI::TriggerOff` 🚧 methods are helper methods to allow the use of options/subcommands from one group to trigger another group on or off.
```cpp ```cpp
CLI::TriggerOn(group1_pointer, triggered_group); CLI::TriggerOn(group1_pointer, triggered_group);

View File

@ -28,8 +28,8 @@ class ConfigJSON : public CLI::Config {
j[name] = opt->results(); j[name] = opt->results();
// If the option has a default and is requested by optional argument // If the option has a default and is requested by optional argument
else if(default_also && !opt->get_defaultval().empty()) else if(default_also && !opt->get_default_str().empty())
j[name] = opt->get_defaultval(); j[name] = opt->get_default_str();
// Flag, one passed // Flag, one passed
} else if(opt->count() == 1) { } else if(opt->count() == 1) {

View File

@ -437,17 +437,33 @@ class App {
Option *add_option(std::string option_name, Option *add_option(std::string option_name,
callback_t option_callback, callback_t option_callback,
std::string option_description = "", std::string option_description = "",
bool defaulted = false) { bool defaulted = false,
Option myopt{option_name, option_description, option_callback, defaulted, this}; std::function<std::string()> func = {}) {
Option myopt{option_name, option_description, option_callback, this};
if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) {
return *v == myopt; return *v == myopt;
}) == std::end(options_)) { }) == std::end(options_)) {
options_.emplace_back(); options_.emplace_back();
Option_p &option = options_.back(); Option_p &option = options_.back();
option.reset(new Option(option_name, option_description, option_callback, defaulted, this)); option.reset(new Option(option_name, option_description, option_callback, this));
// Set the default string capture function
option->default_function(func);
// For compatibility with CLI11 1.7 and before, capture the default string here
if(defaulted)
option->capture_default_str();
// Transfer defaults to the new option
option_defaults_.copy_to(option.get()); option_defaults_.copy_to(option.get());
// Don't bother to capture if we already did
if(!defaulted && option->get_always_capture_default())
option->capture_default_str();
return option.get(); return option.get();
} else } else
throw OptionAlreadyAdded(myopt.get_name()); throw OptionAlreadyAdded(myopt.get_name());
} }
@ -456,12 +472,16 @@ class App {
template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name, Option *add_option(std::string option_name,
T &variable, ///< The variable to set T &variable, ///< The variable to set
std::string option_description = "") { std::string option_description = "",
bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); };
Option *opt = add_option(option_name, fun, option_description, false); Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
return std::string(CLI::detail::to_string(variable));
});
opt->type_name(detail::type_name<T>()); opt->type_name(detail::type_name<T>());
return opt; return opt;
} }
@ -471,7 +491,7 @@ class App {
const std::function<void(const T &)> &func, ///< the callback to execute const std::function<void(const T &)> &func, ///< the callback to execute
std::string option_description = "") { std::string option_description = "") {
CLI::callback_t fun = [func](CLI::results_t res) { auto fun = [func](CLI::results_t res) {
T variable; T variable;
bool result = detail::lexical_cast(res[0], variable); bool result = detail::lexical_cast(res[0], variable);
if(result) { if(result) {
@ -484,6 +504,7 @@ class App {
opt->type_name(detail::type_name<T>()); opt->type_name(detail::type_name<T>());
return opt; return opt;
} }
/// Add option with no description or variable assignment /// Add option with no description or variable assignment
Option *add_option(std::string option_name) { Option *add_option(std::string option_name) {
return add_option(option_name, CLI::callback_t(), std::string{}, false); return add_option(option_name, CLI::callback_t(), std::string{}, false);
@ -497,34 +518,14 @@ class App {
return add_option(option_name, CLI::callback_t(), option_description, false); return add_option(option_name, CLI::callback_t(), option_description, false);
} }
/// Add option for non-vectors with a default print, allow template to specify conversion type
template <typename T,
typename XC = T,
enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
Option *add_option(std::string option_name,
T &variable, ///< The variable to set
std::string option_description,
bool defaulted) {
static_assert(std::is_constructible<T, XC>::value, "assign type must be assignable from conversion type");
CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast<XC>(res[0], variable); };
Option *opt = add_option(option_name, fun, option_description, defaulted);
opt->type_name(detail::type_name<XC>());
if(defaulted) {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return opt;
}
/// Add option for vectors /// Add option for vectors
template <typename T> template <typename T>
Option *add_option(std::string option_name, Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set std::vector<T> &variable, ///< The variable vector to set
std::string option_description = "") { std::string option_description = "",
bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) { auto fun = [&variable](CLI::results_t res) {
bool retval = true; bool retval = true;
variable.clear(); variable.clear();
variable.reserve(res.size()); variable.reserve(res.size());
@ -536,35 +537,18 @@ class App {
return (!variable.empty()) && retval; return (!variable.empty()) && retval;
}; };
Option *opt = add_option(option_name, fun, option_description, false); auto default_function = [&variable]() {
opt->type_name(detail::type_name<T>())->type_size(-1); std::vector<std::string> defaults;
return opt; defaults.resize(variable.size());
} std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) {
return std::string(CLI::detail::to_string(val));
/// Add option for vectors with defaulted argument });
template <typename T> return std::string("[" + detail::join(defaults) + "]");
Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set
std::string option_description,
bool defaulted) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
bool retval = true;
variable.clear();
variable.reserve(res.size());
for(const auto &elem : res) {
variable.emplace_back();
retval &= detail::lexical_cast(elem, variable.back());
}
return (!variable.empty()) && retval;
}; };
Option *opt = add_option(option_name, fun, option_description, defaulted); Option *opt = add_option(option_name, fun, option_description, defaulted, default_function);
opt->type_name(detail::type_name<T>())->type_size(-1); opt->type_name(detail::type_name<T>())->type_size(-1);
if(defaulted)
opt->default_str("[" + detail::join(variable) + "]");
return opt; return opt;
} }
@ -995,13 +979,16 @@ class App {
return worked; return worked;
}; };
CLI::Option *opt = add_option(option_name, std::move(fun), std::move(option_description), defaulted); auto default_function = [&variable]() {
opt->type_name(label)->type_size(2);
if(defaulted) {
std::stringstream out; std::stringstream out;
out << variable; out << variable;
opt->default_str(out.str()); return out.str();
} };
CLI::Option *opt =
add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function);
opt->type_name(label)->type_size(2);
return opt; return opt;
} }

View File

@ -32,8 +32,8 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description,
value = detail::ini_join(opt->results()); value = detail::ini_join(opt->results());
// If the option has a default and is requested by optional argument // If the option has a default and is requested by optional argument
else if(default_also && !opt->get_defaultval().empty()) else if(default_also && !opt->get_default_str().empty())
value = opt->get_defaultval(); value = opt->get_default_str();
// Flag, one passed // Flag, one passed
} else if(opt->count() == 1) { } else if(opt->count() == 1) {
value = "true"; value = "true";

View File

@ -232,8 +232,8 @@ inline std::string Formatter::make_option_opts(const Option *opt) const {
if(opt->get_type_size() != 0) { if(opt->get_type_size() != 0) {
if(!opt->get_type_name().empty()) if(!opt->get_type_name().empty())
out << " " << get_label(opt->get_type_name()); out << " " << get_label(opt->get_type_name());
if(!opt->get_defaultval().empty()) if(!opt->get_default_str().empty())
out << "=" << opt->get_defaultval(); out << "=" << opt->get_default_str();
if(opt->get_expected() > 1) if(opt->get_expected() > 1)
out << " x " << opt->get_expected(); out << " x " << opt->get_expected();
if(opt->get_expected() == -1) if(opt->get_expected() == -1)

View File

@ -50,10 +50,16 @@ template <typename CRTP> class OptionBase {
/// Allow this option to be given in a configuration file /// Allow this option to be given in a configuration file
bool configurable_{true}; bool configurable_{true};
/// Disable overriding flag values with '=value' /// Disable overriding flag values with '=value'
bool disable_flag_override_{false}; bool disable_flag_override_{false};
/// Specify a delimiter character for vector arguments /// Specify a delimiter character for vector arguments
char delimiter_{'\0'}; char delimiter_{'\0'};
/// Automatically capture default value
bool always_capture_default_{false};
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
@ -66,6 +72,7 @@ template <typename CRTP> class OptionBase {
other->configurable(configurable_); other->configurable(configurable_);
other->disable_flag_override(disable_flag_override_); other->disable_flag_override(disable_flag_override_);
other->delimiter(delimiter_); other->delimiter(delimiter_);
other->always_capture_default(always_capture_default_);
other->multi_option_policy(multi_option_policy_); other->multi_option_policy(multi_option_policy_);
} }
@ -87,6 +94,11 @@ template <typename CRTP> class OptionBase {
/// Support Plumbum term /// Support Plumbum term
CRTP *mandatory(bool value = true) { return required(value); } CRTP *mandatory(bool value = true) { return required(value); }
CRTP *always_capture_default(bool value = true) {
always_capture_default_ = value;
return static_cast<CRTP *>(this);
}
// Getters // Getters
/// Get the group of this option /// Get the group of this option
@ -107,7 +119,12 @@ template <typename CRTP> class OptionBase {
/// The status of configurable /// The status of configurable
bool get_disable_flag_override() const { return disable_flag_override_; } bool get_disable_flag_override() const { return disable_flag_override_; }
/// Get the current delimeter char
char get_delimiter() const { return delimiter_; } char get_delimiter() const { return delimiter_; }
/// Return true if this will automatically capture the default value for help printing
bool get_always_capture_default() const { return always_capture_default_; }
/// The status of the multi option policy /// The status of the multi option policy
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
@ -219,16 +236,16 @@ class Option : public OptionBase<Option> {
/// The description for help strings /// The description for help strings
std::string description_; std::string description_;
/// A human readable default value, usually only set if default is true in creation /// A human readable default value, either manually set, captured, or captured by default
std::string defaultval_; std::string default_str_;
/// A human readable type value, set when App creates this /// A human readable type value, set when App creates this
/// ///
/// This is a lambda function so "types" can be dynamic, such as when a set prints its contents. /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
std::function<std::string()> type_name_{[]() { return std::string(); }}; std::function<std::string()> type_name_{[]() { return std::string(); }};
/// True if this option has a default /// Run this function to capture a default (ignore if empty)
bool default_{false}; std::function<std::string()> default_function_;
///@} ///@}
/// @name Configuration /// @name Configuration
@ -277,10 +294,8 @@ class Option : public OptionBase<Option> {
Option(std::string option_name, Option(std::string option_name,
std::string option_description, std::string option_description,
std::function<bool(results_t)> callback, std::function<bool(results_t)> callback,
bool defaulted,
App *parent) App *parent)
: description_(std::move(option_description)), default_(defaulted), parent_(parent), : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
callback_(std::move(callback)) {
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name)); std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
} }
@ -306,6 +321,7 @@ class Option : public OptionBase<Option> {
/// Set the number of expected arguments (Flags don't use this) /// Set the number of expected arguments (Flags don't use this)
Option *expected(int value) { Option *expected(int value) {
// Break if this is a flag // Break if this is a flag
if(type_size_ == 0) if(type_size_ == 0)
throw IncorrectConstruction::SetFlag(get_name(true, true)); throw IncorrectConstruction::SetFlag(get_name(true, true));
@ -532,8 +548,12 @@ class Option : public OptionBase<Option> {
/// The set of options excluded /// The set of options excluded
std::set<Option *> get_excludes() const { return excludes_; } std::set<Option *> get_excludes() const { return excludes_; }
/// The default value (for help printing) DEPRECATED Use get_default_str() instead
CLI11_DEPRECATED("Use get_default_str() instead")
std::string get_defaultval() const { return default_str_; }
/// The default value (for help printing) /// The default value (for help printing)
std::string get_defaultval() const { return defaultval_; } std::string get_default_str() const { return default_str_; }
/// Get the callback function /// Get the callback function
callback_t get_callback() const { return callback_; } callback_t get_callback() const { return callback_; }
@ -571,9 +591,6 @@ class Option : public OptionBase<Option> {
((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1));
} }
/// True if this has a default value
int get_default() const { return default_; }
/// True if the argument can be given directly /// True if the argument can be given directly
bool get_positional() const { return pname_.length() > 0; } bool get_positional() const { return pname_.length() > 0; }
@ -843,7 +860,7 @@ class Option : public OptionBase<Option> {
void results(T &output) const { void results(T &output) const {
bool retval; bool retval;
if(results_.empty()) { if(results_.empty()) {
retval = detail::lexical_cast(defaultval_, output); retval = detail::lexical_cast(default_str_, output);
} else if(results_.size() == 1) { } else if(results_.size() == 1) {
retval = detail::lexical_cast(results_[0], output); retval = detail::lexical_cast(results_[0], output);
} else { } else {
@ -917,13 +934,27 @@ class Option : public OptionBase<Option> {
return this; return this;
} }
/// Set the default value string representation /// Set a capture function for the default. Mostly used by App.
Option *default_str(std::string val) { Option *default_function(const std::function<std::string()> &func) {
defaultval_ = val; default_function_ = func;
return this; return this;
} }
/// Set the default value string representation and evaluate /// Capture the default value from the original value (if it can be captured)
Option *capture_default_str() {
if(default_function_) {
default_str_ = default_function_();
}
return this;
}
/// Set the default value string representation (does not change the contained value)
Option *default_str(std::string val) {
default_str_ = val;
return this;
}
/// Set the default value string representation and evaluate into the bound value
Option *default_val(std::string val) { Option *default_val(std::string val) {
default_str(val); default_str(val);
auto old_results = results_; auto old_results = results_;

View File

@ -46,6 +46,7 @@
#endif #endif
#if CLI11_BOOST_OPTIONAL #if CLI11_BOOST_OPTIONAL
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#endif #endif
// [CLI11:verbatim] // [CLI11:verbatim]

View File

@ -124,6 +124,43 @@ struct pair_adaptor<
} }
}; };
// Check for streamability
// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
template <typename S, typename T> class is_streamable {
template <typename SS, typename TT>
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<S, T>(0))::value;
};
/// Convert an object to a string (directly forward if this can become a string)
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
return std::forward<T>(value);
}
/// Convert an object to a string (streaming must be supported for that type)
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && is_streamable<std::stringstream, T>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&value) {
std::stringstream stream;
stream << value;
return stream.str();
}
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_streamable<std::stringstream, T>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&) {
return std::string{};
}
// Type name print // Type name print
/// Was going to be based on /// Was going to be based on

View File

@ -674,8 +674,8 @@ TEST_F(TApp, DefaultOpts) {
int i = 3; int i = 3;
std::string s = "HI"; std::string s = "HI";
app.add_option("-i,i", i, "", false); app.add_option("-i,i", i);
app.add_option("-s,s", s, "", true); app.add_option("-s,s", s)->capture_default_str(); // Used to be different
args = {"-i2", "9"}; args = {"-i2", "9"};
@ -1511,7 +1511,7 @@ TEST_F(TApp, VectorDefaultedFixedString) {
std::vector<std::string> strvec{"one"}; std::vector<std::string> strvec{"one"};
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3); CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
EXPECT_EQ(3, opt->get_expected()); EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"}; args = {"--string", "mystring", "mystring2", "mystring3"};
@ -1523,7 +1523,7 @@ TEST_F(TApp, VectorDefaultedFixedString) {
TEST_F(TApp, DefaultedResult) { TEST_F(TApp, DefaultedResult) {
std::string sval = "NA"; std::string sval = "NA";
int ival; int ival;
auto opts = app.add_option("--string", sval, "", true); auto opts = app.add_option("--string", sval)->capture_default_str();
auto optv = app.add_option("--val", ival); auto optv = app.add_option("--val", ival);
args = {}; args = {};
run(); run();
@ -1947,7 +1947,7 @@ TEST_F(TApp, FallthroughParents) {
TEST_F(TApp, OptionWithDefaults) { TEST_F(TApp, OptionWithDefaults) {
int someint = 2; int someint = 2;
app.add_option("-a", someint, "", true); app.add_option("-a", someint)->capture_default_str();
args = {"-a1", "-a2"}; args = {"-a1", "-a2"};
@ -2091,7 +2091,7 @@ TEST_F(TApp, CustomUserSepParse) {
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(','); app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
} }
@ -2131,7 +2131,7 @@ TEST_F(TApp, CustomUserSepParse2) {
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(','); app.add_option("--idx", vals, "")->delimiter(',')->capture_default_str();
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
} }
@ -2185,13 +2185,13 @@ TEST_F(TApp, CustomUserSepParse4) {
std::vector<int> vals; std::vector<int> vals;
args = {"--idx", "1, 2"}; args = {"--idx", "1, 2"};
auto opt = app.add_option("--idx", vals)->delimiter(','); auto opt = app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(','); app.add_option("--idx", vals)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
} }
@ -2207,7 +2207,7 @@ TEST_F(TApp, CustomUserSepParse5) {
app.remove_option(opt); app.remove_option(opt);
args = {"this", "is", "a", "test"}; args = {"this", "is", "a", "test"};
app.add_option("bar", bar, "bar", true); app.add_option("bar", bar, "bar")->capture_default_str();
run(); run();
EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"})); EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"}));
} }

View File

@ -728,4 +728,36 @@ TEST(ValidatorTests, ValidatorDefaults) {
EXPECT_EQ(V2.get_description(), "check"); EXPECT_EQ(V2.get_description(), "check");
EXPECT_TRUE(V2.get_active()); EXPECT_TRUE(V2.get_active());
EXPECT_TRUE(V2.get_modifying()); EXPECT_TRUE(V2.get_modifying());
// This class only support streaming in, not out
}
class Unstreamable {
private:
int x_ = -1;
public:
Unstreamable() {}
int get_x() const { return x_; }
void set_x(int x) { x_ = x; }
};
std::istream &operator>>(std::istream &in, Unstreamable &value) {
int x;
in >> x;
value.set_x(x);
return in;
}
TEST_F(TApp, MakeUnstreamableOptiions) {
Unstreamable value;
app.add_option("--value", value);
// This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--value2", value, "", false);
std::vector<Unstreamable> values;
app.add_option("--values", values);
// This used to fail to build, since it tries to stream from Unstreamable
app.add_option("--values2", values, "", false);
} }

View File

@ -1,5 +1,10 @@
#include "app_helper.hpp" #include "app_helper.hpp"
#include "gmock/gmock.h"
using ::testing::HasSubstr;
using ::testing::Not;
TEST(Deprecated, Emtpy) { TEST(Deprecated, Emtpy) {
// No deprecated features at this time. // No deprecated features at this time.
EXPECT_TRUE(true); EXPECT_TRUE(true);
@ -326,3 +331,212 @@ TEST_F(TApp, AddRemoveSetItemsNoCase) {
args = {"--type2", "TYpE2"}; args = {"--type2", "TYpE2"};
EXPECT_THROW(run(), CLI::ValidationError); EXPECT_THROW(run(), CLI::ValidationError);
} }
TEST(THelp, Defaults) {
CLI::App app{"My prog"};
int one{1}, two{2};
app.add_option("--one", one, "Help for one", true);
app.add_option("--set", two, "Help for set", true)->check(CLI::IsMember({2, 3, 4}));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("--one"));
EXPECT_THAT(help, HasSubstr("--set"));
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("=2"));
EXPECT_THAT(help, HasSubstr("2,3,4"));
}
TEST(THelp, VectorOpts) {
CLI::App app{"My prog"};
std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x, "", true);
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=[1,2] ..."));
}
TEST(THelp, SetLower) {
CLI::App app{"My prog"};
std::string def{"One"};
app.add_option("--set", def, "Help for set", true)->check(CLI::IsMember({"oNe", "twO", "THREE"}));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("--set"));
EXPECT_THAT(help, HasSubstr("=One"));
EXPECT_THAT(help, HasSubstr("oNe"));
EXPECT_THAT(help, HasSubstr("twO"));
EXPECT_THAT(help, HasSubstr("THREE"));
}
TEST(THelp, ChangingSetDefaulted) {
CLI::App app;
std::set<int> vals{1, 2, 3};
int val = 2;
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, Not(HasSubstr("4")));
vals.insert(4);
vals.erase(1);
help = app.help();
EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4"));
}
TEST(THelp, ChangingCaselessSetDefaulted) {
CLI::App app;
std::set<std::string> vals{"1", "2", "3"};
std::string val = "2";
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, Not(HasSubstr("4")));
vals.insert("4");
vals.erase("1");
help = app.help();
EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4"));
}
TEST_F(TApp, DefaultOpts) {
int i = 3;
std::string s = "HI";
app.add_option("-i,i", i, "", false);
app.add_option("-s,s", s, "", true);
args = {"-i2", "9"};
run();
EXPECT_EQ(1u, app.count("i"));
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(2, i);
EXPECT_EQ("9", s);
}
TEST_F(TApp, VectorDefaultedFixedString) {
std::vector<std::string> strvec{"one"};
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3);
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, DefaultedResult) {
std::string sval = "NA";
int ival;
auto opts = app.add_option("--string", sval, "", true);
auto optv = app.add_option("--val", ival);
args = {};
run();
EXPECT_EQ(sval, "NA");
std::string nString;
opts->results(nString);
EXPECT_EQ(nString, "NA");
int newIval;
EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
optv->default_str("442");
optv->results(newIval);
EXPECT_EQ(newIval, 442);
}
TEST_F(TApp, OptionWithDefaults) {
int someint = 2;
app.add_option("-a", someint, "", true);
args = {"-a1", "-a2"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
// #209
TEST_F(TApp, CustomUserSepParse) {
std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,3"};
auto opt = app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
std::vector<int> vals2;
// check that the results vector gets the results in the same way
opt->results(vals2);
EXPECT_EQ(vals2, vals);
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
}
// #209
TEST_F(TApp, CustomUserSepParse2) {
std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,"};
auto opt = app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt);
app.add_option("--idx", vals, "", true)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
}
//
// #209
TEST_F(TApp, CustomUserSepParse4) {
std::vector<int> vals;
args = {"--idx", "1, 2"};
auto opt = app.add_option("--idx", vals, "", true)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt);
app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<int>({1, 2}));
}
// #218
TEST_F(TApp, CustomUserSepParse5) {
std::vector<std::string> bar;
args = {"this", "is", "a", "test"};
auto opt = app.add_option("bar", bar, "bar");
run();
EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"}));
app.remove_option(opt);
args = {"this", "is", "a", "test"};
app.add_option("bar", bar, "bar", true);
run();
EXPECT_EQ(bar, std::vector<std::string>({"this", "is", "a", "test"}));
}

View File

@ -119,7 +119,7 @@ TEST(THelp, MultiOpts) {
TEST(THelp, VectorOpts) { TEST(THelp, VectorOpts) {
CLI::App app{"My prog"}; CLI::App app{"My prog"};
std::vector<int> x = {1, 2}; std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x, "", true); app.add_option("-q,--quick", x)->capture_default_str();
std::string help = app.help(); std::string help = app.help();
@ -299,8 +299,8 @@ TEST(THelp, IntDefaults) {
CLI::App app{"My prog"}; CLI::App app{"My prog"};
int one{1}, two{2}; int one{1}, two{2};
app.add_option("--one", one, "Help for one", true); app.add_option("--one", one, "Help for one")->capture_default_str();
app.add_option("--set", two, "Help for set", true)->check(CLI::IsMember({2, 3, 4})); app.add_option("--set", two, "Help for set")->capture_default_str()->check(CLI::IsMember({2, 3, 4}));
std::string help = app.help(); std::string help = app.help();
@ -313,9 +313,10 @@ TEST(THelp, IntDefaults) {
TEST(THelp, SetLower) { TEST(THelp, SetLower) {
CLI::App app{"My prog"}; CLI::App app{"My prog"};
app.option_defaults()->always_capture_default();
std::string def{"One"}; std::string def{"One"};
app.add_option("--set", def, "Help for set", true)->check(CLI::IsMember({"oNe", "twO", "THREE"})); app.add_option("--set", def, "Help for set")->check(CLI::IsMember({"oNe", "twO", "THREE"}));
std::string help = app.help(); std::string help = app.help();
@ -866,7 +867,7 @@ TEST(THelp, ChangingSetDefaulted) {
std::set<int> vals{1, 2, 3}; std::set<int> vals{1, 2, 3};
int val = 2; int val = 2;
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals)); app.add_option("--val", val, "")->check(CLI::IsMember(&vals))->capture_default_str();
std::string help = app.help(); std::string help = app.help();
@ -881,6 +882,7 @@ TEST(THelp, ChangingSetDefaulted) {
EXPECT_THAT(help, Not(HasSubstr("1"))); EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4")); EXPECT_THAT(help, HasSubstr("4"));
} }
TEST(THelp, ChangingCaselessSet) { TEST(THelp, ChangingCaselessSet) {
CLI::App app; CLI::App app;
@ -904,10 +906,11 @@ TEST(THelp, ChangingCaselessSet) {
TEST(THelp, ChangingCaselessSetDefaulted) { TEST(THelp, ChangingCaselessSetDefaulted) {
CLI::App app; CLI::App app;
app.option_defaults()->always_capture_default();
std::set<std::string> vals{"1", "2", "3"}; std::set<std::string> vals{"1", "2", "3"};
std::string val = "2"; std::string val = "2";
app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals, CLI::ignore_case)); app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help(); std::string help = app.help();
@ -922,3 +925,50 @@ TEST(THelp, ChangingCaselessSetDefaulted) {
EXPECT_THAT(help, Not(HasSubstr("1"))); EXPECT_THAT(help, Not(HasSubstr("1")));
EXPECT_THAT(help, HasSubstr("4")); EXPECT_THAT(help, HasSubstr("4"));
} }
// New defaults tests (1.8)
TEST(THelp, ChangingDefaults) {
CLI::App app;
std::vector<int> x = {1, 2};
CLI::Option *opt = app.add_option("-q,--quick", x);
x = {3, 4};
opt->capture_default_str();
x = {5, 6};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=[3,4] ..."));
}
TEST(THelp, ChangingDefaultsWithAutoCapture) {
CLI::App app;
app.option_defaults()->always_capture_default();
std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x);
x = {3, 4};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=[1,2] ..."));
}
TEST(THelp, FunctionDefaultString) {
CLI::App app;
std::vector<int> x = {1, 2};
CLI::Option *opt = app.add_option("-q,--quick", x);
opt->default_function([]() { return std::string("Powerful"); });
opt->capture_default_str();
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("INT=Powerful"));
}

View File

@ -7,6 +7,24 @@
#include <fstream> #include <fstream>
#include <string> #include <string>
class NotStreamable {};
class Streamable {};
std::ostream &operator<<(std::ostream &out, const Streamable &) { return out << "Streamable"; }
TEST(TypeTools, Streaming) {
EXPECT_EQ(CLI::detail::to_string(NotStreamable{}), "");
EXPECT_EQ(CLI::detail::to_string(Streamable{}), "Streamable");
EXPECT_EQ(CLI::detail::to_string(5), "5");
EXPECT_EQ(CLI::detail::to_string("string"), std::string("string"));
EXPECT_EQ(CLI::detail::to_string(std::string("string")), std::string("string"));
}
TEST(Split, SimpleByToken) { TEST(Split, SimpleByToken) {
auto out = CLI::detail::split("one.two.three", '.'); auto out = CLI::detail::split("one.two.three", '.');
ASSERT_EQ(3u, out.size()); ASSERT_EQ(3u, out.size());