mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 20:23:55 +00:00
add transformer and checkedTransformer (#239)
* add transform and checkedTransform tests add Transformer and CheckedTransformer validators * Eliminate the Validator description string, some code cleanup add tests Make Validators a full Object and remove friend, move to descriptions instead of overriding type name. update validators to actually merge the type strings and use all validators in the type outputs rework join so it works without the start variable, allow some forwarding references in the validator types, some tests for non-copyable maps, and transforms merge the search function and enable use of member search function, make the pair adapters forwarding instead of copying * add a few more tests and documentation fix some gcc 4.7 issues and add a few more test cases and more parts of the README Work on ReadMe and add Bound validator to clamp values * updates to README.md * Add some more in TOC of README and fix style in Option.hpp
This commit is contained in:
parent
6aa546fc42
commit
e9934e058d
157
README.md
157
README.md
@ -31,11 +31,17 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
|
||||
- [Usage](#usage)
|
||||
- [Adding options](#adding-options)
|
||||
- [Option types](#option-types)
|
||||
- [Example](#example)
|
||||
- [Option options](#option-options)
|
||||
- [Validators](#validators) 🚧
|
||||
- [Transforming Validators](#transforming-validators)🚧
|
||||
- [Validator operations](#validator-operations)🚧
|
||||
- [Custom Validators](#custom-validators)🚧
|
||||
- [Querying Validators](#querying-validators)🚧
|
||||
- [Getting Results](#getting-results) 🚧
|
||||
- [Subcommands](#subcommands)
|
||||
- [Subcommand options](#subcommand-options)
|
||||
- [Option Groups](#option-groups) 🚧
|
||||
- [Option groups](#option-groups) 🚧
|
||||
- [Configuration file](#configuration-file)
|
||||
- [Inheriting defaults](#inheriting-defaults)
|
||||
- [Formatting](#formatting)
|
||||
@ -245,7 +251,7 @@ alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; th
|
||||
example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `variable_to_bind_to` is anything but an integer value the
|
||||
default behavior is to take the last value given, while if `variable_to_bind_to` is an integer type the behavior will be to sum
|
||||
all the given arguments and return the result. This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited).
|
||||
The default value can be any value For example if you wished to define a numerical flag
|
||||
The default value can be any value. For example if you wished to define a numerical flag
|
||||
```cpp
|
||||
app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag") // 🚧
|
||||
```
|
||||
@ -282,33 +288,15 @@ Before parsing, you can set the following 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
|
||||
- `->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).
|
||||
- `->check(CLI::IsMember(...))`: 🚧 Require an option be a member of a given set. See below for options.
|
||||
- `->transform(CLI::IsMember(...))`: 🚧 Require an option be a member of a given set or map. Can change the parse. See below for options.
|
||||
- `->check(CLI::ExistingFile)`: Requires that the file exists if given.
|
||||
- `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
|
||||
- `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists.
|
||||
- `->check(CLI::NonexistentPath)`: Requires that the path does not exist.
|
||||
- `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
|
||||
- `->check(CLI::PositiveNumber)`: 🚧 Requires the number be greater or equal to 0
|
||||
- `->check(CLI::ValidIPV4)`: 🚧 Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`
|
||||
- `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
|
||||
- `->each(void(std::string)>`: Run this function on each value received, as it is received.
|
||||
- `->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.
|
||||
- `->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.
|
||||
- `->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.
|
||||
|
||||
|
||||
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check 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 `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort). Validators can also be inverted with `!` such as `->check(!CLI::ExistingFile)` which would check that a file doesn't exist.
|
||||
|
||||
🚧 The `IsMember` validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The type should be convertible from a string. You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set.
|
||||
After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`.
|
||||
Here are some examples
|
||||
of `IsMember`:
|
||||
|
||||
- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
|
||||
- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
|
||||
- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
|
||||
- `CLI::IsMember(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the key.
|
||||
- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
|
||||
- Note that you can combine validators with `|`, and only the matched validation will modify the output in transform. The first `IsMember` is the only one that will print in help, though the error message will include all Validators.
|
||||
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Each 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 in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
|
||||
|
||||
On the command line, options can be given as:
|
||||
|
||||
@ -340,8 +328,111 @@ You can access a vector of pointers to the parsed options in the original order
|
||||
If `--` is present in the command line that does not end an unlimited option, then
|
||||
everything after that is positional only.
|
||||
|
||||
#### Getting results {#getting-results} 🚧
|
||||
#### Validators
|
||||
Validators are structures to check or modify inputs, they can be used to verify that an input meets certain criteria or transform it into another value. They are added through an options `check` or `transform` functions. They differences between those two function are that checks do not modify the input whereas transforms can and are executed before any Validators added through `check`.
|
||||
|
||||
CLI11 has several Validators built in that perform some common checks
|
||||
|
||||
- `CLI::IsMember(...)`: 🚧 Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details.
|
||||
- `CLI::Transformer(...)`: 🚧 Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details.
|
||||
- `CLI::CheckedTransformer(...)`: 🚧 Modify the input using a map, and Require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details.
|
||||
- `CLI::ExistingFile`: Requires that the file exists if given.
|
||||
- `CLI::ExistingDirectory`: Requires that the directory exists.
|
||||
- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
|
||||
- `CLI::NonexistentPath`: Requires that the path does not exist.
|
||||
- `CLI::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
|
||||
- `CLI::Bounded(min,max)`: 🚧 Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0. Will produce an Error if conversion is not possible.
|
||||
- `CLI::PositiveNumber`: 🚧 Requires the number be greater or equal to 0.
|
||||
- `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
|
||||
```cpp
|
||||
->check(CLI::ExistingFile);
|
||||
```
|
||||
```cpp
|
||||
->check(CLI::Range(0,10));
|
||||
```
|
||||
|
||||
Validators can be merged using `&` and `|` and inverted using `!`
|
||||
such as
|
||||
```cpp
|
||||
->check(CLI::Range(0,10)|CLI::Range(20,30));
|
||||
```
|
||||
will produce a check if a value is between 0 and 10 or 20 and 30.
|
||||
```cpp
|
||||
->check(!CLI::PositiveNumber);
|
||||
```
|
||||
will produce a check for a number less than 0;
|
||||
|
||||
##### 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.
|
||||
* 🚧 `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max, it will fail if the value cannot be converted and produce a `ValidationError`
|
||||
* 🚧 The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string, You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set. The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters.
|
||||
After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and CLI::ignore_space. These all work on strings but it is possible to define functions that work on other types.
|
||||
Here are some examples
|
||||
of `IsMember`:
|
||||
|
||||
* `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
|
||||
* `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
|
||||
* `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
|
||||
* `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`.
|
||||
* `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
|
||||
* 🚧 The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, or one of the expected output values, and if not will generate a ValidationError. A Transformer placed using `check` will not do anything.
|
||||
After specifying a map of options, you can also specify "filter" just like in CLI::IsMember.
|
||||
Here are some examples (`Transfomer` and `CheckedTransformer` are interchangeable in the examples)
|
||||
of `Transformer`:
|
||||
|
||||
* `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
|
||||
|
||||
* `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind.
|
||||
* `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
|
||||
* `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
|
||||
|
||||
NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a specialized search 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🚧
|
||||
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 operation function of a Validator can be set via
|
||||
`.operation(std::function<std::string(std::string &>)`. The `.active()` function can activate or deactivate a Validator from the operation.
|
||||
All the functions return a Validator reference allowing them to be chained. For example
|
||||
|
||||
```cpp
|
||||
opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));
|
||||
```
|
||||
will specify a check on an option with a name "range", but deactivate it for the time being.
|
||||
The check can later be activated through
|
||||
```cpp
|
||||
opt->get_validator("range")->active();
|
||||
```
|
||||
|
||||
##### Custom Validators🚧
|
||||
|
||||
A validator object with a custom function can be created via
|
||||
```cpp
|
||||
CLI::Validator(std::function<std::string(std::string &>,validator_description,validator_name="");
|
||||
```
|
||||
or if the operation function is set later they can be created with
|
||||
```cpp
|
||||
CLI::Validator(validator_description);
|
||||
```
|
||||
|
||||
It is also possible to create a subclass of `CLI::Validator`, in which case it can also set a custom description function, and operation function.
|
||||
|
||||
##### Querying Validators 🚧
|
||||
Once loaded into an Option, a pointer to a named Validator can be retrieved via
|
||||
```cpp
|
||||
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.
|
||||
|
||||
Validators have a few functions to query the current values
|
||||
* `get_description()`:🚧 Will return a description string
|
||||
* `get_name()`:🚧 Will return the Validator name
|
||||
* `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 `non_modifying()`🚧 method, though it is recommended to let check and transform function manipulate it if needed.
|
||||
|
||||
#### 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:
|
||||
|
||||
- `results()`: Retrieves a vector of strings with all the results in the order they were given.
|
||||
@ -365,7 +456,7 @@ You are allowed to throw `CLI::Success` in the callbacks.
|
||||
Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
|
||||
|
||||
🚧 Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
|
||||
Nameless subcommands function a similarly to groups in the main `App`. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed.
|
||||
Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed.
|
||||
|
||||
#### Subcommand options
|
||||
|
||||
@ -378,16 +469,16 @@ There are several options that are supported on the main app and subcommands and
|
||||
- `.disable()`: 🚧 Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
|
||||
- `.exludes(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(N)`: 🚧 Require `N` options or option groups if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 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 is unlimited.
|
||||
- `.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 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.
|
||||
- `.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.
|
||||
- `.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.
|
||||
- `.add_option_group(name="", description="")`: 🚧 Add an option group 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.
|
||||
- `.get_parent()`: Get the parent App or nullptr if called on master App.
|
||||
- `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched
|
||||
- `.get_option_no_throw(name)`: 🚧 Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available.
|
||||
@ -399,7 +490,7 @@ There are several options that are supported on the main app and subcommands and
|
||||
- `.parsed()`: True if this subcommand was given on the command line.
|
||||
- `.count()`: Returns the number of times the subcommand was called
|
||||
- `.count(option_name)`: Returns the number of times a particular option was called
|
||||
- `.count_all()`: 🚧 Returns the total number of arguments a particular subcommand had, on the master App it returns the total number of processed commands
|
||||
- `.count_all()`: 🚧 Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands
|
||||
- `.name(name)`: Add or change the name.
|
||||
- `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point.
|
||||
- `.allow_extras()`: Do not throw an error if extra arguments are left over.
|
||||
@ -414,7 +505,7 @@ There are several options that are supported on the main app and subcommands and
|
||||
|
||||
> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
|
||||
|
||||
#### Option Groups 🚧 {#option-groups}
|
||||
#### Option groups 🚧
|
||||
|
||||
The method
|
||||
```cpp
|
||||
|
@ -109,8 +109,8 @@ set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION
|
||||
add_cli_exe(validators validators.cpp)
|
||||
add_test(NAME validators_help COMMAND validators --help)
|
||||
set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION
|
||||
" -f,--file FILE File name"
|
||||
" -v,--value INT in [3 - 6] Value in range")
|
||||
" -f,--file TEXT:FILE[\\r\\n\\t ]+File name"
|
||||
" -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range")
|
||||
add_test(NAME validators_file COMMAND validators --file nonex.xxx)
|
||||
set_property(TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION
|
||||
"--file: File does not exist: nonex.xxx"
|
||||
@ -155,7 +155,7 @@ add_cli_exe(enum enum.cpp)
|
||||
add_test(NAME enum_pass COMMAND enum -l 1)
|
||||
add_test(NAME enum_fail COMMAND enum -l 4)
|
||||
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
|
||||
"--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}")
|
||||
"--level: Check 4 value in {" "FAILED")
|
||||
|
||||
add_cli_exe(digit_args digit_args.cpp)
|
||||
add_test(NAME digit_args COMMAND digit_args -h)
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <map>
|
||||
|
||||
enum class Level : int { High, Medium, Low };
|
||||
|
||||
@ -7,11 +6,14 @@ int main(int argc, char **argv) {
|
||||
CLI::App app;
|
||||
|
||||
Level level;
|
||||
std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}};
|
||||
|
||||
// specify string->value mappings
|
||||
std::vector<std::pair<std::string, Level>> map{
|
||||
{"high", Level::High}, {"medium", Level::Medium}, {"low", Level::Low}};
|
||||
// checked Transform does the translation and checks the results are either in one of the strings or one of the
|
||||
// translations already
|
||||
app.add_option("-l,--level", level, "Level settings")
|
||||
->required()
|
||||
->transform(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low}));
|
||||
->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
|
@ -244,7 +244,7 @@ class Option : public OptionBase<Option> {
|
||||
int expected_{1};
|
||||
|
||||
/// A list of validators to run on each value parsed
|
||||
std::vector<std::function<std::string(std::string &)>> validators_;
|
||||
std::vector<Validator> validators_;
|
||||
|
||||
/// A list of options that are required with this option
|
||||
std::set<Option *> needs_;
|
||||
@ -331,59 +331,70 @@ class Option : public OptionBase<Option> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Adds a validator with a built in type name
|
||||
Option *check(const Validator &validator) {
|
||||
std::function<std::string(std::string &)> func = validator.func;
|
||||
validators_.emplace_back([func](const std::string &value) {
|
||||
/// Throw away changes to the string value
|
||||
std::string ignore_changes_value = value;
|
||||
return func(ignore_changes_value);
|
||||
});
|
||||
if(validator.tname_function)
|
||||
type_name_fn(validator.tname_function);
|
||||
else if(!validator.tname.empty())
|
||||
type_name(validator.tname);
|
||||
/// Adds a Validator with a built in type name
|
||||
Option *check(Validator validator, std::string validator_name = "") {
|
||||
validator.non_modifying();
|
||||
validators_.push_back(std::move(validator));
|
||||
if(!validator_name.empty())
|
||||
validators_.front().name(validator_name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Adds a validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
|
||||
Option *check(std::function<std::string(const std::string &)> validator) {
|
||||
validators_.emplace_back(validator);
|
||||
/// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
|
||||
Option *check(std::function<std::string(const std::string &)> validator,
|
||||
std::string validator_description = "",
|
||||
std::string validator_name = "") {
|
||||
validators_.emplace_back(validator, std::move(validator_description), std::move(validator_name));
|
||||
validators_.back().non_modifying();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Adds a transforming validator with a built in type name
|
||||
Option *transform(const Validator &validator) {
|
||||
validators_.emplace_back(validator.func);
|
||||
if(validator.tname_function)
|
||||
type_name_fn(validator.tname_function);
|
||||
else if(!validator.tname.empty())
|
||||
type_name(validator.tname);
|
||||
Option *transform(Validator validator, std::string validator_name = "") {
|
||||
validators_.insert(validators_.begin(), std::move(validator));
|
||||
if(!validator_name.empty())
|
||||
validators_.front().name(validator_name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Adds a validator-like function that can change result
|
||||
Option *transform(std::function<std::string(std::string)> func) {
|
||||
validators_.emplace_back([func](std::string &inout) {
|
||||
try {
|
||||
inout = func(inout);
|
||||
} catch(const ValidationError &e) {
|
||||
return std::string(e.what());
|
||||
}
|
||||
return std::string();
|
||||
});
|
||||
Option *transform(std::function<std::string(std::string)> func,
|
||||
std::string transform_description = "",
|
||||
std::string transform_name = "") {
|
||||
validators_.insert(validators_.begin(),
|
||||
Validator(
|
||||
[func](std::string &val) {
|
||||
val = func(val);
|
||||
return std::string{};
|
||||
},
|
||||
std::move(transform_description),
|
||||
std::move(transform_name)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
|
||||
Option *each(std::function<void(std::string)> func) {
|
||||
validators_.emplace_back([func](std::string &inout) {
|
||||
validators_.emplace_back(
|
||||
[func](std::string &inout) {
|
||||
func(inout);
|
||||
return std::string();
|
||||
});
|
||||
return std::string{};
|
||||
},
|
||||
std::string{});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Get a named Validator
|
||||
Validator *get_validator(const std::string &validator_name = "") {
|
||||
for(auto &validator : validators_) {
|
||||
if(validator_name == validator.get_name()) {
|
||||
return &validator;
|
||||
}
|
||||
}
|
||||
if((validator_name.empty()) && (!validators_.empty())) {
|
||||
return &(validators_.front());
|
||||
}
|
||||
throw OptionNotFound(std::string("Validator ") + validator_name + " Not Found");
|
||||
}
|
||||
/// Sets required options
|
||||
Option *needs(Option *opt) {
|
||||
auto tup = needs_.insert(opt);
|
||||
@ -663,7 +674,7 @@ class Option : public OptionBase<Option> {
|
||||
try {
|
||||
err_msg = vali(result);
|
||||
} catch(const ValidationError &err) {
|
||||
throw ValidationError(err.what(), get_name());
|
||||
throw ValidationError(get_name(), err.what());
|
||||
}
|
||||
|
||||
if(!err_msg.empty())
|
||||
@ -938,8 +949,19 @@ class Option : public OptionBase<Option> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Get the typename for this option
|
||||
std::string get_type_name() const { return type_name_(); }
|
||||
/// Get the full typename for this option
|
||||
std::string get_type_name() const {
|
||||
std::string full_type_name = type_name_();
|
||||
if(!validators_.empty()) {
|
||||
for(auto &validator : validators_) {
|
||||
std::string vtype = validator.get_description();
|
||||
if(!vtype.empty()) {
|
||||
full_type_name += ":" + vtype;
|
||||
}
|
||||
}
|
||||
}
|
||||
return full_type_name;
|
||||
}
|
||||
|
||||
private:
|
||||
int _add_result(std::string &&result) {
|
||||
|
@ -57,15 +57,27 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
/// simple utility to convert various types to a string
|
||||
template <typename T> inline std::string as_string(const T &v) {
|
||||
std::ostringstream s;
|
||||
s << v;
|
||||
return s.str();
|
||||
}
|
||||
// if the data type is already a string just forward it
|
||||
template <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>
|
||||
inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) {
|
||||
return std::forward<T>(v);
|
||||
}
|
||||
|
||||
/// Simple function to join a string
|
||||
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
size_t start = 0;
|
||||
for(const auto &i : v) {
|
||||
if(start++ > 0)
|
||||
s << delim;
|
||||
s << i;
|
||||
auto beg = std::begin(v);
|
||||
auto end = std::end(v);
|
||||
if(beg != end)
|
||||
s << *beg++;
|
||||
while(beg != end) {
|
||||
s << delim << *beg++;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
@ -76,11 +88,12 @@ template <typename T,
|
||||
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
|
||||
std::string join(const T &v, Callable func, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
size_t start = 0;
|
||||
for(const auto &i : v) {
|
||||
if(start++ > 0)
|
||||
s << delim;
|
||||
s << func(i);
|
||||
auto beg = std::begin(v);
|
||||
auto end = std::end(v);
|
||||
if(beg != end)
|
||||
s << func(*beg++);
|
||||
while(beg != end) {
|
||||
s << delim << func(*beg++);
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
@ -58,6 +58,9 @@ template <typename T> struct is_shared_ptr : std::false_type {};
|
||||
/// Check to see if something is a shared pointer (True if really a shared pointer)
|
||||
template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
|
||||
|
||||
/// Check to see if something is a shared pointer (True if really a shared pointer)
|
||||
template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {};
|
||||
|
||||
/// Check to see if something is copyable pointer
|
||||
template <typename T> struct is_copyable_ptr {
|
||||
static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
|
||||
@ -71,7 +74,7 @@ template <> struct IsMemberType<const char *> { using type = std::string; };
|
||||
|
||||
namespace detail {
|
||||
|
||||
// These are utilites for IsMember
|
||||
// These are utilities for IsMember
|
||||
|
||||
/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
|
||||
/// pointer_traits<T> be valid.
|
||||
@ -84,16 +87,20 @@ template <typename T> struct element_type {
|
||||
/// the container
|
||||
template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
|
||||
|
||||
/// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing.
|
||||
/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
|
||||
template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
|
||||
using value_type = typename T::value_type;
|
||||
using first_type = typename std::remove_const<value_type>::type;
|
||||
using second_type = typename std::remove_const<value_type>::type;
|
||||
|
||||
/// Get the first value (really just the underlying value)
|
||||
template <typename Q> static first_type first(Q &&value) { return value; }
|
||||
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
|
||||
return std::forward<Q>(pair_value);
|
||||
}
|
||||
/// Get the second value (really just the underlying value)
|
||||
template <typename Q> static second_type second(Q &&value) { return value; }
|
||||
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
|
||||
return std::forward<Q>(pair_value);
|
||||
}
|
||||
};
|
||||
|
||||
/// Adaptor for map-like structure (true version, must have key_type and mapped_type).
|
||||
@ -108,9 +115,13 @@ struct pair_adaptor<
|
||||
using second_type = typename std::remove_const<typename value_type::second_type>::type;
|
||||
|
||||
/// Get the first value (really just the underlying value)
|
||||
template <typename Q> static first_type first(Q &&value) { return value.first; }
|
||||
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) {
|
||||
return std::get<0>(std::forward<Q>(pair_value));
|
||||
}
|
||||
/// Get the second value (really just the underlying value)
|
||||
template <typename Q> static second_type second(Q &&value) { return value.second; }
|
||||
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) {
|
||||
return std::get<1>(std::forward<Q>(pair_value));
|
||||
}
|
||||
};
|
||||
|
||||
// Type name print
|
||||
@ -158,7 +169,7 @@ constexpr const char *type_name() {
|
||||
|
||||
// Lexical cast
|
||||
|
||||
/// convert a flag into an integer value typically binary flags
|
||||
/// Convert a flag into an integer value typically binary flags
|
||||
inline int64_t to_flag_value(std::string val) {
|
||||
static const std::string trueString("true");
|
||||
static const std::string falseString("false");
|
||||
@ -247,7 +258,7 @@ bool lexical_cast(std::string input, T &output) {
|
||||
}
|
||||
}
|
||||
|
||||
/// boolean values
|
||||
/// Boolean values
|
||||
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
try {
|
||||
@ -283,7 +294,7 @@ bool lexical_cast(std::string input, T &output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// enumerations
|
||||
/// Enumerations
|
||||
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
typename std::underlying_type<T>::type val;
|
||||
@ -308,7 +319,7 @@ bool lexical_cast(std::string input, T &output) {
|
||||
return !is.fail() && !is.rdbuf()->in_avail();
|
||||
}
|
||||
|
||||
/// sum a vector of flag representations
|
||||
/// Sum a vector of flag representations
|
||||
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
|
||||
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
|
||||
/// common true and false strings then uses stoll to convert the rest for summing
|
||||
@ -322,7 +333,7 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
|
||||
output = (count > 0) ? static_cast<T>(count) : T{0};
|
||||
}
|
||||
|
||||
/// sum a vector of flag representations
|
||||
/// Sum a vector of flag representations
|
||||
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
|
||||
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
|
||||
/// common true and false strings then uses stoll to convert the rest for summing
|
||||
|
@ -32,50 +32,113 @@ class Option;
|
||||
|
||||
///
|
||||
class Validator {
|
||||
friend Option;
|
||||
|
||||
protected:
|
||||
/// This is the type name, if empty the type name will not be changed
|
||||
std::string tname;
|
||||
|
||||
/// This is the type function, if empty the tname will be used
|
||||
std::function<std::string()> tname_function;
|
||||
/// This is the description function, if empty the description_ will be used
|
||||
std::function<std::string()> desc_function_{[]() { return std::string{}; }};
|
||||
|
||||
/// This it the base function that is to be called.
|
||||
/// Returns a string error message if validation fails.
|
||||
std::function<std::string(std::string &)> func;
|
||||
std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
|
||||
/// The name for search purposes of the Validator
|
||||
std::string name_;
|
||||
/// Enable for Validator to allow it to be disabled if need be
|
||||
bool active_{true};
|
||||
/// specify that a validator should not modify the input
|
||||
bool non_modifying_{false};
|
||||
|
||||
public:
|
||||
/// This is the required operator for a validator - provided to help
|
||||
Validator() = default;
|
||||
/// Construct a Validator with just the description string
|
||||
explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {}
|
||||
// Construct Validator from basic information
|
||||
Validator(std::function<std::string(std::string &)> op, std::string validator_desc, std::string validator_name = "")
|
||||
: desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)),
|
||||
name_(std::move(validator_name)) {}
|
||||
/// Set the Validator operation function
|
||||
Validator &operation(std::function<std::string(std::string &)> op) {
|
||||
func_ = std::move(op);
|
||||
return *this;
|
||||
}
|
||||
/// This is the required operator for a Validator - provided to help
|
||||
/// users (CLI11 uses the member `func` directly)
|
||||
std::string operator()(std::string &str) const { return func(str); };
|
||||
std::string operator()(std::string &str) const {
|
||||
std::string retstring;
|
||||
if(active_) {
|
||||
if(non_modifying_) {
|
||||
std::string value = str;
|
||||
retstring = func_(value);
|
||||
} else {
|
||||
retstring = func_(str);
|
||||
}
|
||||
}
|
||||
return retstring;
|
||||
};
|
||||
|
||||
/// This is the required operator for a validator - provided to help
|
||||
/// This is the required operator for a Validator - provided to help
|
||||
/// users (CLI11 uses the member `func` directly)
|
||||
std::string operator()(const std::string &str) const {
|
||||
std::string value = str;
|
||||
return func(value);
|
||||
return (active_) ? func_(value) : std::string{};
|
||||
};
|
||||
|
||||
/// Specify the type string
|
||||
Validator &description(std::string validator_desc) {
|
||||
desc_function_ = [validator_desc]() { return validator_desc; };
|
||||
return *this;
|
||||
}
|
||||
/// Generate type description information for the Validator
|
||||
std::string get_description() const {
|
||||
if(active_) {
|
||||
return desc_function_();
|
||||
}
|
||||
return std::string{};
|
||||
}
|
||||
/// Specify the type string
|
||||
Validator &name(std::string validator_name) {
|
||||
name_ = std::move(validator_name);
|
||||
return *this;
|
||||
}
|
||||
/// Get the name of the Validator
|
||||
const std::string &get_name() const { return name_; }
|
||||
/// Specify whether the Validator is active or not
|
||||
Validator &active(bool active_val = true) {
|
||||
active_ = active_val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Specify whether the Validator can be modifying or not
|
||||
Validator &non_modifying(bool no_modify = true) {
|
||||
non_modifying_ = no_modify;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Get a boolean if the validator is active
|
||||
bool get_active() const { return active_; }
|
||||
|
||||
/// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input
|
||||
bool get_modifying() const { return !non_modifying_; }
|
||||
|
||||
/// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
|
||||
/// same.
|
||||
Validator operator&(const Validator &other) const {
|
||||
Validator newval;
|
||||
newval.tname = (tname == other.tname ? tname : "");
|
||||
newval.tname_function = tname_function;
|
||||
|
||||
newval._merge_description(*this, other, " AND ");
|
||||
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & filename)> &f1 = func;
|
||||
const std::function<std::string(std::string & filename)> &f2 = other.func;
|
||||
const std::function<std::string(std::string & filename)> &f1 = func_;
|
||||
const std::function<std::string(std::string & filename)> &f2 = other.func_;
|
||||
|
||||
newval.func = [f1, f2](std::string &input) {
|
||||
newval.func_ = [f1, f2](std::string &input) {
|
||||
std::string s1 = f1(input);
|
||||
std::string s2 = f2(input);
|
||||
if(!s1.empty() && !s2.empty())
|
||||
return s1 + " AND " + s2;
|
||||
return std::string("(") + s1 + ") AND (" + s2 + ")";
|
||||
else
|
||||
return s1 + s2;
|
||||
};
|
||||
|
||||
newval.active_ = (active_ & other.active_);
|
||||
return newval;
|
||||
}
|
||||
|
||||
@ -83,48 +146,68 @@ class Validator {
|
||||
/// same.
|
||||
Validator operator|(const Validator &other) const {
|
||||
Validator newval;
|
||||
newval.tname = (tname == other.tname ? tname : "");
|
||||
newval.tname_function = tname_function;
|
||||
|
||||
newval._merge_description(*this, other, " OR ");
|
||||
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string &)> &f1 = func;
|
||||
const std::function<std::string(std::string &)> &f2 = other.func;
|
||||
const std::function<std::string(std::string &)> &f1 = func_;
|
||||
const std::function<std::string(std::string &)> &f2 = other.func_;
|
||||
|
||||
newval.func = [f1, f2](std::string &input) {
|
||||
newval.func_ = [f1, f2](std::string &input) {
|
||||
std::string s1 = f1(input);
|
||||
std::string s2 = f2(input);
|
||||
if(s1.empty() || s2.empty())
|
||||
return std::string();
|
||||
else
|
||||
return s1 + " OR " + s2;
|
||||
return std::string("(") + s1 + ") OR (" + s2 + ")";
|
||||
};
|
||||
newval.active_ = (active_ & other.active_);
|
||||
return newval;
|
||||
}
|
||||
|
||||
/// Create a validator that fails when a given validator succeeds
|
||||
Validator operator!() const {
|
||||
Validator newval;
|
||||
std::string typestring = tname;
|
||||
if(tname.empty()) {
|
||||
typestring = tname_function();
|
||||
}
|
||||
newval.tname = "NOT " + typestring;
|
||||
|
||||
std::string failString = "check " + typestring + " succeeded improperly";
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & res)> &f1 = func;
|
||||
|
||||
newval.func = [f1, failString](std::string &test) -> std::string {
|
||||
std::string s1 = f1(test);
|
||||
if(s1.empty())
|
||||
return failString;
|
||||
else
|
||||
return std::string();
|
||||
const std::function<std::string()> &dfunc1 = desc_function_;
|
||||
newval.desc_function_ = [dfunc1]() {
|
||||
auto str = dfunc1();
|
||||
return (!str.empty()) ? std::string("NOT ") + str : std::string{};
|
||||
};
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & res)> &f1 = func_;
|
||||
|
||||
newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
|
||||
std::string s1 = f1(test);
|
||||
if(s1.empty()) {
|
||||
return std::string("check ") + dfunc1() + " succeeded improperly";
|
||||
} else
|
||||
return std::string{};
|
||||
};
|
||||
newval.active_ = active_;
|
||||
return newval;
|
||||
}
|
||||
|
||||
private:
|
||||
void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
|
||||
|
||||
const std::function<std::string()> &dfunc1 = val1.desc_function_;
|
||||
const std::function<std::string()> &dfunc2 = val2.desc_function_;
|
||||
|
||||
desc_function_ = [=]() {
|
||||
std::string f1 = dfunc1();
|
||||
std::string f2 = dfunc2();
|
||||
if((f1.empty()) || (f2.empty())) {
|
||||
return f1 + f2;
|
||||
}
|
||||
return std::string("(") + f1 + ")" + merger + "(" + f2 + ")";
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Class wrapping some of the accessors of Validator
|
||||
class CustomValidator : public Validator {
|
||||
public:
|
||||
};
|
||||
// The implementation of the built in validators is using the Validator class;
|
||||
// the user is only expected to use the const (static) versions (since there's no setup).
|
||||
// Therefore, this is in detail.
|
||||
@ -133,9 +216,8 @@ namespace detail {
|
||||
/// Check for an existing file (returns error message if check fails)
|
||||
class ExistingFileValidator : public Validator {
|
||||
public:
|
||||
ExistingFileValidator() {
|
||||
tname = "FILE";
|
||||
func = [](std::string &filename) {
|
||||
ExistingFileValidator() : Validator("FILE") {
|
||||
func_ = [](std::string &filename) {
|
||||
struct stat buffer;
|
||||
bool exist = stat(filename.c_str(), &buffer) == 0;
|
||||
bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
|
||||
@ -152,9 +234,8 @@ class ExistingFileValidator : public Validator {
|
||||
/// Check for an existing directory (returns error message if check fails)
|
||||
class ExistingDirectoryValidator : public Validator {
|
||||
public:
|
||||
ExistingDirectoryValidator() {
|
||||
tname = "DIR";
|
||||
func = [](std::string &filename) {
|
||||
ExistingDirectoryValidator() : Validator("DIR") {
|
||||
func_ = [](std::string &filename) {
|
||||
struct stat buffer;
|
||||
bool exist = stat(filename.c_str(), &buffer) == 0;
|
||||
bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
|
||||
@ -171,9 +252,8 @@ class ExistingDirectoryValidator : public Validator {
|
||||
/// Check for an existing path
|
||||
class ExistingPathValidator : public Validator {
|
||||
public:
|
||||
ExistingPathValidator() {
|
||||
tname = "PATH";
|
||||
func = [](std::string &filename) {
|
||||
ExistingPathValidator() : Validator("PATH(existing)") {
|
||||
func_ = [](std::string &filename) {
|
||||
struct stat buffer;
|
||||
bool const exist = stat(filename.c_str(), &buffer) == 0;
|
||||
if(!exist) {
|
||||
@ -187,9 +267,8 @@ class ExistingPathValidator : public Validator {
|
||||
/// Check for an non-existing path
|
||||
class NonexistentPathValidator : public Validator {
|
||||
public:
|
||||
NonexistentPathValidator() {
|
||||
tname = "PATH";
|
||||
func = [](std::string &filename) {
|
||||
NonexistentPathValidator() : Validator("PATH(non-existing)") {
|
||||
func_ = [](std::string &filename) {
|
||||
struct stat buffer;
|
||||
bool exist = stat(filename.c_str(), &buffer) == 0;
|
||||
if(exist) {
|
||||
@ -203,9 +282,8 @@ class NonexistentPathValidator : public Validator {
|
||||
/// Validate the given string is a legal ipv4 address
|
||||
class IPV4Validator : public Validator {
|
||||
public:
|
||||
IPV4Validator() {
|
||||
tname = "IPV4";
|
||||
func = [](std::string &ip_addr) {
|
||||
IPV4Validator() : Validator("IPV4") {
|
||||
func_ = [](std::string &ip_addr) {
|
||||
auto result = CLI::detail::split(ip_addr, '.');
|
||||
if(result.size() != 4) {
|
||||
return "Invalid IPV4 address must have four parts " + ip_addr;
|
||||
@ -229,9 +307,8 @@ class IPV4Validator : public Validator {
|
||||
/// Validate the argument is a number and greater than or equal to 0
|
||||
class PositiveNumber : public Validator {
|
||||
public:
|
||||
PositiveNumber() {
|
||||
tname = "POSITIVE";
|
||||
func = [](std::string &number_str) {
|
||||
PositiveNumber() : Validator("POSITIVE") {
|
||||
func_ = [](std::string &number_str) {
|
||||
int number;
|
||||
if(!detail::lexical_cast(number_str, number)) {
|
||||
return "Failed parsing number " + number_str;
|
||||
@ -276,12 +353,12 @@ class Range : public Validator {
|
||||
template <typename T> Range(T min, T max) {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<T>() << " in [" << min << " - " << max << "]";
|
||||
description(out.str());
|
||||
|
||||
tname = out.str();
|
||||
func = [min, max](std::string &input) {
|
||||
func_ = [min, max](std::string &input) {
|
||||
T val;
|
||||
detail::lexical_cast(input, val);
|
||||
if(val < min || val > max)
|
||||
bool converted = detail::lexical_cast(input, val);
|
||||
if((!converted) || (val < min || val > max))
|
||||
return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max);
|
||||
|
||||
return std::string();
|
||||
@ -292,18 +369,126 @@ class Range : public Validator {
|
||||
template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {}
|
||||
};
|
||||
|
||||
/// Produce a bounded range (factory). Min and max are inclusive.
|
||||
class Bound : public Validator {
|
||||
public:
|
||||
/// This bounds a value with min and max inclusive.
|
||||
///
|
||||
/// Note that the constructor is templated, but the struct is not, so C++17 is not
|
||||
/// needed to provide nice syntax for Range(a,b).
|
||||
template <typename T> Bound(T min, T max) {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<T>() << " bounded to [" << min << " - " << max << "]";
|
||||
description(out.str());
|
||||
|
||||
func_ = [min, max](std::string &input) {
|
||||
T val;
|
||||
bool converted = detail::lexical_cast(input, val);
|
||||
if(!converted) {
|
||||
return "Value " + input + " could not be converted";
|
||||
}
|
||||
if(val < min)
|
||||
input = detail::as_string(min);
|
||||
else if(val > max)
|
||||
input = detail::as_string(max);
|
||||
|
||||
return std::string();
|
||||
};
|
||||
}
|
||||
|
||||
/// Range of one value is 0 to value
|
||||
template <typename T> explicit Bound(T max) : Bound(static_cast<T>(0), max) {}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T, enable_if_t<is_copyable_ptr<T>::value, detail::enabler> = detail::dummy>
|
||||
template <typename T,
|
||||
enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
|
||||
auto smart_deref(T value) -> decltype(*value) {
|
||||
return *value;
|
||||
}
|
||||
|
||||
template <typename T, enable_if_t<!is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> T smart_deref(T value) {
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
|
||||
typename std::remove_reference<T>::type &smart_deref(T &value) {
|
||||
return value;
|
||||
}
|
||||
/// Generate a string representation of a set
|
||||
template <typename T> std::string generate_set(const T &set) {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
std::string out(1, '{');
|
||||
out.append(detail::join(detail::smart_deref(set),
|
||||
[](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
|
||||
","));
|
||||
out.push_back('}');
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Generate a string representation of a map
|
||||
template <typename T> std::string generate_map(const T &map) {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
std::string out(1, '{');
|
||||
out.append(detail::join(detail::smart_deref(map),
|
||||
[](const iteration_type_t &v) {
|
||||
return detail::as_string(detail::pair_adaptor<element_t>::first(v)) + "->" +
|
||||
detail::as_string(detail::pair_adaptor<element_t>::second(v));
|
||||
},
|
||||
","));
|
||||
out.push_back('}');
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename> struct sfinae_true : std::true_type {};
|
||||
/// Function to check for the existence of a member find function which presumably is more efficient than looping over
|
||||
/// everything
|
||||
template <typename T, typename V>
|
||||
static auto test_find(int) -> sfinae_true<decltype(std::declval<T>().find(std::declval<V>()))>;
|
||||
template <typename, typename V> static auto test_find(long) -> std::false_type;
|
||||
|
||||
template <typename T, typename V> struct has_find : decltype(test_find<T, V>(0)) {};
|
||||
|
||||
/// A search function
|
||||
template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
|
||||
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
|
||||
return (detail::pair_adaptor<element_t>::first(v) == val);
|
||||
});
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// A search function that uses the built in find function
|
||||
template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
|
||||
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = setref.find(val);
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// A search function with a filter function
|
||||
template <typename T, typename V>
|
||||
auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
|
||||
-> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
// do the potentially faster first search
|
||||
auto res = search(set, val);
|
||||
if((res.first) || (!(filter_function))) {
|
||||
return res;
|
||||
}
|
||||
// if we haven't found it do the longer linear search with all the element translations
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
|
||||
V a = detail::pair_adaptor<element_t>::first(v);
|
||||
a = filter_function(a);
|
||||
return (a == val);
|
||||
});
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Verify items are in a set
|
||||
class IsMember : public Validator {
|
||||
public:
|
||||
@ -315,7 +500,7 @@ class IsMember : public Validator {
|
||||
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// This checks to see if an item is in a set (empty function)
|
||||
template <typename T> explicit IsMember(T set) : IsMember(std::move(set), nullptr) {}
|
||||
template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
@ -333,76 +518,198 @@ class IsMember : public Validator {
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
tname_function = [set]() {
|
||||
std::stringstream out;
|
||||
out << "{";
|
||||
int i = 0; // I don't like counters like this
|
||||
for(const auto &v : detail::smart_deref(set))
|
||||
out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v);
|
||||
out << "}";
|
||||
return out.str();
|
||||
};
|
||||
desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
|
||||
|
||||
// This is the function that validates
|
||||
// It stores a copy of the set pointer-like, so shared_ptr will stay alive
|
||||
func = [set, filter_fn](std::string &input) {
|
||||
for(const auto &v : detail::smart_deref(set)) {
|
||||
local_item_t a = detail::pair_adaptor<element_t>::first(v);
|
||||
func_ = [set, filter_fn](std::string &input) {
|
||||
local_item_t b;
|
||||
if(!detail::lexical_cast(input, b))
|
||||
if(!detail::lexical_cast(input, b)) {
|
||||
throw ValidationError(input); // name is added later
|
||||
|
||||
// The filter function might be empty, so don't filter if it is.
|
||||
}
|
||||
if(filter_fn) {
|
||||
a = filter_fn(a);
|
||||
b = filter_fn(b);
|
||||
}
|
||||
|
||||
if(a == b) {
|
||||
auto res = detail::search(set, b, filter_fn);
|
||||
if(res.first) {
|
||||
// Make sure the version in the input string is identical to the one in the set
|
||||
// Requires std::stringstream << be supported on T.
|
||||
// If this is a map, output the map instead.
|
||||
if(filter_fn || detail::pair_adaptor<element_t>::value) {
|
||||
std::stringstream out;
|
||||
out << detail::pair_adaptor<element_t>::second(v);
|
||||
input = out.str();
|
||||
if(filter_fn) {
|
||||
input = detail::as_string(detail::pair_adaptor<element_t>::first(*(res.second)));
|
||||
}
|
||||
|
||||
// Return empty error string (success)
|
||||
return std::string();
|
||||
}
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
// If you reach this point, the result was not found
|
||||
std::stringstream out;
|
||||
out << input << " not in {";
|
||||
int i = 0; // I still don't like counters like this
|
||||
for(const auto &v : detail::smart_deref(set))
|
||||
out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v);
|
||||
out << "}";
|
||||
return out.str();
|
||||
std::string out(" not in ");
|
||||
out += detail::generate_set(detail::smart_deref(set));
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest (string only currently)
|
||||
template <typename T, typename... Args>
|
||||
IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
|
||||
: IsMember(std::move(set),
|
||||
IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
|
||||
: IsMember(std::forward<T>(set),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// Helper function to allow ignore_case to be passed to IsMember
|
||||
/// definition of the default transformation object
|
||||
template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
|
||||
|
||||
/// Translate named items to other or a value set
|
||||
class Transformer : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction
|
||||
template <typename... Args>
|
||||
explicit Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args)
|
||||
: Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// direct map of std::string to std::string
|
||||
template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
|
||||
|
||||
static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
|
||||
"mapping must produce value pairs");
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
|
||||
|
||||
func_ = [mapping, filter_fn](std::string &input) {
|
||||
local_item_t b;
|
||||
if(!detail::lexical_cast(input, b)) {
|
||||
return std::string();
|
||||
// there is no possible way we can match anything in the mapping if we can't convert so just return
|
||||
}
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(mapping, b, filter_fn);
|
||||
if(res.first) {
|
||||
input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||
}
|
||||
return std::string{};
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
template <typename T, typename... Args>
|
||||
Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
|
||||
: Transformer(std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// translate named items to other or a value set
|
||||
class CheckedTransformer : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction
|
||||
template <typename... Args>
|
||||
explicit CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args)
|
||||
: CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// direct map of std::string to std::string
|
||||
template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
|
||||
|
||||
static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
|
||||
"mapping must produce value pairs");
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair //
|
||||
// the type of the object pair
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
auto tfunc = [mapping]() {
|
||||
std::string out("value in ");
|
||||
out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
|
||||
out += detail::join(
|
||||
detail::smart_deref(mapping),
|
||||
[](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor<element_t>::second(v)); },
|
||||
",");
|
||||
out.push_back('}');
|
||||
return out;
|
||||
};
|
||||
|
||||
desc_function_ = tfunc;
|
||||
|
||||
func_ = [mapping, tfunc, filter_fn](std::string &input) {
|
||||
local_item_t b;
|
||||
bool converted = detail::lexical_cast(input, b);
|
||||
if(converted) {
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(mapping, b, filter_fn);
|
||||
if(res.first) {
|
||||
input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||
return std::string{};
|
||||
}
|
||||
}
|
||||
for(const auto &v : detail::smart_deref(mapping)) {
|
||||
auto output_string = detail::as_string(detail::pair_adaptor<element_t>::second(v));
|
||||
if(output_string == input) {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
return "Check " + input + " " + tfunc() + " FAILED";
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
template <typename T, typename... Args>
|
||||
CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
|
||||
: CheckedTransformer(std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
}; // namespace CLI
|
||||
|
||||
/// Helper function to allow ignore_case to be passed to IsMember or Transform
|
||||
inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
|
||||
|
||||
/// Helper function to allow ignore_underscore to be passed to IsMember
|
||||
/// Helper function to allow ignore_underscore to be passed to IsMember or Transform
|
||||
inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
|
||||
|
||||
/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform
|
||||
inline std::string ignore_space(std::string item) {
|
||||
item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
|
||||
item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
|
||||
return item;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
/// Split a string into a program name and command line arguments
|
||||
/// the string is assumed to contain a file name followed by other arguments
|
||||
/// the return value contains is a pair with the first argument containing the program name and the second everything
|
||||
/// else.
|
||||
/// the return value contains is a pair with the first argument containing the program name and the second
|
||||
/// everything else.
|
||||
inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
|
||||
// try to determine the programName
|
||||
std::pair<std::string, std::string> vals;
|
||||
|
@ -1431,7 +1431,7 @@ TEST_F(TApp, FileNotExists) {
|
||||
ASSERT_NO_THROW(CLI::NonexistentPath(myfile));
|
||||
|
||||
std::string filename;
|
||||
app.add_option("--file", filename)->check(CLI::NonexistentPath);
|
||||
auto opt = app.add_option("--file", filename)->check(CLI::NonexistentPath, "path_check");
|
||||
args = {"--file", myfile};
|
||||
|
||||
run();
|
||||
@ -1440,7 +1440,9 @@ TEST_F(TApp, FileNotExists) {
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
// deactivate the check, so it should run now
|
||||
opt->get_validator("path_check")->active(false);
|
||||
EXPECT_NO_THROW(run());
|
||||
std::remove(myfile.c_str());
|
||||
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
|
||||
}
|
||||
@ -1861,7 +1863,7 @@ TEST_F(TApp, OrderedModifingTransforms) {
|
||||
|
||||
run();
|
||||
|
||||
EXPECT_EQ(val, std::vector<std::string>({"one12", "two12"}));
|
||||
EXPECT_EQ(val, std::vector<std::string>({"one21", "two21"}));
|
||||
}
|
||||
|
||||
TEST_F(TApp, ThrowingTransform) {
|
||||
|
@ -27,6 +27,7 @@ set(CLI11_TESTS
|
||||
SimpleTest
|
||||
AppTest
|
||||
SetTest
|
||||
TransformTest
|
||||
CreationTest
|
||||
SubcommandTest
|
||||
HelpTest
|
||||
|
@ -561,3 +561,164 @@ TEST_F(TApp, GetOptionList) {
|
||||
EXPECT_EQ(opt_list.at(1), flag);
|
||||
EXPECT_EQ(opt_list.at(2), opt);
|
||||
}
|
||||
|
||||
TEST(ValidatorTests, TestValidatorCreation) {
|
||||
std::function<std::string(std::string &)> op1 = [](std::string &val) {
|
||||
return (val.size() >= 5) ? std::string{} : val;
|
||||
};
|
||||
CLI::Validator V(op1, "", "size");
|
||||
|
||||
EXPECT_EQ(V.get_name(), "size");
|
||||
V.name("harry");
|
||||
EXPECT_EQ(V.get_name(), "harry");
|
||||
EXPECT_TRUE(V.get_active());
|
||||
|
||||
EXPECT_EQ(V("test"), "test");
|
||||
EXPECT_EQ(V("test5"), std::string{});
|
||||
|
||||
EXPECT_EQ(V.get_description(), std::string{});
|
||||
V.description("this is a description");
|
||||
EXPECT_EQ(V.get_description(), "this is a description");
|
||||
}
|
||||
|
||||
TEST(ValidatorTests, TestValidatorOps) {
|
||||
std::function<std::string(std::string &)> op1 = [](std::string &val) {
|
||||
return (val.size() >= 5) ? std::string{} : val;
|
||||
};
|
||||
std::function<std::string(std::string &)> op2 = [](std::string &val) {
|
||||
return (val.size() >= 9) ? std::string{} : val;
|
||||
};
|
||||
std::function<std::string(std::string &)> op3 = [](std::string &val) {
|
||||
return (val.size() < 3) ? std::string{} : val;
|
||||
};
|
||||
std::function<std::string(std::string &)> op4 = [](std::string &val) {
|
||||
return (val.size() <= 9) ? std::string{} : val;
|
||||
};
|
||||
CLI::Validator V1(op1, "SIZE >= 5");
|
||||
|
||||
CLI::Validator V2(op2, "SIZE >= 9");
|
||||
CLI::Validator V3(op3, "SIZE < 3");
|
||||
CLI::Validator V4(op4, "SIZE <= 9");
|
||||
|
||||
std::string two(2, 'a');
|
||||
std::string four(4, 'a');
|
||||
std::string five(5, 'a');
|
||||
std::string eight(8, 'a');
|
||||
std::string nine(9, 'a');
|
||||
std::string ten(10, 'a');
|
||||
EXPECT_TRUE(V1(five).empty());
|
||||
EXPECT_FALSE(V1(four).empty());
|
||||
|
||||
EXPECT_TRUE(V2(nine).empty());
|
||||
EXPECT_FALSE(V2(eight).empty());
|
||||
|
||||
EXPECT_TRUE(V3(two).empty());
|
||||
EXPECT_FALSE(V3(four).empty());
|
||||
|
||||
EXPECT_TRUE(V4(eight).empty());
|
||||
EXPECT_FALSE(V4(ten).empty());
|
||||
|
||||
auto V1a2 = V1 & V2;
|
||||
EXPECT_EQ(V1a2.get_description(), "(SIZE >= 5) AND (SIZE >= 9)");
|
||||
EXPECT_FALSE(V1a2(five).empty());
|
||||
EXPECT_TRUE(V1a2(nine).empty());
|
||||
|
||||
auto V1a4 = V1 & V4;
|
||||
EXPECT_EQ(V1a4.get_description(), "(SIZE >= 5) AND (SIZE <= 9)");
|
||||
EXPECT_TRUE(V1a4(five).empty());
|
||||
EXPECT_TRUE(V1a4(eight).empty());
|
||||
EXPECT_FALSE(V1a4(ten).empty());
|
||||
EXPECT_FALSE(V1a4(four).empty());
|
||||
|
||||
auto V1o3 = V1 | V3;
|
||||
EXPECT_EQ(V1o3.get_description(), "(SIZE >= 5) OR (SIZE < 3)");
|
||||
EXPECT_TRUE(V1o3(two).empty());
|
||||
EXPECT_TRUE(V1o3(eight).empty());
|
||||
EXPECT_TRUE(V1o3(ten).empty());
|
||||
EXPECT_TRUE(V1o3(two).empty());
|
||||
EXPECT_FALSE(V1o3(four).empty());
|
||||
|
||||
auto m1 = V1o3 & V4;
|
||||
EXPECT_EQ(m1.get_description(), "((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)");
|
||||
EXPECT_TRUE(m1(two).empty());
|
||||
EXPECT_TRUE(m1(eight).empty());
|
||||
EXPECT_FALSE(m1(ten).empty());
|
||||
EXPECT_TRUE(m1(two).empty());
|
||||
EXPECT_TRUE(m1(five).empty());
|
||||
EXPECT_FALSE(m1(four).empty());
|
||||
|
||||
auto m2 = m1 & V2;
|
||||
EXPECT_EQ(m2.get_description(), "(((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)");
|
||||
EXPECT_FALSE(m2(two).empty());
|
||||
EXPECT_FALSE(m2(eight).empty());
|
||||
EXPECT_FALSE(m2(ten).empty());
|
||||
EXPECT_FALSE(m2(two).empty());
|
||||
EXPECT_TRUE(m2(nine).empty());
|
||||
EXPECT_FALSE(m2(four).empty());
|
||||
|
||||
auto m3 = m2 | V3;
|
||||
EXPECT_EQ(m3.get_description(), "((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)) OR (SIZE < 3)");
|
||||
EXPECT_TRUE(m3(two).empty());
|
||||
EXPECT_FALSE(m3(eight).empty());
|
||||
EXPECT_TRUE(m3(nine).empty());
|
||||
EXPECT_FALSE(m3(four).empty());
|
||||
|
||||
auto m4 = V3 | m2;
|
||||
EXPECT_EQ(m4.get_description(), "(SIZE < 3) OR ((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9))");
|
||||
EXPECT_TRUE(m4(two).empty());
|
||||
EXPECT_FALSE(m4(eight).empty());
|
||||
EXPECT_TRUE(m4(nine).empty());
|
||||
EXPECT_FALSE(m4(four).empty());
|
||||
}
|
||||
|
||||
TEST(ValidatorTests, TestValidatorNegation) {
|
||||
|
||||
std::function<std::string(std::string &)> op1 = [](std::string &val) {
|
||||
return (val.size() >= 5) ? std::string{} : val;
|
||||
};
|
||||
|
||||
CLI::Validator V1(op1, "SIZE >= 5", "size");
|
||||
|
||||
std::string four(4, 'a');
|
||||
std::string five(5, 'a');
|
||||
|
||||
EXPECT_TRUE(V1(five).empty());
|
||||
EXPECT_FALSE(V1(four).empty());
|
||||
|
||||
auto V2 = !V1;
|
||||
EXPECT_FALSE(V2(five).empty());
|
||||
EXPECT_TRUE(V2(four).empty());
|
||||
EXPECT_EQ(V2.get_description(), "NOT SIZE >= 5");
|
||||
|
||||
V2.active(false);
|
||||
EXPECT_TRUE(V2(five).empty());
|
||||
EXPECT_TRUE(V2(four).empty());
|
||||
EXPECT_TRUE(V2.get_description().empty());
|
||||
}
|
||||
|
||||
TEST(ValidatorTests, ValidatorDefaults) {
|
||||
|
||||
CLI::Validator V1{};
|
||||
|
||||
std::string four(4, 'a');
|
||||
std::string five(5, 'a');
|
||||
|
||||
// make sure this doesn't generate a seg fault or something
|
||||
EXPECT_TRUE(V1(five).empty());
|
||||
EXPECT_TRUE(V1(four).empty());
|
||||
|
||||
EXPECT_TRUE(V1.get_name().empty());
|
||||
EXPECT_TRUE(V1.get_description().empty());
|
||||
EXPECT_TRUE(V1.get_active());
|
||||
EXPECT_TRUE(V1.get_modifying());
|
||||
|
||||
CLI::Validator V2{"check"};
|
||||
// make sure this doesn't generate a seg fault or something
|
||||
EXPECT_TRUE(V2(five).empty());
|
||||
EXPECT_TRUE(V2(four).empty());
|
||||
|
||||
EXPECT_TRUE(V2.get_name().empty());
|
||||
EXPECT_EQ(V2.get_description(), "check");
|
||||
EXPECT_TRUE(V2.get_active());
|
||||
EXPECT_TRUE(V2.get_modifying());
|
||||
}
|
||||
|
@ -251,11 +251,10 @@ TEST(THelp, ManualSetterOverFunction) {
|
||||
EXPECT_EQ(x, 1);
|
||||
|
||||
std::string help = app.help();
|
||||
|
||||
EXPECT_THAT(help, HasSubstr("=12"));
|
||||
EXPECT_THAT(help, HasSubstr("BIGGLES"));
|
||||
EXPECT_THAT(help, HasSubstr("QUIGGLES"));
|
||||
EXPECT_THAT(help, Not(HasSubstr("1,2")));
|
||||
EXPECT_THAT(help, HasSubstr("{1,2}"));
|
||||
}
|
||||
|
||||
TEST(THelp, Subcom) {
|
||||
@ -743,10 +742,9 @@ TEST(THelp, ValidatorsText) {
|
||||
app.add_option("--f4", y)->check(CLI::Range(12));
|
||||
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("FILE"));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:FILE"));
|
||||
EXPECT_THAT(help, HasSubstr("INT in [1 - 4]"));
|
||||
EXPECT_THAT(help, HasSubstr("INT in [0 - 12]")); // Loses UINT
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("UINT:INT in [0 - 12]")); // Loses UINT
|
||||
}
|
||||
|
||||
TEST(THelp, ValidatorsNonPathText) {
|
||||
@ -756,8 +754,7 @@ TEST(THelp, ValidatorsNonPathText) {
|
||||
app.add_option("--f2", filename)->check(CLI::NonexistentPath);
|
||||
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("PATH"));
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:PATH"));
|
||||
}
|
||||
|
||||
TEST(THelp, ValidatorsDirText) {
|
||||
@ -767,8 +764,7 @@ TEST(THelp, ValidatorsDirText) {
|
||||
app.add_option("--f2", filename)->check(CLI::ExistingDirectory);
|
||||
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("DIR"));
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:DIR"));
|
||||
}
|
||||
|
||||
TEST(THelp, ValidatorsPathText) {
|
||||
@ -778,8 +774,7 @@ TEST(THelp, ValidatorsPathText) {
|
||||
app.add_option("--f2", filename)->check(CLI::ExistingPath);
|
||||
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("PATH"));
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:PATH"));
|
||||
}
|
||||
|
||||
TEST(THelp, CombinedValidatorsText) {
|
||||
@ -792,9 +787,8 @@ TEST(THelp, CombinedValidatorsText) {
|
||||
// Can't programatically tell!
|
||||
// (Users can use ExistingPath, by the way)
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("TEXT"));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:(FILE) OR (DIR)"));
|
||||
EXPECT_THAT(help, Not(HasSubstr("PATH")));
|
||||
EXPECT_THAT(help, Not(HasSubstr("FILE")));
|
||||
}
|
||||
|
||||
// Don't do this in real life, please
|
||||
@ -806,7 +800,7 @@ TEST(THelp, CombinedValidatorsPathyText) {
|
||||
|
||||
// Combining validators with the same type string is OK
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:"));
|
||||
EXPECT_THAT(help, HasSubstr("PATH"));
|
||||
}
|
||||
|
||||
@ -819,8 +813,7 @@ TEST(THelp, CombinedValidatorsPathyTextAsTransform) {
|
||||
|
||||
// Combining validators with the same type string is OK
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("PATH"));
|
||||
EXPECT_THAT(help, HasSubstr("TEXT:(PATH(existing)) OR (PATH"));
|
||||
}
|
||||
|
||||
// #113 Part 2
|
||||
|
@ -4,20 +4,31 @@
|
||||
static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
|
||||
static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
|
||||
static_assert(CLI::is_shared_ptr<int>::value == false, "is_shared_ptr should work on non-pointers");
|
||||
static_assert(CLI::is_shared_ptr<const std::shared_ptr<int>>::value == true,
|
||||
"is_shared_ptr should work on const shared pointers");
|
||||
static_assert(CLI::is_shared_ptr<const int *>::value == false, "is_shared_ptr should work on const pointers");
|
||||
static_assert(CLI::is_shared_ptr<const int &>::value == false, "is_shared_ptr should work on const references");
|
||||
static_assert(CLI::is_shared_ptr<int &>::value == false, "is_shared_ptr should work on non-const references");
|
||||
|
||||
static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true,
|
||||
"is_copyable_ptr should work on shared pointers");
|
||||
static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers");
|
||||
static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers");
|
||||
static_assert(CLI::is_copyable_ptr<const std::shared_ptr<int>>::value == true,
|
||||
"is_copyable_ptr should work on const shared pointers");
|
||||
static_assert(CLI::is_copyable_ptr<const int *>::value == true, "is_copyable_ptr should work on const pointers");
|
||||
static_assert(CLI::is_copyable_ptr<const int &>::value == false, "is_copyable_ptr should work on const references");
|
||||
static_assert(CLI::is_copyable_ptr<int &>::value == false, "is_copyable_ptr should work on non-const references");
|
||||
|
||||
static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs");
|
||||
static_assert(CLI::detail::pair_adaptor<std::vector<std::string>>::value == false, "Should not have pairs");
|
||||
static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs");
|
||||
static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs");
|
||||
|
||||
TEST_F(TApp, SimpleMaps) {
|
||||
int value;
|
||||
std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
@ -29,7 +40,7 @@ TEST_F(TApp, SimpleMaps) {
|
||||
TEST_F(TApp, StringStringMap) {
|
||||
std::string value;
|
||||
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
|
||||
app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
app.add_option("-s,--set", value)->transform(CLI::CheckedTransformer(map));
|
||||
args = {"-s", "a"};
|
||||
run();
|
||||
EXPECT_EQ(value, "b");
|
||||
@ -39,7 +50,7 @@ TEST_F(TApp, StringStringMap) {
|
||||
EXPECT_EQ(value, "c");
|
||||
|
||||
args = {"-s", "c"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
EXPECT_EQ(value, "c");
|
||||
}
|
||||
|
||||
TEST_F(TApp, StringStringMapNoModify) {
|
||||
@ -63,7 +74,7 @@ enum SimpleEnum { SE_one = 1, SE_two = 2 };
|
||||
TEST_F(TApp, EnumMap) {
|
||||
SimpleEnum value;
|
||||
std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}};
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
@ -77,7 +88,7 @@ enum class SimpleEnumC { one = 1, two = 2 };
|
||||
TEST_F(TApp, EnumCMap) {
|
||||
SimpleEnumC value;
|
||||
std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}};
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
@ -86,6 +97,155 @@ TEST_F(TApp, EnumCMap) {
|
||||
EXPECT_EQ(value, SimpleEnumC::one);
|
||||
}
|
||||
|
||||
TEST_F(TApp, structMap) {
|
||||
struct tstruct {
|
||||
int val2;
|
||||
double val3;
|
||||
std::string v4;
|
||||
};
|
||||
std::string struct_name;
|
||||
std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
|
||||
auto opt = app.add_option("-s,--set", struct_name)->check(CLI::IsMember(map));
|
||||
args = {"-s", "sone"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(struct_name, "sone");
|
||||
|
||||
args = {"-s", "sthree"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, structMapChange) {
|
||||
struct tstruct {
|
||||
int val2;
|
||||
double val3;
|
||||
std::string v4;
|
||||
};
|
||||
std::string struct_name;
|
||||
std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
|
||||
auto opt = app.add_option("-s,--set", struct_name)
|
||||
->transform(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
|
||||
args = {"-s", "s one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(struct_name, "sone");
|
||||
|
||||
args = {"-s", "sthree"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "S_t_w_o"};
|
||||
run();
|
||||
EXPECT_EQ(struct_name, "stwo");
|
||||
args = {"-s", "S two"};
|
||||
run();
|
||||
EXPECT_EQ(struct_name, "stwo");
|
||||
}
|
||||
|
||||
TEST_F(TApp, structMapNoChange) {
|
||||
struct tstruct {
|
||||
int val2;
|
||||
double val3;
|
||||
std::string v4;
|
||||
};
|
||||
std::string struct_name;
|
||||
std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
|
||||
auto opt = app.add_option("-s,--set", struct_name)
|
||||
->check(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
|
||||
args = {"-s", "SONE"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(struct_name, "SONE");
|
||||
|
||||
args = {"-s", "sthree"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "S_t_w_o"};
|
||||
run();
|
||||
EXPECT_EQ(struct_name, "S_t_w_o");
|
||||
|
||||
args = {"-s", "S two"};
|
||||
run();
|
||||
EXPECT_EQ(struct_name, "S two");
|
||||
}
|
||||
|
||||
TEST_F(TApp, NonCopyableMap) {
|
||||
|
||||
std::string map_name;
|
||||
std::map<std::string, std::unique_ptr<double>> map;
|
||||
map["e1"] = std::unique_ptr<double>(new double(5.7));
|
||||
map["e3"] = std::unique_ptr<double>(new double(23.8));
|
||||
auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
|
||||
args = {"-s", "e1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(map_name, "e1");
|
||||
|
||||
args = {"-s", "e45"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NonCopyableMapWithFunction) {
|
||||
|
||||
std::string map_name;
|
||||
std::map<std::string, std::unique_ptr<double>> map;
|
||||
map["e1"] = std::unique_ptr<double>(new double(5.7));
|
||||
map["e3"] = std::unique_ptr<double>(new double(23.8));
|
||||
auto opt = app.add_option("-s,--set", map_name)->transform(CLI::IsMember(&map, CLI::ignore_underscore));
|
||||
args = {"-s", "e_1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(map_name, "e1");
|
||||
|
||||
args = {"-s", "e45"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NonCopyableMapNonStringMap) {
|
||||
|
||||
std::string map_name;
|
||||
std::map<int, std::unique_ptr<double>> map;
|
||||
map[4] = std::unique_ptr<double>(new double(5.7));
|
||||
map[17] = std::unique_ptr<double>(new double(23.8));
|
||||
auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
|
||||
args = {"-s", "4"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(map_name, "4");
|
||||
|
||||
args = {"-s", "e45"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, CopyableMapMove) {
|
||||
|
||||
std::string map_name;
|
||||
std::map<int, double> map;
|
||||
map[4] = 5.7;
|
||||
map[17] = 23.8;
|
||||
auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(std::move(map)));
|
||||
args = {"-s", "4"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(map_name, "4");
|
||||
|
||||
args = {"-s", "e45"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, SimpleSets) {
|
||||
std::string value;
|
||||
auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
|
||||
|
433
tests/TransformTest.cpp
Normal file
433
tests/TransformTest.cpp
Normal file
@ -0,0 +1,433 @@
|
||||
#include "app_helper.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
TEST_F(TApp, SimpleTransform) {
|
||||
int value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_F(TApp, SimpleTransformInitList) {
|
||||
int value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_F(TApp, SimpleNumericalTransform) {
|
||||
int value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer(CLI::TransformPairs<int>{{"one", 1}}));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_F(TApp, EnumTransform) {
|
||||
enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
|
||||
test value;
|
||||
auto opt = app.add_option("-s", value)
|
||||
->transform(CLI::Transformer(
|
||||
CLI::TransformPairs<test>{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}}));
|
||||
args = {"-s", "val1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, test::val1);
|
||||
|
||||
args = {"-s", "val2"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val2);
|
||||
|
||||
args = {"-s", "val3"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "val4"};
|
||||
EXPECT_THROW(run(), CLI::ConversionError);
|
||||
|
||||
// transformer doesn't do any checking so this still works
|
||||
args = {"-s", "5"};
|
||||
run();
|
||||
EXPECT_EQ(static_cast<int16_t>(value), int16_t(5));
|
||||
}
|
||||
|
||||
TEST_F(TApp, EnumCheckedTransform) {
|
||||
enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
|
||||
test value;
|
||||
auto opt = app.add_option("-s", value)
|
||||
->transform(CLI::CheckedTransformer(
|
||||
CLI::TransformPairs<test>{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}}));
|
||||
args = {"-s", "val1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, test::val1);
|
||||
|
||||
args = {"-s", "val2"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val2);
|
||||
|
||||
args = {"-s", "val3"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "17"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "val4"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "5"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, SimpleTransformFn) {
|
||||
int value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
|
||||
args = {"-s", "ONE"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_F(TApp, SimpleNumericalTransformFn) {
|
||||
int value;
|
||||
auto opt =
|
||||
app.add_option("-s", value)
|
||||
->transform(CLI::Transformer(std::vector<std::pair<std::string, int>>{{"one", 1}}, CLI::ignore_case));
|
||||
args = {"-s", "ONe"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_F(TApp, EnumTransformFn) {
|
||||
enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
|
||||
test value;
|
||||
auto opt = app.add_option("-s", value)
|
||||
->transform(CLI::Transformer(
|
||||
CLI::TransformPairs<test>{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}},
|
||||
CLI::ignore_case,
|
||||
CLI::ignore_underscore));
|
||||
args = {"-s", "val_1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, test::val1);
|
||||
|
||||
args = {"-s", "VAL_2"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val2);
|
||||
|
||||
args = {"-s", "VAL3"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "val_4"};
|
||||
EXPECT_THROW(run(), CLI::ConversionError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, EnumTransformFnMap) {
|
||||
enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
|
||||
std::map<std::string, test> map{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}};
|
||||
test value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
|
||||
args = {"-s", "val_1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, test::val1);
|
||||
|
||||
args = {"-s", "VAL_2"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val2);
|
||||
|
||||
args = {"-s", "VAL3"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "val_4"};
|
||||
EXPECT_THROW(run(), CLI::ConversionError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, EnumTransformFnPtrMap) {
|
||||
enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
|
||||
std::map<std::string, test> map{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}};
|
||||
test value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer(&map, CLI::ignore_case, CLI::ignore_underscore));
|
||||
args = {"-s", "val_1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, test::val1);
|
||||
|
||||
args = {"-s", "VAL_2"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val2);
|
||||
|
||||
args = {"-s", "VAL3"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "val_4"};
|
||||
EXPECT_THROW(run(), CLI::ConversionError);
|
||||
|
||||
map["val4"] = test::val4;
|
||||
run();
|
||||
EXPECT_EQ(value, test::val4);
|
||||
}
|
||||
|
||||
TEST_F(TApp, EnumTransformFnSharedPtrMap) {
|
||||
enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
|
||||
auto map = std::make_shared<std::unordered_map<std::string, test>>();
|
||||
auto &mp = *map;
|
||||
mp["val1"] = test::val1;
|
||||
mp["val2"] = test::val2;
|
||||
mp["val3"] = test::val3;
|
||||
|
||||
test value;
|
||||
auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
|
||||
args = {"-s", "val_1"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, test::val1);
|
||||
|
||||
args = {"-s", "VAL_2"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val2);
|
||||
|
||||
args = {"-s", "VAL3"};
|
||||
run();
|
||||
EXPECT_EQ(value, test::val3);
|
||||
|
||||
args = {"-s", "val_4"};
|
||||
EXPECT_THROW(run(), CLI::ConversionError);
|
||||
|
||||
mp["val4"] = test::val4;
|
||||
run();
|
||||
EXPECT_EQ(value, test::val4);
|
||||
}
|
||||
|
||||
// Test a cascade of transform functions
|
||||
TEST_F(TApp, TransformCascade) {
|
||||
|
||||
std::string output;
|
||||
auto opt = app.add_option("-s", output);
|
||||
opt->transform(CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case));
|
||||
opt->transform(
|
||||
CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore));
|
||||
opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case));
|
||||
opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}));
|
||||
args = {"-s", "abcd"};
|
||||
run();
|
||||
EXPECT_EQ(output, "abcd");
|
||||
|
||||
args = {"-s", "Bbc"};
|
||||
run();
|
||||
EXPECT_EQ(output, "bbcd");
|
||||
|
||||
args = {"-s", "C_B"};
|
||||
run();
|
||||
EXPECT_EQ(output, "cbcd");
|
||||
|
||||
args = {"-s", "A"};
|
||||
run();
|
||||
EXPECT_EQ(output, "abcd");
|
||||
}
|
||||
|
||||
// Test a cascade of transform functions
|
||||
TEST_F(TApp, TransformCascadeDeactivate) {
|
||||
|
||||
std::string output;
|
||||
auto opt = app.add_option("-s", output);
|
||||
opt->transform(
|
||||
CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case).name("tform1"));
|
||||
opt->transform(
|
||||
CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore)
|
||||
.name("tform2")
|
||||
.active(false));
|
||||
opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case).name("tform3"));
|
||||
opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}).name("check"));
|
||||
args = {"-s", "abcd"};
|
||||
run();
|
||||
EXPECT_EQ(output, "abcd");
|
||||
|
||||
args = {"-s", "Bbc"};
|
||||
run();
|
||||
EXPECT_EQ(output, "bbcd");
|
||||
|
||||
args = {"-s", "C_B"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
auto validator = opt->get_validator("tform2");
|
||||
EXPECT_FALSE(validator->get_active());
|
||||
EXPECT_EQ(validator->get_name(), "tform2");
|
||||
validator->active();
|
||||
EXPECT_TRUE(validator->get_active());
|
||||
args = {"-s", "C_B"};
|
||||
run();
|
||||
EXPECT_EQ(output, "cbcd");
|
||||
|
||||
opt->get_validator("check")->active(false);
|
||||
args = {"-s", "gsdgsgs"};
|
||||
run();
|
||||
EXPECT_EQ(output, "gsdgsgs");
|
||||
|
||||
EXPECT_THROW(opt->get_validator("sdfsdf"), CLI::OptionNotFound);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IntTransformFn) {
|
||||
std::string value;
|
||||
app.add_option("-s", value)
|
||||
->transform(
|
||||
CLI::CheckedTransformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}, [](int in) { return in - 10; }));
|
||||
args = {"-s", "25"};
|
||||
run();
|
||||
EXPECT_EQ(value, "5");
|
||||
|
||||
args = {"-s", "6"};
|
||||
run();
|
||||
EXPECT_EQ(value, "6");
|
||||
|
||||
args = {"-s", "45"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "val_4"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IntTransformNonConvertible) {
|
||||
std::string value;
|
||||
app.add_option("-s", value)->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}));
|
||||
args = {"-s", "15"};
|
||||
run();
|
||||
EXPECT_EQ(value, "5");
|
||||
|
||||
args = {"-s", "18"};
|
||||
run();
|
||||
EXPECT_EQ(value, "6");
|
||||
|
||||
// value can't be converted to int so it is just ignored
|
||||
args = {"-s", "abcd"};
|
||||
run();
|
||||
EXPECT_EQ(value, "abcd");
|
||||
}
|
||||
|
||||
TEST_F(TApp, IntTransformNonMerge) {
|
||||
std::string value;
|
||||
app.add_option("-s", value)
|
||||
->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) &
|
||||
CLI::Transformer(std::map<int, int>{{25, 5}, {28, 6}, {31, 7}}),
|
||||
"merge");
|
||||
args = {"-s", "15"};
|
||||
run();
|
||||
EXPECT_EQ(value, "5");
|
||||
|
||||
args = {"-s", "18"};
|
||||
run();
|
||||
EXPECT_EQ(value, "6");
|
||||
|
||||
// value can't be converted to int so it is just ignored
|
||||
args = {"-s", "abcd"};
|
||||
run();
|
||||
EXPECT_EQ(value, "abcd");
|
||||
|
||||
args = {"-s", "25"};
|
||||
run();
|
||||
EXPECT_EQ(value, "5");
|
||||
|
||||
args = {"-s", "31"};
|
||||
run();
|
||||
EXPECT_EQ(value, "7");
|
||||
|
||||
auto help = app.help();
|
||||
EXPECT_TRUE(help.find("15->5") != std::string::npos);
|
||||
EXPECT_TRUE(help.find("25->5") != std::string::npos);
|
||||
|
||||
auto validator = app.get_option("-s")->get_validator();
|
||||
help = validator->get_description();
|
||||
EXPECT_TRUE(help.find("15->5") != std::string::npos);
|
||||
EXPECT_TRUE(help.find("25->5") != std::string::npos);
|
||||
|
||||
auto validator2 = app.get_option("-s")->get_validator("merge");
|
||||
EXPECT_EQ(validator2, validator);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IntTransformMergeWithCustomValidator) {
|
||||
std::string value;
|
||||
auto opt = app.add_option("-s", value)
|
||||
->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) |
|
||||
CLI::Validator(
|
||||
[](std::string &element) {
|
||||
if(element == "frog") {
|
||||
element = "hops";
|
||||
}
|
||||
return std::string{};
|
||||
},
|
||||
std::string{}),
|
||||
"check");
|
||||
args = {"-s", "15"};
|
||||
run();
|
||||
EXPECT_EQ(value, "5");
|
||||
|
||||
args = {"-s", "18"};
|
||||
run();
|
||||
EXPECT_EQ(value, "6");
|
||||
|
||||
// value can't be converted to int so it is just ignored
|
||||
args = {"-s", "frog"};
|
||||
run();
|
||||
EXPECT_EQ(value, "hops");
|
||||
|
||||
args = {"-s", "25"};
|
||||
run();
|
||||
EXPECT_EQ(value, "25");
|
||||
|
||||
auto help = app.help();
|
||||
EXPECT_TRUE(help.find("15->5") != std::string::npos);
|
||||
EXPECT_TRUE(help.find("OR") == std::string::npos);
|
||||
|
||||
auto validator = opt->get_validator("check");
|
||||
EXPECT_EQ(validator->get_name(), "check");
|
||||
validator->active(false);
|
||||
help = app.help();
|
||||
EXPECT_TRUE(help.find("15->5") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(TApp, BoundTests) {
|
||||
double value;
|
||||
app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
|
||||
args = {"-s", "15"};
|
||||
run();
|
||||
EXPECT_EQ(value, 5.9);
|
||||
|
||||
args = {"-s", "3.689"};
|
||||
run();
|
||||
EXPECT_EQ(value, std::stod("3.689"));
|
||||
|
||||
// value can't be converted to int so it is just ignored
|
||||
args = {"-s", "abcd"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "2.5"};
|
||||
run();
|
||||
EXPECT_EQ(value, 3.4);
|
||||
|
||||
auto help = app.help();
|
||||
EXPECT_TRUE(help.find("bounded to") != std::string::npos);
|
||||
EXPECT_TRUE(help.find("[3.4 - 5.9]") != std::string::npos);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user