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:
parent
ca4bc6a6fc
commit
d81843002a
10
.travis.yml
10
.travis.yml
@ -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:
|
||||||
|
19
CHANGELOG.md
19
CHANGELOG.md
@ -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
|
||||||
|
26
README.md
26
README.md
@ -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.
|
||||||
@ -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.
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
@ -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)
|
||||||
|
@ -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_;
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"}));
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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"}));
|
||||||
|
}
|
||||||
|
@ -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"));
|
||||||
|
}
|
||||||
|
@ -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());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user