Commit e9934e058d5f0f1c43fdda698ac87481db88e3cf

Authored by Philip Top
Committed by Henry Schreiner
1 parent 6aa546fc

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
README.md
@@ -31,11 +31,17 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature @@ -31,11 +31,17 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature
31 - [Usage](#usage) 31 - [Usage](#usage)
32 - [Adding options](#adding-options) 32 - [Adding options](#adding-options)
33 - [Option types](#option-types) 33 - [Option types](#option-types)
  34 + - [Example](#example)
34 - [Option options](#option-options) 35 - [Option options](#option-options)
  36 + - [Validators](#validators) ๐Ÿšง
  37 + - [Transforming Validators](#transforming-validators)๐Ÿšง
  38 + - [Validator operations](#validator-operations)๐Ÿšง
  39 + - [Custom Validators](#custom-validators)๐Ÿšง
  40 + - [Querying Validators](#querying-validators)๐Ÿšง
35 - [Getting Results](#getting-results) ๐Ÿšง 41 - [Getting Results](#getting-results) ๐Ÿšง
36 - [Subcommands](#subcommands) 42 - [Subcommands](#subcommands)
37 - [Subcommand options](#subcommand-options) 43 - [Subcommand options](#subcommand-options)
38 - - [Option Groups](#option-groups) ๐Ÿšง 44 + - [Option groups](#option-groups) ๐Ÿšง
39 - [Configuration file](#configuration-file) 45 - [Configuration file](#configuration-file)
40 - [Inheriting defaults](#inheriting-defaults) 46 - [Inheriting defaults](#inheriting-defaults)
41 - [Formatting](#formatting) 47 - [Formatting](#formatting)
@@ -245,7 +251,7 @@ alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; th @@ -245,7 +251,7 @@ alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; th
245 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 251 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
246 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 252 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
247 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). 253 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).
248 -The default value can be any value For example if you wished to define a numerical flag 254 +The default value can be any value. For example if you wished to define a numerical flag
249 ```cpp 255 ```cpp
250 app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag") // ๐Ÿšง 256 app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag") // ๐Ÿšง
251 ``` 257 ```
@@ -282,33 +288,15 @@ Before parsing, you can set the following options: @@ -282,33 +288,15 @@ Before parsing, you can set the following options:
282 - `->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 288 - `->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
283 - `->description(str)`: ๐Ÿ†• Set/change the description. 289 - `->description(str)`: ๐Ÿ†• Set/change the description.
284 - `->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). 290 - `->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).
285 -- `->check(CLI::IsMember(...))`: ๐Ÿšง Require an option be a member of a given set. See below for options.  
286 -- `->transform(CLI::IsMember(...))`: ๐Ÿšง Require an option be a member of a given set or map. Can change the parse. See below for options.  
287 -- `->check(CLI::ExistingFile)`: Requires that the file exists if given.  
288 -- `->check(CLI::ExistingDirectory)`: Requires that the directory exists.  
289 -- `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists.  
290 -- `->check(CLI::NonexistentPath)`: Requires that the path does not exist.  
291 -- `->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.  
292 -- `->check(CLI::PositiveNumber)`: ๐Ÿšง Requires the number be greater or equal to 0  
293 -- `->check(CLI::ValidIPV4)`: ๐Ÿšง Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`  
294 -- `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.  
295 -- `->each(void(std::string)>`: Run this function on each value received, as it is received. 291 +- `->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
  292 +- `->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.
  293 +- `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
  294 +- `->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.
  295 +- `->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
296 - `->configurable(false)`: Disable this option from being in a configuration file. 296 - `->configurable(false)`: Disable this option from being in a configuration file.
297 297
298 298
299 -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.  
300 -  
301 -๐Ÿšง 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.  
302 -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`.  
303 -Here are some examples  
304 -of `IsMember`:  
305 -  
306 -- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.  
307 -- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.  
308 -- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.  
309 -- `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.  
310 -- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.  
311 -- 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. 299 +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.
312 300
313 On the command line, options can be given as: 301 On the command line, options can be given as:
314 302
@@ -340,8 +328,111 @@ You can access a vector of pointers to the parsed options in the original order @@ -340,8 +328,111 @@ You can access a vector of pointers to the parsed options in the original order
340 If `--` is present in the command line that does not end an unlimited option, then 328 If `--` is present in the command line that does not end an unlimited option, then
341 everything after that is positional only. 329 everything after that is positional only.
342 330
343 -#### Getting results {#getting-results} ๐Ÿšง 331 +#### Validators
  332 +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`.
  333 +
  334 +CLI11 has several Validators built in that perform some common checks
  335 +
  336 +- `CLI::IsMember(...)`: ๐Ÿšง Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details.
  337 +- `CLI::Transformer(...)`: ๐Ÿšง Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details.
  338 +- `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.
  339 +- `CLI::ExistingFile`: Requires that the file exists if given.
  340 +- `CLI::ExistingDirectory`: Requires that the directory exists.
  341 +- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
  342 +- `CLI::NonexistentPath`: Requires that the path does not exist.
  343 +- `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.
  344 +- `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.
  345 +- `CLI::PositiveNumber`: ๐Ÿšง Requires the number be greater or equal to 0.
  346 +- `CLI::ValidIPV4`: ๐Ÿšง Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
  347 +
  348 +These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
  349 +```cpp
  350 +->check(CLI::ExistingFile);
  351 +```
  352 +```cpp
  353 +->check(CLI::Range(0,10));
  354 +```
  355 +
  356 +Validators can be merged using `&` and `|` and inverted using `!`
  357 +such as
  358 +```cpp
  359 +->check(CLI::Range(0,10)|CLI::Range(20,30));
  360 +```
  361 +will produce a check if a value is between 0 and 10 or 20 and 30.
  362 +```cpp
  363 +->check(!CLI::PositiveNumber);
  364 +```
  365 +will produce a check for a number less than 0;
  366 +
  367 +##### Transforming Validators
  368 +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.
  369 + * ๐Ÿšง `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`
  370 + * ๐Ÿšง 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.
  371 +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.
  372 +Here are some examples
  373 +of `IsMember`:
  374 +
  375 + * `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
  376 + * `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
  377 + * `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
  378 + * `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`.
  379 + * `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
  380 +* ๐Ÿšง 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.
  381 +After specifying a map of options, you can also specify "filter" just like in CLI::IsMember.
  382 +Here are some examples (`Transfomer` and `CheckedTransformer` are interchangeable in the examples)
  383 +of `Transformer`:
  384 +
  385 + * `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
  386 +
  387 + * `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.
  388 + * `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.
  389 + * `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>>`
  390 +
  391 +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.
  392 +
  393 +##### Validator operations๐Ÿšง
  394 +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)`.
  395 +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)`
  396 +The operation function of a Validator can be set via
  397 +`.operation(std::function<std::string(std::string &>)`. The `.active()` function can activate or deactivate a Validator from the operation.
  398 +All the functions return a Validator reference allowing them to be chained. For example
  399 +
  400 +```cpp
  401 +opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));
  402 +```
  403 +will specify a check on an option with a name "range", but deactivate it for the time being.
  404 +The check can later be activated through
  405 +```cpp
  406 +opt->get_validator("range")->active();
  407 +```
  408 +
  409 +##### Custom Validators๐Ÿšง
  410 +
  411 +A validator object with a custom function can be created via
  412 +```cpp
  413 +CLI::Validator(std::function<std::string(std::string &>,validator_description,validator_name="");
  414 +```
  415 +or if the operation function is set later they can be created with
  416 +```cpp
  417 +CLI::Validator(validator_description);
  418 +```
  419 +
  420 + 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.
  421 +
  422 +##### Querying Validators ๐Ÿšง
  423 +Once loaded into an Option, a pointer to a named Validator can be retrieved via
  424 +```cpp
  425 +opt->get_validator(name);
  426 +```
  427 +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.
  428 +
  429 +Validators have a few functions to query the current values
  430 + * `get_description()`:๐Ÿšง Will return a description string
  431 + * `get_name()`:๐Ÿšง Will return the Validator name
  432 + * `get_active()`:๐Ÿšง Will return the current active state, true if the Validator is active.
  433 + * `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.
344 434
  435 +#### Getting results
345 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: 436 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:
346 437
347 - `results()`: Retrieves a vector of strings with all the results in the order they were given. 438 - `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. @@ -365,7 +456,7 @@ You are allowed to throw `CLI::Success` in the callbacks.
365 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). 456 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
366 457
367 ๐Ÿšง Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments. 458 ๐Ÿšง Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
368 -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. 459 +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.
369 460
370 #### Subcommand options 461 #### Subcommand options
371 462
@@ -378,16 +469,16 @@ There are several options that are supported on the main app and subcommands and @@ -378,16 +469,16 @@ There are several options that are supported on the main app and subcommands and
378 - `.disable()`: ๐Ÿšง Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. 469 - `.disable()`: ๐Ÿšง Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
379 - `.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. 470 - `.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.
380 - `.require_option()`: ๐Ÿšง Require 1 or more options or option groups be used. 471 - `.require_option()`: ๐Ÿšง Require 1 or more options or option groups be used.
381 -- `.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. 472 +- `.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.
382 - `.require_option(min, max)`: ๐Ÿšง Explicitly set min and max allowed options or option groups. Setting `max` to 0 is unlimited. 473 - `.require_option(min, max)`: ๐Ÿšง Explicitly set min and max allowed options or option groups. Setting `max` to 0 is unlimited.
383 - `.require_subcommand()`: Require 1 or more subcommands. 474 - `.require_subcommand()`: Require 1 or more subcommands.
384 -- `.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. 475 +- `.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.
385 - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. 476 - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
386 - `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand. 477 - `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand.
387 - `.add_subcommand(shared_ptr<App>)`: ๐Ÿšง Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand. 478 - `.add_subcommand(shared_ptr<App>)`: ๐Ÿšง Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
388 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. 479 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
389 - `.get_subcommands(filter)`: The list of subcommands that match a particular filter function. 480 - `.get_subcommands(filter)`: The list of subcommands that match a particular filter function.
390 -- `.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. 481 +- `.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.
391 - `.get_parent()`: Get the parent App or nullptr if called on master App. 482 - `.get_parent()`: Get the parent App or nullptr if called on master App.
392 - `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched 483 - `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched
393 - `.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. 484 - `.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 @@ -399,7 +490,7 @@ There are several options that are supported on the main app and subcommands and
399 - `.parsed()`: True if this subcommand was given on the command line. 490 - `.parsed()`: True if this subcommand was given on the command line.
400 - `.count()`: Returns the number of times the subcommand was called 491 - `.count()`: Returns the number of times the subcommand was called
401 - `.count(option_name)`: Returns the number of times a particular option was called 492 - `.count(option_name)`: Returns the number of times a particular option was called
402 -- `.count_all()`: ๐Ÿšง Returns the total number of arguments a particular subcommand had, on the master App it returns the total number of processed commands 493 +- `.count_all()`: ๐Ÿšง Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands
403 - `.name(name)`: Add or change the name. 494 - `.name(name)`: Add or change the name.
404 - `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point. 495 - `.callback(void() function)`: Set the callback that runs at the end of parsing. The options have already run at this point.
405 - `.allow_extras()`: Do not throw an error if extra arguments are left over. 496 - `.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 @@ -414,7 +505,7 @@ There are several options that are supported on the main app and subcommands and
414 505
415 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function. 506 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
416 507
417 -#### Option Groups ๐Ÿšง {#option-groups} 508 +#### Option groups ๐Ÿšง
418 509
419 The method 510 The method
420 ```cpp 511 ```cpp
examples/CMakeLists.txt
@@ -109,8 +109,8 @@ set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION @@ -109,8 +109,8 @@ set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION
109 add_cli_exe(validators validators.cpp) 109 add_cli_exe(validators validators.cpp)
110 add_test(NAME validators_help COMMAND validators --help) 110 add_test(NAME validators_help COMMAND validators --help)
111 set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION 111 set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION
112 - " -f,--file FILE File name"  
113 - " -v,--value INT in [3 - 6] Value in range") 112 + " -f,--file TEXT:FILE[\\r\\n\\t ]+File name"
  113 + " -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range")
114 add_test(NAME validators_file COMMAND validators --file nonex.xxx) 114 add_test(NAME validators_file COMMAND validators --file nonex.xxx)
115 set_property(TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION 115 set_property(TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION
116 "--file: File does not exist: nonex.xxx" 116 "--file: File does not exist: nonex.xxx"
@@ -155,7 +155,7 @@ add_cli_exe(enum enum.cpp) @@ -155,7 +155,7 @@ add_cli_exe(enum enum.cpp)
155 add_test(NAME enum_pass COMMAND enum -l 1) 155 add_test(NAME enum_pass COMMAND enum -l 1)
156 add_test(NAME enum_fail COMMAND enum -l 4) 156 add_test(NAME enum_fail COMMAND enum -l 4)
157 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION 157 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
158 - "--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}") 158 + "--level: Check 4 value in {" "FAILED")
159 159
160 add_cli_exe(digit_args digit_args.cpp) 160 add_cli_exe(digit_args digit_args.cpp)
161 add_test(NAME digit_args COMMAND digit_args -h) 161 add_test(NAME digit_args COMMAND digit_args -h)
examples/enum.cpp
1 #include <CLI/CLI.hpp> 1 #include <CLI/CLI.hpp>
2 -#include <map>  
3 2
4 enum class Level : int { High, Medium, Low }; 3 enum class Level : int { High, Medium, Low };
5 4
@@ -7,11 +6,14 @@ int main(int argc, char **argv) { @@ -7,11 +6,14 @@ int main(int argc, char **argv) {
7 CLI::App app; 6 CLI::App app;
8 7
9 Level level; 8 Level level;
10 - std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}};  
11 - 9 + // specify string->value mappings
  10 + std::vector<std::pair<std::string, Level>> map{
  11 + {"high", Level::High}, {"medium", Level::Medium}, {"low", Level::Low}};
  12 + // checked Transform does the translation and checks the results are either in one of the strings or one of the
  13 + // translations already
12 app.add_option("-l,--level", level, "Level settings") 14 app.add_option("-l,--level", level, "Level settings")
13 ->required() 15 ->required()
14 - ->transform(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low})); 16 + ->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
15 17
16 CLI11_PARSE(app, argc, argv); 18 CLI11_PARSE(app, argc, argv);
17 19
include/CLI/Option.hpp
@@ -244,7 +244,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -244,7 +244,7 @@ class Option : public OptionBase&lt;Option&gt; {
244 int expected_{1}; 244 int expected_{1};
245 245
246 /// A list of validators to run on each value parsed 246 /// A list of validators to run on each value parsed
247 - std::vector<std::function<std::string(std::string &)>> validators_; 247 + std::vector<Validator> validators_;
248 248
249 /// A list of options that are required with this option 249 /// A list of options that are required with this option
250 std::set<Option *> needs_; 250 std::set<Option *> needs_;
@@ -331,59 +331,70 @@ class Option : public OptionBase&lt;Option&gt; { @@ -331,59 +331,70 @@ class Option : public OptionBase&lt;Option&gt; {
331 return this; 331 return this;
332 } 332 }
333 333
334 - /// Adds a validator with a built in type name  
335 - Option *check(const Validator &validator) {  
336 - std::function<std::string(std::string &)> func = validator.func;  
337 - validators_.emplace_back([func](const std::string &value) {  
338 - /// Throw away changes to the string value  
339 - std::string ignore_changes_value = value;  
340 - return func(ignore_changes_value);  
341 - });  
342 - if(validator.tname_function)  
343 - type_name_fn(validator.tname_function);  
344 - else if(!validator.tname.empty())  
345 - type_name(validator.tname); 334 + /// Adds a Validator with a built in type name
  335 + Option *check(Validator validator, std::string validator_name = "") {
  336 + validator.non_modifying();
  337 + validators_.push_back(std::move(validator));
  338 + if(!validator_name.empty())
  339 + validators_.front().name(validator_name);
346 return this; 340 return this;
347 } 341 }
348 342
349 - /// Adds a validator. Takes a const string& and returns an error message (empty if conversion/check is okay).  
350 - Option *check(std::function<std::string(const std::string &)> validator) {  
351 - validators_.emplace_back(validator); 343 + /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
  344 + Option *check(std::function<std::string(const std::string &)> validator,
  345 + std::string validator_description = "",
  346 + std::string validator_name = "") {
  347 + validators_.emplace_back(validator, std::move(validator_description), std::move(validator_name));
  348 + validators_.back().non_modifying();
352 return this; 349 return this;
353 } 350 }
354 351
355 /// Adds a transforming validator with a built in type name 352 /// Adds a transforming validator with a built in type name
356 - Option *transform(const Validator &validator) {  
357 - validators_.emplace_back(validator.func);  
358 - if(validator.tname_function)  
359 - type_name_fn(validator.tname_function);  
360 - else if(!validator.tname.empty())  
361 - type_name(validator.tname); 353 + Option *transform(Validator validator, std::string validator_name = "") {
  354 + validators_.insert(validators_.begin(), std::move(validator));
  355 + if(!validator_name.empty())
  356 + validators_.front().name(validator_name);
362 return this; 357 return this;
363 } 358 }
364 359
365 /// Adds a validator-like function that can change result 360 /// Adds a validator-like function that can change result
366 - Option *transform(std::function<std::string(std::string)> func) {  
367 - validators_.emplace_back([func](std::string &inout) {  
368 - try {  
369 - inout = func(inout);  
370 - } catch(const ValidationError &e) {  
371 - return std::string(e.what());  
372 - }  
373 - return std::string();  
374 - }); 361 + Option *transform(std::function<std::string(std::string)> func,
  362 + std::string transform_description = "",
  363 + std::string transform_name = "") {
  364 + validators_.insert(validators_.begin(),
  365 + Validator(
  366 + [func](std::string &val) {
  367 + val = func(val);
  368 + return std::string{};
  369 + },
  370 + std::move(transform_description),
  371 + std::move(transform_name)));
  372 +
375 return this; 373 return this;
376 } 374 }
377 375
378 /// Adds a user supplied function to run on each item passed in (communicate though lambda capture) 376 /// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
379 Option *each(std::function<void(std::string)> func) { 377 Option *each(std::function<void(std::string)> func) {
380 - validators_.emplace_back([func](std::string &inout) {  
381 - func(inout);  
382 - return std::string();  
383 - }); 378 + validators_.emplace_back(
  379 + [func](std::string &inout) {
  380 + func(inout);
  381 + return std::string{};
  382 + },
  383 + std::string{});
384 return this; 384 return this;
385 } 385 }
386 - 386 + /// Get a named Validator
  387 + Validator *get_validator(const std::string &validator_name = "") {
  388 + for(auto &validator : validators_) {
  389 + if(validator_name == validator.get_name()) {
  390 + return &validator;
  391 + }
  392 + }
  393 + if((validator_name.empty()) && (!validators_.empty())) {
  394 + return &(validators_.front());
  395 + }
  396 + throw OptionNotFound(std::string("Validator ") + validator_name + " Not Found");
  397 + }
387 /// Sets required options 398 /// Sets required options
388 Option *needs(Option *opt) { 399 Option *needs(Option *opt) {
389 auto tup = needs_.insert(opt); 400 auto tup = needs_.insert(opt);
@@ -663,7 +674,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -663,7 +674,7 @@ class Option : public OptionBase&lt;Option&gt; {
663 try { 674 try {
664 err_msg = vali(result); 675 err_msg = vali(result);
665 } catch(const ValidationError &err) { 676 } catch(const ValidationError &err) {
666 - throw ValidationError(err.what(), get_name()); 677 + throw ValidationError(get_name(), err.what());
667 } 678 }
668 679
669 if(!err_msg.empty()) 680 if(!err_msg.empty())
@@ -938,8 +949,19 @@ class Option : public OptionBase&lt;Option&gt; { @@ -938,8 +949,19 @@ class Option : public OptionBase&lt;Option&gt; {
938 return this; 949 return this;
939 } 950 }
940 951
941 - /// Get the typename for this option  
942 - std::string get_type_name() const { return type_name_(); } 952 + /// Get the full typename for this option
  953 + std::string get_type_name() const {
  954 + std::string full_type_name = type_name_();
  955 + if(!validators_.empty()) {
  956 + for(auto &validator : validators_) {
  957 + std::string vtype = validator.get_description();
  958 + if(!vtype.empty()) {
  959 + full_type_name += ":" + vtype;
  960 + }
  961 + }
  962 + }
  963 + return full_type_name;
  964 + }
943 965
944 private: 966 private:
945 int _add_result(std::string &&result) { 967 int _add_result(std::string &&result) {
include/CLI/StringTools.hpp
@@ -57,15 +57,27 @@ inline std::vector&lt;std::string&gt; split(const std::string &amp;s, char delim) { @@ -57,15 +57,27 @@ inline std::vector&lt;std::string&gt; split(const std::string &amp;s, char delim) {
57 } 57 }
58 return elems; 58 return elems;
59 } 59 }
  60 +/// simple utility to convert various types to a string
  61 +template <typename T> inline std::string as_string(const T &v) {
  62 + std::ostringstream s;
  63 + s << v;
  64 + return s.str();
  65 +}
  66 +// if the data type is already a string just forward it
  67 +template <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>
  68 +inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) {
  69 + return std::forward<T>(v);
  70 +}
60 71
61 /// Simple function to join a string 72 /// Simple function to join a string
62 template <typename T> std::string join(const T &v, std::string delim = ",") { 73 template <typename T> std::string join(const T &v, std::string delim = ",") {
63 std::ostringstream s; 74 std::ostringstream s;
64 - size_t start = 0;  
65 - for(const auto &i : v) {  
66 - if(start++ > 0)  
67 - s << delim;  
68 - s << i; 75 + auto beg = std::begin(v);
  76 + auto end = std::end(v);
  77 + if(beg != end)
  78 + s << *beg++;
  79 + while(beg != end) {
  80 + s << delim << *beg++;
69 } 81 }
70 return s.str(); 82 return s.str();
71 } 83 }
@@ -76,11 +88,12 @@ template &lt;typename T, @@ -76,11 +88,12 @@ template &lt;typename T,
76 typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type> 88 typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
77 std::string join(const T &v, Callable func, std::string delim = ",") { 89 std::string join(const T &v, Callable func, std::string delim = ",") {
78 std::ostringstream s; 90 std::ostringstream s;
79 - size_t start = 0;  
80 - for(const auto &i : v) {  
81 - if(start++ > 0)  
82 - s << delim;  
83 - s << func(i); 91 + auto beg = std::begin(v);
  92 + auto end = std::end(v);
  93 + if(beg != end)
  94 + s << func(*beg++);
  95 + while(beg != end) {
  96 + s << delim << func(*beg++);
84 } 97 }
85 return s.str(); 98 return s.str();
86 } 99 }
include/CLI/TypeTools.hpp
@@ -58,6 +58,9 @@ template &lt;typename T&gt; struct is_shared_ptr : std::false_type {}; @@ -58,6 +58,9 @@ template &lt;typename T&gt; struct is_shared_ptr : std::false_type {};
58 /// Check to see if something is a shared pointer (True if really a shared pointer) 58 /// Check to see if something is a shared pointer (True if really a shared pointer)
59 template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {}; 59 template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
60 60
  61 +/// Check to see if something is a shared pointer (True if really a shared pointer)
  62 +template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {};
  63 +
61 /// Check to see if something is copyable pointer 64 /// Check to see if something is copyable pointer
62 template <typename T> struct is_copyable_ptr { 65 template <typename T> struct is_copyable_ptr {
63 static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value; 66 static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
@@ -71,7 +74,7 @@ template &lt;&gt; struct IsMemberType&lt;const char *&gt; { using type = std::string; }; @@ -71,7 +74,7 @@ template &lt;&gt; struct IsMemberType&lt;const char *&gt; { using type = std::string; };
71 74
72 namespace detail { 75 namespace detail {
73 76
74 -// These are utilites for IsMember 77 +// These are utilities for IsMember
75 78
76 /// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that 79 /// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
77 /// pointer_traits<T> be valid. 80 /// pointer_traits<T> be valid.
@@ -84,16 +87,20 @@ template &lt;typename T&gt; struct element_type { @@ -84,16 +87,20 @@ template &lt;typename T&gt; struct element_type {
84 /// the container 87 /// the container
85 template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; }; 88 template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
86 89
87 -/// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing. 90 +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
88 template <typename T, typename _ = void> struct pair_adaptor : std::false_type { 91 template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
89 using value_type = typename T::value_type; 92 using value_type = typename T::value_type;
90 using first_type = typename std::remove_const<value_type>::type; 93 using first_type = typename std::remove_const<value_type>::type;
91 using second_type = typename std::remove_const<value_type>::type; 94 using second_type = typename std::remove_const<value_type>::type;
92 95
93 /// Get the first value (really just the underlying value) 96 /// Get the first value (really just the underlying value)
94 - template <typename Q> static first_type first(Q &&value) { return value; } 97 + template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
  98 + return std::forward<Q>(pair_value);
  99 + }
95 /// Get the second value (really just the underlying value) 100 /// Get the second value (really just the underlying value)
96 - template <typename Q> static second_type second(Q &&value) { return value; } 101 + template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
  102 + return std::forward<Q>(pair_value);
  103 + }
97 }; 104 };
98 105
99 /// Adaptor for map-like structure (true version, must have key_type and mapped_type). 106 /// Adaptor for map-like structure (true version, must have key_type and mapped_type).
@@ -108,9 +115,13 @@ struct pair_adaptor&lt; @@ -108,9 +115,13 @@ struct pair_adaptor&lt;
108 using second_type = typename std::remove_const<typename value_type::second_type>::type; 115 using second_type = typename std::remove_const<typename value_type::second_type>::type;
109 116
110 /// Get the first value (really just the underlying value) 117 /// Get the first value (really just the underlying value)
111 - template <typename Q> static first_type first(Q &&value) { return value.first; } 118 + template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) {
  119 + return std::get<0>(std::forward<Q>(pair_value));
  120 + }
112 /// Get the second value (really just the underlying value) 121 /// Get the second value (really just the underlying value)
113 - template <typename Q> static second_type second(Q &&value) { return value.second; } 122 + template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) {
  123 + return std::get<1>(std::forward<Q>(pair_value));
  124 + }
114 }; 125 };
115 126
116 // Type name print 127 // Type name print
@@ -158,7 +169,7 @@ constexpr const char *type_name() { @@ -158,7 +169,7 @@ constexpr const char *type_name() {
158 169
159 // Lexical cast 170 // Lexical cast
160 171
161 -/// convert a flag into an integer value typically binary flags 172 +/// Convert a flag into an integer value typically binary flags
162 inline int64_t to_flag_value(std::string val) { 173 inline int64_t to_flag_value(std::string val) {
163 static const std::string trueString("true"); 174 static const std::string trueString("true");
164 static const std::string falseString("false"); 175 static const std::string falseString("false");
@@ -247,7 +258,7 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -247,7 +258,7 @@ bool lexical_cast(std::string input, T &amp;output) {
247 } 258 }
248 } 259 }
249 260
250 -/// boolean values 261 +/// Boolean values
251 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> 262 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
252 bool lexical_cast(std::string input, T &output) { 263 bool lexical_cast(std::string input, T &output) {
253 try { 264 try {
@@ -283,7 +294,7 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -283,7 +294,7 @@ bool lexical_cast(std::string input, T &amp;output) {
283 return true; 294 return true;
284 } 295 }
285 296
286 -/// enumerations 297 +/// Enumerations
287 template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> 298 template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
288 bool lexical_cast(std::string input, T &output) { 299 bool lexical_cast(std::string input, T &output) {
289 typename std::underlying_type<T>::type val; 300 typename std::underlying_type<T>::type val;
@@ -308,7 +319,7 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -308,7 +319,7 @@ bool lexical_cast(std::string input, T &amp;output) {
308 return !is.fail() && !is.rdbuf()->in_avail(); 319 return !is.fail() && !is.rdbuf()->in_avail();
309 } 320 }
310 321
311 -/// sum a vector of flag representations 322 +/// Sum a vector of flag representations
312 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by 323 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
313 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most 324 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
314 /// common true and false strings then uses stoll to convert the rest for summing 325 /// 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&lt;std::string&gt; &amp;flags, T &amp;output) { @@ -322,7 +333,7 @@ void sum_flag_vector(const std::vector&lt;std::string&gt; &amp;flags, T &amp;output) {
322 output = (count > 0) ? static_cast<T>(count) : T{0}; 333 output = (count > 0) ? static_cast<T>(count) : T{0};
323 } 334 }
324 335
325 -/// sum a vector of flag representations 336 +/// Sum a vector of flag representations
326 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by 337 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
327 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most 338 /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
328 /// common true and false strings then uses stoll to convert the rest for summing 339 /// common true and false strings then uses stoll to convert the rest for summing
include/CLI/Validators.hpp
@@ -32,50 +32,113 @@ class Option; @@ -32,50 +32,113 @@ class Option;
32 32
33 /// 33 ///
34 class Validator { 34 class Validator {
35 - friend Option;  
36 -  
37 protected: 35 protected:
38 - /// This is the type name, if empty the type name will not be changed  
39 - std::string tname;  
40 -  
41 - /// This is the type function, if empty the tname will be used  
42 - std::function<std::string()> tname_function; 36 + /// This is the description function, if empty the description_ will be used
  37 + std::function<std::string()> desc_function_{[]() { return std::string{}; }};
43 38
44 /// This it the base function that is to be called. 39 /// This it the base function that is to be called.
45 /// Returns a string error message if validation fails. 40 /// Returns a string error message if validation fails.
46 - std::function<std::string(std::string &)> func; 41 + std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
  42 + /// The name for search purposes of the Validator
  43 + std::string name_;
  44 + /// Enable for Validator to allow it to be disabled if need be
  45 + bool active_{true};
  46 + /// specify that a validator should not modify the input
  47 + bool non_modifying_{false};
47 48
48 public: 49 public:
49 - /// This is the required operator for a validator - provided to help 50 + Validator() = default;
  51 + /// Construct a Validator with just the description string
  52 + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {}
  53 + // Construct Validator from basic information
  54 + Validator(std::function<std::string(std::string &)> op, std::string validator_desc, std::string validator_name = "")
  55 + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)),
  56 + name_(std::move(validator_name)) {}
  57 + /// Set the Validator operation function
  58 + Validator &operation(std::function<std::string(std::string &)> op) {
  59 + func_ = std::move(op);
  60 + return *this;
  61 + }
  62 + /// This is the required operator for a Validator - provided to help
50 /// users (CLI11 uses the member `func` directly) 63 /// users (CLI11 uses the member `func` directly)
51 - std::string operator()(std::string &str) const { return func(str); }; 64 + std::string operator()(std::string &str) const {
  65 + std::string retstring;
  66 + if(active_) {
  67 + if(non_modifying_) {
  68 + std::string value = str;
  69 + retstring = func_(value);
  70 + } else {
  71 + retstring = func_(str);
  72 + }
  73 + }
  74 + return retstring;
  75 + };
52 76
53 - /// This is the required operator for a validator - provided to help 77 + /// This is the required operator for a Validator - provided to help
54 /// users (CLI11 uses the member `func` directly) 78 /// users (CLI11 uses the member `func` directly)
55 std::string operator()(const std::string &str) const { 79 std::string operator()(const std::string &str) const {
56 std::string value = str; 80 std::string value = str;
57 - return func(value); 81 + return (active_) ? func_(value) : std::string{};
58 }; 82 };
59 83
  84 + /// Specify the type string
  85 + Validator &description(std::string validator_desc) {
  86 + desc_function_ = [validator_desc]() { return validator_desc; };
  87 + return *this;
  88 + }
  89 + /// Generate type description information for the Validator
  90 + std::string get_description() const {
  91 + if(active_) {
  92 + return desc_function_();
  93 + }
  94 + return std::string{};
  95 + }
  96 + /// Specify the type string
  97 + Validator &name(std::string validator_name) {
  98 + name_ = std::move(validator_name);
  99 + return *this;
  100 + }
  101 + /// Get the name of the Validator
  102 + const std::string &get_name() const { return name_; }
  103 + /// Specify whether the Validator is active or not
  104 + Validator &active(bool active_val = true) {
  105 + active_ = active_val;
  106 + return *this;
  107 + }
  108 +
  109 + /// Specify whether the Validator can be modifying or not
  110 + Validator &non_modifying(bool no_modify = true) {
  111 + non_modifying_ = no_modify;
  112 + return *this;
  113 + }
  114 +
  115 + /// Get a boolean if the validator is active
  116 + bool get_active() const { return active_; }
  117 +
  118 + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input
  119 + bool get_modifying() const { return !non_modifying_; }
  120 +
60 /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the 121 /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
61 /// same. 122 /// same.
62 Validator operator&(const Validator &other) const { 123 Validator operator&(const Validator &other) const {
63 Validator newval; 124 Validator newval;
64 - newval.tname = (tname == other.tname ? tname : "");  
65 - newval.tname_function = tname_function; 125 +
  126 + newval._merge_description(*this, other, " AND ");
66 127
67 // Give references (will make a copy in lambda function) 128 // Give references (will make a copy in lambda function)
68 - const std::function<std::string(std::string & filename)> &f1 = func;  
69 - const std::function<std::string(std::string & filename)> &f2 = other.func; 129 + const std::function<std::string(std::string & filename)> &f1 = func_;
  130 + const std::function<std::string(std::string & filename)> &f2 = other.func_;
70 131
71 - newval.func = [f1, f2](std::string &input) { 132 + newval.func_ = [f1, f2](std::string &input) {
72 std::string s1 = f1(input); 133 std::string s1 = f1(input);
73 std::string s2 = f2(input); 134 std::string s2 = f2(input);
74 if(!s1.empty() && !s2.empty()) 135 if(!s1.empty() && !s2.empty())
75 - return s1 + " AND " + s2; 136 + return std::string("(") + s1 + ") AND (" + s2 + ")";
76 else 137 else
77 return s1 + s2; 138 return s1 + s2;
78 }; 139 };
  140 +
  141 + newval.active_ = (active_ & other.active_);
79 return newval; 142 return newval;
80 } 143 }
81 144
@@ -83,48 +146,68 @@ class Validator { @@ -83,48 +146,68 @@ class Validator {
83 /// same. 146 /// same.
84 Validator operator|(const Validator &other) const { 147 Validator operator|(const Validator &other) const {
85 Validator newval; 148 Validator newval;
86 - newval.tname = (tname == other.tname ? tname : "");  
87 - newval.tname_function = tname_function; 149 +
  150 + newval._merge_description(*this, other, " OR ");
88 151
89 // Give references (will make a copy in lambda function) 152 // Give references (will make a copy in lambda function)
90 - const std::function<std::string(std::string &)> &f1 = func;  
91 - const std::function<std::string(std::string &)> &f2 = other.func; 153 + const std::function<std::string(std::string &)> &f1 = func_;
  154 + const std::function<std::string(std::string &)> &f2 = other.func_;
92 155
93 - newval.func = [f1, f2](std::string &input) { 156 + newval.func_ = [f1, f2](std::string &input) {
94 std::string s1 = f1(input); 157 std::string s1 = f1(input);
95 std::string s2 = f2(input); 158 std::string s2 = f2(input);
96 if(s1.empty() || s2.empty()) 159 if(s1.empty() || s2.empty())
97 return std::string(); 160 return std::string();
98 else 161 else
99 - return s1 + " OR " + s2; 162 + return std::string("(") + s1 + ") OR (" + s2 + ")";
100 }; 163 };
  164 + newval.active_ = (active_ & other.active_);
101 return newval; 165 return newval;
102 } 166 }
103 167
104 /// Create a validator that fails when a given validator succeeds 168 /// Create a validator that fails when a given validator succeeds
105 Validator operator!() const { 169 Validator operator!() const {
106 Validator newval; 170 Validator newval;
107 - std::string typestring = tname;  
108 - if(tname.empty()) {  
109 - typestring = tname_function();  
110 - }  
111 - newval.tname = "NOT " + typestring;  
112 -  
113 - std::string failString = "check " + typestring + " succeeded improperly"; 171 + const std::function<std::string()> &dfunc1 = desc_function_;
  172 + newval.desc_function_ = [dfunc1]() {
  173 + auto str = dfunc1();
  174 + return (!str.empty()) ? std::string("NOT ") + str : std::string{};
  175 + };
114 // Give references (will make a copy in lambda function) 176 // Give references (will make a copy in lambda function)
115 - const std::function<std::string(std::string & res)> &f1 = func; 177 + const std::function<std::string(std::string & res)> &f1 = func_;
116 178
117 - newval.func = [f1, failString](std::string &test) -> std::string { 179 + newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
118 std::string s1 = f1(test); 180 std::string s1 = f1(test);
119 - if(s1.empty())  
120 - return failString;  
121 - else  
122 - return std::string(); 181 + if(s1.empty()) {
  182 + return std::string("check ") + dfunc1() + " succeeded improperly";
  183 + } else
  184 + return std::string{};
123 }; 185 };
  186 + newval.active_ = active_;
124 return newval; 187 return newval;
125 } 188 }
  189 +
  190 + private:
  191 + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
  192 +
  193 + const std::function<std::string()> &dfunc1 = val1.desc_function_;
  194 + const std::function<std::string()> &dfunc2 = val2.desc_function_;
  195 +
  196 + desc_function_ = [=]() {
  197 + std::string f1 = dfunc1();
  198 + std::string f2 = dfunc2();
  199 + if((f1.empty()) || (f2.empty())) {
  200 + return f1 + f2;
  201 + }
  202 + return std::string("(") + f1 + ")" + merger + "(" + f2 + ")";
  203 + };
  204 + }
126 }; 205 };
127 206
  207 +/// Class wrapping some of the accessors of Validator
  208 +class CustomValidator : public Validator {
  209 + public:
  210 +};
128 // The implementation of the built in validators is using the Validator class; 211 // The implementation of the built in validators is using the Validator class;
129 // the user is only expected to use the const (static) versions (since there's no setup). 212 // the user is only expected to use the const (static) versions (since there's no setup).
130 // Therefore, this is in detail. 213 // Therefore, this is in detail.
@@ -133,9 +216,8 @@ namespace detail { @@ -133,9 +216,8 @@ namespace detail {
133 /// Check for an existing file (returns error message if check fails) 216 /// Check for an existing file (returns error message if check fails)
134 class ExistingFileValidator : public Validator { 217 class ExistingFileValidator : public Validator {
135 public: 218 public:
136 - ExistingFileValidator() {  
137 - tname = "FILE";  
138 - func = [](std::string &filename) { 219 + ExistingFileValidator() : Validator("FILE") {
  220 + func_ = [](std::string &filename) {
139 struct stat buffer; 221 struct stat buffer;
140 bool exist = stat(filename.c_str(), &buffer) == 0; 222 bool exist = stat(filename.c_str(), &buffer) == 0;
141 bool is_dir = (buffer.st_mode & S_IFDIR) != 0; 223 bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
@@ -152,9 +234,8 @@ class ExistingFileValidator : public Validator { @@ -152,9 +234,8 @@ class ExistingFileValidator : public Validator {
152 /// Check for an existing directory (returns error message if check fails) 234 /// Check for an existing directory (returns error message if check fails)
153 class ExistingDirectoryValidator : public Validator { 235 class ExistingDirectoryValidator : public Validator {
154 public: 236 public:
155 - ExistingDirectoryValidator() {  
156 - tname = "DIR";  
157 - func = [](std::string &filename) { 237 + ExistingDirectoryValidator() : Validator("DIR") {
  238 + func_ = [](std::string &filename) {
158 struct stat buffer; 239 struct stat buffer;
159 bool exist = stat(filename.c_str(), &buffer) == 0; 240 bool exist = stat(filename.c_str(), &buffer) == 0;
160 bool is_dir = (buffer.st_mode & S_IFDIR) != 0; 241 bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
@@ -171,9 +252,8 @@ class ExistingDirectoryValidator : public Validator { @@ -171,9 +252,8 @@ class ExistingDirectoryValidator : public Validator {
171 /// Check for an existing path 252 /// Check for an existing path
172 class ExistingPathValidator : public Validator { 253 class ExistingPathValidator : public Validator {
173 public: 254 public:
174 - ExistingPathValidator() {  
175 - tname = "PATH";  
176 - func = [](std::string &filename) { 255 + ExistingPathValidator() : Validator("PATH(existing)") {
  256 + func_ = [](std::string &filename) {
177 struct stat buffer; 257 struct stat buffer;
178 bool const exist = stat(filename.c_str(), &buffer) == 0; 258 bool const exist = stat(filename.c_str(), &buffer) == 0;
179 if(!exist) { 259 if(!exist) {
@@ -187,9 +267,8 @@ class ExistingPathValidator : public Validator { @@ -187,9 +267,8 @@ class ExistingPathValidator : public Validator {
187 /// Check for an non-existing path 267 /// Check for an non-existing path
188 class NonexistentPathValidator : public Validator { 268 class NonexistentPathValidator : public Validator {
189 public: 269 public:
190 - NonexistentPathValidator() {  
191 - tname = "PATH";  
192 - func = [](std::string &filename) { 270 + NonexistentPathValidator() : Validator("PATH(non-existing)") {
  271 + func_ = [](std::string &filename) {
193 struct stat buffer; 272 struct stat buffer;
194 bool exist = stat(filename.c_str(), &buffer) == 0; 273 bool exist = stat(filename.c_str(), &buffer) == 0;
195 if(exist) { 274 if(exist) {
@@ -203,9 +282,8 @@ class NonexistentPathValidator : public Validator { @@ -203,9 +282,8 @@ class NonexistentPathValidator : public Validator {
203 /// Validate the given string is a legal ipv4 address 282 /// Validate the given string is a legal ipv4 address
204 class IPV4Validator : public Validator { 283 class IPV4Validator : public Validator {
205 public: 284 public:
206 - IPV4Validator() {  
207 - tname = "IPV4";  
208 - func = [](std::string &ip_addr) { 285 + IPV4Validator() : Validator("IPV4") {
  286 + func_ = [](std::string &ip_addr) {
209 auto result = CLI::detail::split(ip_addr, '.'); 287 auto result = CLI::detail::split(ip_addr, '.');
210 if(result.size() != 4) { 288 if(result.size() != 4) {
211 return "Invalid IPV4 address must have four parts " + ip_addr; 289 return "Invalid IPV4 address must have four parts " + ip_addr;
@@ -229,9 +307,8 @@ class IPV4Validator : public Validator { @@ -229,9 +307,8 @@ class IPV4Validator : public Validator {
229 /// Validate the argument is a number and greater than or equal to 0 307 /// Validate the argument is a number and greater than or equal to 0
230 class PositiveNumber : public Validator { 308 class PositiveNumber : public Validator {
231 public: 309 public:
232 - PositiveNumber() {  
233 - tname = "POSITIVE";  
234 - func = [](std::string &number_str) { 310 + PositiveNumber() : Validator("POSITIVE") {
  311 + func_ = [](std::string &number_str) {
235 int number; 312 int number;
236 if(!detail::lexical_cast(number_str, number)) { 313 if(!detail::lexical_cast(number_str, number)) {
237 return "Failed parsing number " + number_str; 314 return "Failed parsing number " + number_str;
@@ -276,12 +353,12 @@ class Range : public Validator { @@ -276,12 +353,12 @@ class Range : public Validator {
276 template <typename T> Range(T min, T max) { 353 template <typename T> Range(T min, T max) {
277 std::stringstream out; 354 std::stringstream out;
278 out << detail::type_name<T>() << " in [" << min << " - " << max << "]"; 355 out << detail::type_name<T>() << " in [" << min << " - " << max << "]";
  356 + description(out.str());
279 357
280 - tname = out.str();  
281 - func = [min, max](std::string &input) { 358 + func_ = [min, max](std::string &input) {
282 T val; 359 T val;
283 - detail::lexical_cast(input, val);  
284 - if(val < min || val > max) 360 + bool converted = detail::lexical_cast(input, val);
  361 + if((!converted) || (val < min || val > max))
285 return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); 362 return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max);
286 363
287 return std::string(); 364 return std::string();
@@ -292,18 +369,126 @@ class Range : public Validator { @@ -292,18 +369,126 @@ class Range : public Validator {
292 template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {} 369 template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {}
293 }; 370 };
294 371
  372 +/// Produce a bounded range (factory). Min and max are inclusive.
  373 +class Bound : public Validator {
  374 + public:
  375 + /// This bounds a value with min and max inclusive.
  376 + ///
  377 + /// Note that the constructor is templated, but the struct is not, so C++17 is not
  378 + /// needed to provide nice syntax for Range(a,b).
  379 + template <typename T> Bound(T min, T max) {
  380 + std::stringstream out;
  381 + out << detail::type_name<T>() << " bounded to [" << min << " - " << max << "]";
  382 + description(out.str());
  383 +
  384 + func_ = [min, max](std::string &input) {
  385 + T val;
  386 + bool converted = detail::lexical_cast(input, val);
  387 + if(!converted) {
  388 + return "Value " + input + " could not be converted";
  389 + }
  390 + if(val < min)
  391 + input = detail::as_string(min);
  392 + else if(val > max)
  393 + input = detail::as_string(max);
  394 +
  395 + return std::string();
  396 + };
  397 + }
  398 +
  399 + /// Range of one value is 0 to value
  400 + template <typename T> explicit Bound(T max) : Bound(static_cast<T>(0), max) {}
  401 +};
  402 +
295 namespace detail { 403 namespace detail {
296 -template <typename T, enable_if_t<is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> 404 +template <typename T,
  405 + enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
297 auto smart_deref(T value) -> decltype(*value) { 406 auto smart_deref(T value) -> decltype(*value) {
298 return *value; 407 return *value;
299 } 408 }
300 409
301 -template <typename T, enable_if_t<!is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> T smart_deref(T value) { 410 +template <
  411 + typename T,
  412 + enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
  413 +typename std::remove_reference<T>::type &smart_deref(T &value) {
302 return value; 414 return value;
303 } 415 }
  416 +/// Generate a string representation of a set
  417 +template <typename T> std::string generate_set(const T &set) {
  418 + using element_t = typename detail::element_type<T>::type;
  419 + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
  420 + std::string out(1, '{');
  421 + out.append(detail::join(detail::smart_deref(set),
  422 + [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
  423 + ","));
  424 + out.push_back('}');
  425 + return out;
  426 +}
304 427
305 -} // namespace detail 428 +/// Generate a string representation of a map
  429 +template <typename T> std::string generate_map(const T &map) {
  430 + using element_t = typename detail::element_type<T>::type;
  431 + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
  432 + std::string out(1, '{');
  433 + out.append(detail::join(detail::smart_deref(map),
  434 + [](const iteration_type_t &v) {
  435 + return detail::as_string(detail::pair_adaptor<element_t>::first(v)) + "->" +
  436 + detail::as_string(detail::pair_adaptor<element_t>::second(v));
  437 + },
  438 + ","));
  439 + out.push_back('}');
  440 + return out;
  441 +}
  442 +
  443 +template <typename> struct sfinae_true : std::true_type {};
  444 +/// Function to check for the existence of a member find function which presumably is more efficient than looping over
  445 +/// everything
  446 +template <typename T, typename V>
  447 +static auto test_find(int) -> sfinae_true<decltype(std::declval<T>().find(std::declval<V>()))>;
  448 +template <typename, typename V> static auto test_find(long) -> std::false_type;
  449 +
  450 +template <typename T, typename V> struct has_find : decltype(test_find<T, V>(0)) {};
  451 +
  452 +/// A search function
  453 +template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
  454 +auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
  455 + using element_t = typename detail::element_type<T>::type;
  456 + auto &setref = detail::smart_deref(set);
  457 + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
  458 + return (detail::pair_adaptor<element_t>::first(v) == val);
  459 + });
  460 + return {(it != std::end(setref)), it};
  461 +}
  462 +
  463 +/// A search function that uses the built in find function
  464 +template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
  465 +auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
  466 + auto &setref = detail::smart_deref(set);
  467 + auto it = setref.find(val);
  468 + return {(it != std::end(setref)), it};
  469 +}
  470 +
  471 +/// A search function with a filter function
  472 +template <typename T, typename V>
  473 +auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
  474 + -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
  475 + using element_t = typename detail::element_type<T>::type;
  476 + // do the potentially faster first search
  477 + auto res = search(set, val);
  478 + if((res.first) || (!(filter_function))) {
  479 + return res;
  480 + }
  481 + // if we haven't found it do the longer linear search with all the element translations
  482 + auto &setref = detail::smart_deref(set);
  483 + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
  484 + V a = detail::pair_adaptor<element_t>::first(v);
  485 + a = filter_function(a);
  486 + return (a == val);
  487 + });
  488 + return {(it != std::end(setref)), it};
  489 +}
306 490
  491 +} // namespace detail
307 /// Verify items are in a set 492 /// Verify items are in a set
308 class IsMember : public Validator { 493 class IsMember : public Validator {
309 public: 494 public:
@@ -315,7 +500,7 @@ class IsMember : public Validator { @@ -315,7 +500,7 @@ class IsMember : public Validator {
315 : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {} 500 : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
316 501
317 /// This checks to see if an item is in a set (empty function) 502 /// This checks to see if an item is in a set (empty function)
318 - template <typename T> explicit IsMember(T set) : IsMember(std::move(set), nullptr) {} 503 + template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
319 504
320 /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter 505 /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
321 /// both sides of the comparison before computing the comparison. 506 /// both sides of the comparison before computing the comparison.
@@ -333,76 +518,198 @@ class IsMember : public Validator { @@ -333,76 +518,198 @@ class IsMember : public Validator {
333 std::function<local_item_t(local_item_t)> filter_fn = filter_function; 518 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
334 519
335 // This is the type name for help, it will take the current version of the set contents 520 // This is the type name for help, it will take the current version of the set contents
336 - tname_function = [set]() {  
337 - std::stringstream out;  
338 - out << "{";  
339 - int i = 0; // I don't like counters like this  
340 - for(const auto &v : detail::smart_deref(set))  
341 - out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v);  
342 - out << "}";  
343 - return out.str();  
344 - }; 521 + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
345 522
346 // This is the function that validates 523 // This is the function that validates
347 // It stores a copy of the set pointer-like, so shared_ptr will stay alive 524 // It stores a copy of the set pointer-like, so shared_ptr will stay alive
348 - func = [set, filter_fn](std::string &input) {  
349 - for(const auto &v : detail::smart_deref(set)) {  
350 - local_item_t a = detail::pair_adaptor<element_t>::first(v);  
351 - local_item_t b;  
352 - if(!detail::lexical_cast(input, b))  
353 - throw ValidationError(input); // name is added later  
354 -  
355 - // The filter function might be empty, so don't filter if it is. 525 + func_ = [set, filter_fn](std::string &input) {
  526 + local_item_t b;
  527 + if(!detail::lexical_cast(input, b)) {
  528 + throw ValidationError(input); // name is added later
  529 + }
  530 + if(filter_fn) {
  531 + b = filter_fn(b);
  532 + }
  533 + auto res = detail::search(set, b, filter_fn);
  534 + if(res.first) {
  535 + // Make sure the version in the input string is identical to the one in the set
356 if(filter_fn) { 536 if(filter_fn) {
357 - a = filter_fn(a);  
358 - b = filter_fn(b); 537 + input = detail::as_string(detail::pair_adaptor<element_t>::first(*(res.second)));
359 } 538 }
360 539
361 - if(a == b) {  
362 - // Make sure the version in the input string is identical to the one in the set  
363 - // Requires std::stringstream << be supported on T.  
364 - // If this is a map, output the map instead.  
365 - if(filter_fn || detail::pair_adaptor<element_t>::value) {  
366 - std::stringstream out;  
367 - out << detail::pair_adaptor<element_t>::second(v);  
368 - input = out.str();  
369 - }  
370 -  
371 - // Return empty error string (success)  
372 - return std::string();  
373 - } 540 + // Return empty error string (success)
  541 + return std::string{};
374 } 542 }
375 543
376 // If you reach this point, the result was not found 544 // If you reach this point, the result was not found
377 - std::stringstream out;  
378 - out << input << " not in {";  
379 - int i = 0; // I still don't like counters like this  
380 - for(const auto &v : detail::smart_deref(set))  
381 - out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v);  
382 - out << "}";  
383 - return out.str(); 545 + std::string out(" not in ");
  546 + out += detail::generate_set(detail::smart_deref(set));
  547 + return out;
384 }; 548 };
385 } 549 }
386 550
387 /// You can pass in as many filter functions as you like, they nest (string only currently) 551 /// You can pass in as many filter functions as you like, they nest (string only currently)
388 template <typename T, typename... Args> 552 template <typename T, typename... Args>
389 - IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)  
390 - : IsMember(std::move(set), 553 + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
  554 + : IsMember(std::forward<T>(set),
391 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, 555 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
392 other...) {} 556 other...) {}
393 }; 557 };
394 558
395 -/// Helper function to allow ignore_case to be passed to IsMember 559 +/// definition of the default transformation object
  560 +template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
  561 +
  562 +/// Translate named items to other or a value set
  563 +class Transformer : public Validator {
  564 + public:
  565 + using filter_fn_t = std::function<std::string(std::string)>;
  566 +
  567 + /// This allows in-place construction
  568 + template <typename... Args>
  569 + explicit Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args)
  570 + : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
  571 +
  572 + /// direct map of std::string to std::string
  573 + template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
  574 +
  575 + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
  576 + /// both sides of the comparison before computing the comparison.
  577 + template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
  578 +
  579 + static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
  580 + "mapping must produce value pairs");
  581 + // Get the type of the contained item - requires a container have ::value_type
  582 + // if the type does not have first_type and second_type, these are both value_type
  583 + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
  584 + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
  585 + using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
  586 + // (const char * to std::string)
  587 +
  588 + // Make a local copy of the filter function, using a std::function if not one already
  589 + std::function<local_item_t(local_item_t)> filter_fn = filter_function;
  590 +
  591 + // This is the type name for help, it will take the current version of the set contents
  592 + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
  593 +
  594 + func_ = [mapping, filter_fn](std::string &input) {
  595 + local_item_t b;
  596 + if(!detail::lexical_cast(input, b)) {
  597 + return std::string();
  598 + // there is no possible way we can match anything in the mapping if we can't convert so just return
  599 + }
  600 + if(filter_fn) {
  601 + b = filter_fn(b);
  602 + }
  603 + auto res = detail::search(mapping, b, filter_fn);
  604 + if(res.first) {
  605 + input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));
  606 + }
  607 + return std::string{};
  608 + };
  609 + }
  610 +
  611 + /// You can pass in as many filter functions as you like, they nest
  612 + template <typename T, typename... Args>
  613 + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
  614 + : Transformer(std::forward<T>(mapping),
  615 + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
  616 + other...) {}
  617 +};
  618 +
  619 +/// translate named items to other or a value set
  620 +class CheckedTransformer : public Validator {
  621 + public:
  622 + using filter_fn_t = std::function<std::string(std::string)>;
  623 +
  624 + /// This allows in-place construction
  625 + template <typename... Args>
  626 + explicit CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args)
  627 + : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
  628 +
  629 + /// direct map of std::string to std::string
  630 + template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
  631 +
  632 + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
  633 + /// both sides of the comparison before computing the comparison.
  634 + template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
  635 +
  636 + static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
  637 + "mapping must produce value pairs");
  638 + // Get the type of the contained item - requires a container have ::value_type
  639 + // if the type does not have first_type and second_type, these are both value_type
  640 + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
  641 + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
  642 + using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
  643 + // (const char * to std::string)
  644 + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair //
  645 + // the type of the object pair
  646 +
  647 + // Make a local copy of the filter function, using a std::function if not one already
  648 + std::function<local_item_t(local_item_t)> filter_fn = filter_function;
  649 +
  650 + auto tfunc = [mapping]() {
  651 + std::string out("value in ");
  652 + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
  653 + out += detail::join(
  654 + detail::smart_deref(mapping),
  655 + [](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor<element_t>::second(v)); },
  656 + ",");
  657 + out.push_back('}');
  658 + return out;
  659 + };
  660 +
  661 + desc_function_ = tfunc;
  662 +
  663 + func_ = [mapping, tfunc, filter_fn](std::string &input) {
  664 + local_item_t b;
  665 + bool converted = detail::lexical_cast(input, b);
  666 + if(converted) {
  667 + if(filter_fn) {
  668 + b = filter_fn(b);
  669 + }
  670 + auto res = detail::search(mapping, b, filter_fn);
  671 + if(res.first) {
  672 + input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));
  673 + return std::string{};
  674 + }
  675 + }
  676 + for(const auto &v : detail::smart_deref(mapping)) {
  677 + auto output_string = detail::as_string(detail::pair_adaptor<element_t>::second(v));
  678 + if(output_string == input) {
  679 + return std::string();
  680 + }
  681 + }
  682 +
  683 + return "Check " + input + " " + tfunc() + " FAILED";
  684 + };
  685 + }
  686 +
  687 + /// You can pass in as many filter functions as you like, they nest
  688 + template <typename T, typename... Args>
  689 + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
  690 + : CheckedTransformer(std::forward<T>(mapping),
  691 + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
  692 + other...) {}
  693 +}; // namespace CLI
  694 +
  695 +/// Helper function to allow ignore_case to be passed to IsMember or Transform
396 inline std::string ignore_case(std::string item) { return detail::to_lower(item); } 696 inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
397 697
398 -/// Helper function to allow ignore_underscore to be passed to IsMember 698 +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform
399 inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } 699 inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
400 700
  701 +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform
  702 +inline std::string ignore_space(std::string item) {
  703 + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
  704 + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
  705 + return item;
  706 +}
  707 +
401 namespace detail { 708 namespace detail {
402 /// Split a string into a program name and command line arguments 709 /// Split a string into a program name and command line arguments
403 /// the string is assumed to contain a file name followed by other arguments 710 /// the string is assumed to contain a file name followed by other arguments
404 -/// the return value contains is a pair with the first argument containing the program name and the second everything  
405 -/// else. 711 +/// the return value contains is a pair with the first argument containing the program name and the second
  712 +/// everything else.
406 inline std::pair<std::string, std::string> split_program_name(std::string commandline) { 713 inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
407 // try to determine the programName 714 // try to determine the programName
408 std::pair<std::string, std::string> vals; 715 std::pair<std::string, std::string> vals;
tests/AppTest.cpp
@@ -1431,7 +1431,7 @@ TEST_F(TApp, FileNotExists) { @@ -1431,7 +1431,7 @@ TEST_F(TApp, FileNotExists) {
1431 ASSERT_NO_THROW(CLI::NonexistentPath(myfile)); 1431 ASSERT_NO_THROW(CLI::NonexistentPath(myfile));
1432 1432
1433 std::string filename; 1433 std::string filename;
1434 - app.add_option("--file", filename)->check(CLI::NonexistentPath); 1434 + auto opt = app.add_option("--file", filename)->check(CLI::NonexistentPath, "path_check");
1435 args = {"--file", myfile}; 1435 args = {"--file", myfile};
1436 1436
1437 run(); 1437 run();
@@ -1440,7 +1440,9 @@ TEST_F(TApp, FileNotExists) { @@ -1440,7 +1440,9 @@ TEST_F(TApp, FileNotExists) {
1440 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 1440 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
1441 EXPECT_TRUE(ok); 1441 EXPECT_TRUE(ok);
1442 EXPECT_THROW(run(), CLI::ValidationError); 1442 EXPECT_THROW(run(), CLI::ValidationError);
1443 - 1443 + // deactivate the check, so it should run now
  1444 + opt->get_validator("path_check")->active(false);
  1445 + EXPECT_NO_THROW(run());
1444 std::remove(myfile.c_str()); 1446 std::remove(myfile.c_str());
1445 EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); 1447 EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
1446 } 1448 }
@@ -1861,7 +1863,7 @@ TEST_F(TApp, OrderedModifingTransforms) { @@ -1861,7 +1863,7 @@ TEST_F(TApp, OrderedModifingTransforms) {
1861 1863
1862 run(); 1864 run();
1863 1865
1864 - EXPECT_EQ(val, std::vector<std::string>({"one12", "two12"})); 1866 + EXPECT_EQ(val, std::vector<std::string>({"one21", "two21"}));
1865 } 1867 }
1866 1868
1867 TEST_F(TApp, ThrowingTransform) { 1869 TEST_F(TApp, ThrowingTransform) {
tests/CMakeLists.txt
@@ -27,6 +27,7 @@ set(CLI11_TESTS @@ -27,6 +27,7 @@ set(CLI11_TESTS
27 SimpleTest 27 SimpleTest
28 AppTest 28 AppTest
29 SetTest 29 SetTest
  30 + TransformTest
30 CreationTest 31 CreationTest
31 SubcommandTest 32 SubcommandTest
32 HelpTest 33 HelpTest
tests/CreationTest.cpp
@@ -561,3 +561,164 @@ TEST_F(TApp, GetOptionList) { @@ -561,3 +561,164 @@ TEST_F(TApp, GetOptionList) {
561 EXPECT_EQ(opt_list.at(1), flag); 561 EXPECT_EQ(opt_list.at(1), flag);
562 EXPECT_EQ(opt_list.at(2), opt); 562 EXPECT_EQ(opt_list.at(2), opt);
563 } 563 }
  564 +
  565 +TEST(ValidatorTests, TestValidatorCreation) {
  566 + std::function<std::string(std::string &)> op1 = [](std::string &val) {
  567 + return (val.size() >= 5) ? std::string{} : val;
  568 + };
  569 + CLI::Validator V(op1, "", "size");
  570 +
  571 + EXPECT_EQ(V.get_name(), "size");
  572 + V.name("harry");
  573 + EXPECT_EQ(V.get_name(), "harry");
  574 + EXPECT_TRUE(V.get_active());
  575 +
  576 + EXPECT_EQ(V("test"), "test");
  577 + EXPECT_EQ(V("test5"), std::string{});
  578 +
  579 + EXPECT_EQ(V.get_description(), std::string{});
  580 + V.description("this is a description");
  581 + EXPECT_EQ(V.get_description(), "this is a description");
  582 +}
  583 +
  584 +TEST(ValidatorTests, TestValidatorOps) {
  585 + std::function<std::string(std::string &)> op1 = [](std::string &val) {
  586 + return (val.size() >= 5) ? std::string{} : val;
  587 + };
  588 + std::function<std::string(std::string &)> op2 = [](std::string &val) {
  589 + return (val.size() >= 9) ? std::string{} : val;
  590 + };
  591 + std::function<std::string(std::string &)> op3 = [](std::string &val) {
  592 + return (val.size() < 3) ? std::string{} : val;
  593 + };
  594 + std::function<std::string(std::string &)> op4 = [](std::string &val) {
  595 + return (val.size() <= 9) ? std::string{} : val;
  596 + };
  597 + CLI::Validator V1(op1, "SIZE >= 5");
  598 +
  599 + CLI::Validator V2(op2, "SIZE >= 9");
  600 + CLI::Validator V3(op3, "SIZE < 3");
  601 + CLI::Validator V4(op4, "SIZE <= 9");
  602 +
  603 + std::string two(2, 'a');
  604 + std::string four(4, 'a');
  605 + std::string five(5, 'a');
  606 + std::string eight(8, 'a');
  607 + std::string nine(9, 'a');
  608 + std::string ten(10, 'a');
  609 + EXPECT_TRUE(V1(five).empty());
  610 + EXPECT_FALSE(V1(four).empty());
  611 +
  612 + EXPECT_TRUE(V2(nine).empty());
  613 + EXPECT_FALSE(V2(eight).empty());
  614 +
  615 + EXPECT_TRUE(V3(two).empty());
  616 + EXPECT_FALSE(V3(four).empty());
  617 +
  618 + EXPECT_TRUE(V4(eight).empty());
  619 + EXPECT_FALSE(V4(ten).empty());
  620 +
  621 + auto V1a2 = V1 & V2;
  622 + EXPECT_EQ(V1a2.get_description(), "(SIZE >= 5) AND (SIZE >= 9)");
  623 + EXPECT_FALSE(V1a2(five).empty());
  624 + EXPECT_TRUE(V1a2(nine).empty());
  625 +
  626 + auto V1a4 = V1 & V4;
  627 + EXPECT_EQ(V1a4.get_description(), "(SIZE >= 5) AND (SIZE <= 9)");
  628 + EXPECT_TRUE(V1a4(five).empty());
  629 + EXPECT_TRUE(V1a4(eight).empty());
  630 + EXPECT_FALSE(V1a4(ten).empty());
  631 + EXPECT_FALSE(V1a4(four).empty());
  632 +
  633 + auto V1o3 = V1 | V3;
  634 + EXPECT_EQ(V1o3.get_description(), "(SIZE >= 5) OR (SIZE < 3)");
  635 + EXPECT_TRUE(V1o3(two).empty());
  636 + EXPECT_TRUE(V1o3(eight).empty());
  637 + EXPECT_TRUE(V1o3(ten).empty());
  638 + EXPECT_TRUE(V1o3(two).empty());
  639 + EXPECT_FALSE(V1o3(four).empty());
  640 +
  641 + auto m1 = V1o3 & V4;
  642 + EXPECT_EQ(m1.get_description(), "((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)");
  643 + EXPECT_TRUE(m1(two).empty());
  644 + EXPECT_TRUE(m1(eight).empty());
  645 + EXPECT_FALSE(m1(ten).empty());
  646 + EXPECT_TRUE(m1(two).empty());
  647 + EXPECT_TRUE(m1(five).empty());
  648 + EXPECT_FALSE(m1(four).empty());
  649 +
  650 + auto m2 = m1 & V2;
  651 + EXPECT_EQ(m2.get_description(), "(((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)");
  652 + EXPECT_FALSE(m2(two).empty());
  653 + EXPECT_FALSE(m2(eight).empty());
  654 + EXPECT_FALSE(m2(ten).empty());
  655 + EXPECT_FALSE(m2(two).empty());
  656 + EXPECT_TRUE(m2(nine).empty());
  657 + EXPECT_FALSE(m2(four).empty());
  658 +
  659 + auto m3 = m2 | V3;
  660 + EXPECT_EQ(m3.get_description(), "((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)) OR (SIZE < 3)");
  661 + EXPECT_TRUE(m3(two).empty());
  662 + EXPECT_FALSE(m3(eight).empty());
  663 + EXPECT_TRUE(m3(nine).empty());
  664 + EXPECT_FALSE(m3(four).empty());
  665 +
  666 + auto m4 = V3 | m2;
  667 + EXPECT_EQ(m4.get_description(), "(SIZE < 3) OR ((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9))");
  668 + EXPECT_TRUE(m4(two).empty());
  669 + EXPECT_FALSE(m4(eight).empty());
  670 + EXPECT_TRUE(m4(nine).empty());
  671 + EXPECT_FALSE(m4(four).empty());
  672 +}
  673 +
  674 +TEST(ValidatorTests, TestValidatorNegation) {
  675 +
  676 + std::function<std::string(std::string &)> op1 = [](std::string &val) {
  677 + return (val.size() >= 5) ? std::string{} : val;
  678 + };
  679 +
  680 + CLI::Validator V1(op1, "SIZE >= 5", "size");
  681 +
  682 + std::string four(4, 'a');
  683 + std::string five(5, 'a');
  684 +
  685 + EXPECT_TRUE(V1(five).empty());
  686 + EXPECT_FALSE(V1(four).empty());
  687 +
  688 + auto V2 = !V1;
  689 + EXPECT_FALSE(V2(five).empty());
  690 + EXPECT_TRUE(V2(four).empty());
  691 + EXPECT_EQ(V2.get_description(), "NOT SIZE >= 5");
  692 +
  693 + V2.active(false);
  694 + EXPECT_TRUE(V2(five).empty());
  695 + EXPECT_TRUE(V2(four).empty());
  696 + EXPECT_TRUE(V2.get_description().empty());
  697 +}
  698 +
  699 +TEST(ValidatorTests, ValidatorDefaults) {
  700 +
  701 + CLI::Validator V1{};
  702 +
  703 + std::string four(4, 'a');
  704 + std::string five(5, 'a');
  705 +
  706 + // make sure this doesn't generate a seg fault or something
  707 + EXPECT_TRUE(V1(five).empty());
  708 + EXPECT_TRUE(V1(four).empty());
  709 +
  710 + EXPECT_TRUE(V1.get_name().empty());
  711 + EXPECT_TRUE(V1.get_description().empty());
  712 + EXPECT_TRUE(V1.get_active());
  713 + EXPECT_TRUE(V1.get_modifying());
  714 +
  715 + CLI::Validator V2{"check"};
  716 + // make sure this doesn't generate a seg fault or something
  717 + EXPECT_TRUE(V2(five).empty());
  718 + EXPECT_TRUE(V2(four).empty());
  719 +
  720 + EXPECT_TRUE(V2.get_name().empty());
  721 + EXPECT_EQ(V2.get_description(), "check");
  722 + EXPECT_TRUE(V2.get_active());
  723 + EXPECT_TRUE(V2.get_modifying());
  724 +}
tests/HelpTest.cpp
@@ -251,11 +251,10 @@ TEST(THelp, ManualSetterOverFunction) { @@ -251,11 +251,10 @@ TEST(THelp, ManualSetterOverFunction) {
251 EXPECT_EQ(x, 1); 251 EXPECT_EQ(x, 1);
252 252
253 std::string help = app.help(); 253 std::string help = app.help();
254 -  
255 EXPECT_THAT(help, HasSubstr("=12")); 254 EXPECT_THAT(help, HasSubstr("=12"));
256 EXPECT_THAT(help, HasSubstr("BIGGLES")); 255 EXPECT_THAT(help, HasSubstr("BIGGLES"));
257 EXPECT_THAT(help, HasSubstr("QUIGGLES")); 256 EXPECT_THAT(help, HasSubstr("QUIGGLES"));
258 - EXPECT_THAT(help, Not(HasSubstr("1,2"))); 257 + EXPECT_THAT(help, HasSubstr("{1,2}"));
259 } 258 }
260 259
261 TEST(THelp, Subcom) { 260 TEST(THelp, Subcom) {
@@ -743,10 +742,9 @@ TEST(THelp, ValidatorsText) { @@ -743,10 +742,9 @@ TEST(THelp, ValidatorsText) {
743 app.add_option("--f4", y)->check(CLI::Range(12)); 742 app.add_option("--f4", y)->check(CLI::Range(12));
744 743
745 std::string help = app.help(); 744 std::string help = app.help();
746 - EXPECT_THAT(help, HasSubstr("FILE")); 745 + EXPECT_THAT(help, HasSubstr("TEXT:FILE"));
747 EXPECT_THAT(help, HasSubstr("INT in [1 - 4]")); 746 EXPECT_THAT(help, HasSubstr("INT in [1 - 4]"));
748 - EXPECT_THAT(help, HasSubstr("INT in [0 - 12]")); // Loses UINT  
749 - EXPECT_THAT(help, Not(HasSubstr("TEXT"))); 747 + EXPECT_THAT(help, HasSubstr("UINT:INT in [0 - 12]")); // Loses UINT
750 } 748 }
751 749
752 TEST(THelp, ValidatorsNonPathText) { 750 TEST(THelp, ValidatorsNonPathText) {
@@ -756,8 +754,7 @@ TEST(THelp, ValidatorsNonPathText) { @@ -756,8 +754,7 @@ TEST(THelp, ValidatorsNonPathText) {
756 app.add_option("--f2", filename)->check(CLI::NonexistentPath); 754 app.add_option("--f2", filename)->check(CLI::NonexistentPath);
757 755
758 std::string help = app.help(); 756 std::string help = app.help();
759 - EXPECT_THAT(help, HasSubstr("PATH"));  
760 - EXPECT_THAT(help, Not(HasSubstr("TEXT"))); 757 + EXPECT_THAT(help, HasSubstr("TEXT:PATH"));
761 } 758 }
762 759
763 TEST(THelp, ValidatorsDirText) { 760 TEST(THelp, ValidatorsDirText) {
@@ -767,8 +764,7 @@ TEST(THelp, ValidatorsDirText) { @@ -767,8 +764,7 @@ TEST(THelp, ValidatorsDirText) {
767 app.add_option("--f2", filename)->check(CLI::ExistingDirectory); 764 app.add_option("--f2", filename)->check(CLI::ExistingDirectory);
768 765
769 std::string help = app.help(); 766 std::string help = app.help();
770 - EXPECT_THAT(help, HasSubstr("DIR"));  
771 - EXPECT_THAT(help, Not(HasSubstr("TEXT"))); 767 + EXPECT_THAT(help, HasSubstr("TEXT:DIR"));
772 } 768 }
773 769
774 TEST(THelp, ValidatorsPathText) { 770 TEST(THelp, ValidatorsPathText) {
@@ -778,8 +774,7 @@ TEST(THelp, ValidatorsPathText) { @@ -778,8 +774,7 @@ TEST(THelp, ValidatorsPathText) {
778 app.add_option("--f2", filename)->check(CLI::ExistingPath); 774 app.add_option("--f2", filename)->check(CLI::ExistingPath);
779 775
780 std::string help = app.help(); 776 std::string help = app.help();
781 - EXPECT_THAT(help, HasSubstr("PATH"));  
782 - EXPECT_THAT(help, Not(HasSubstr("TEXT"))); 777 + EXPECT_THAT(help, HasSubstr("TEXT:PATH"));
783 } 778 }
784 779
785 TEST(THelp, CombinedValidatorsText) { 780 TEST(THelp, CombinedValidatorsText) {
@@ -792,9 +787,8 @@ TEST(THelp, CombinedValidatorsText) { @@ -792,9 +787,8 @@ TEST(THelp, CombinedValidatorsText) {
792 // Can't programatically tell! 787 // Can't programatically tell!
793 // (Users can use ExistingPath, by the way) 788 // (Users can use ExistingPath, by the way)
794 std::string help = app.help(); 789 std::string help = app.help();
795 - EXPECT_THAT(help, HasSubstr("TEXT")); 790 + EXPECT_THAT(help, HasSubstr("TEXT:(FILE) OR (DIR)"));
796 EXPECT_THAT(help, Not(HasSubstr("PATH"))); 791 EXPECT_THAT(help, Not(HasSubstr("PATH")));
797 - EXPECT_THAT(help, Not(HasSubstr("FILE")));  
798 } 792 }
799 793
800 // Don't do this in real life, please 794 // Don't do this in real life, please
@@ -806,7 +800,7 @@ TEST(THelp, CombinedValidatorsPathyText) { @@ -806,7 +800,7 @@ TEST(THelp, CombinedValidatorsPathyText) {
806 800
807 // Combining validators with the same type string is OK 801 // Combining validators with the same type string is OK
808 std::string help = app.help(); 802 std::string help = app.help();
809 - EXPECT_THAT(help, Not(HasSubstr("TEXT"))); 803 + EXPECT_THAT(help, HasSubstr("TEXT:"));
810 EXPECT_THAT(help, HasSubstr("PATH")); 804 EXPECT_THAT(help, HasSubstr("PATH"));
811 } 805 }
812 806
@@ -819,8 +813,7 @@ TEST(THelp, CombinedValidatorsPathyTextAsTransform) { @@ -819,8 +813,7 @@ TEST(THelp, CombinedValidatorsPathyTextAsTransform) {
819 813
820 // Combining validators with the same type string is OK 814 // Combining validators with the same type string is OK
821 std::string help = app.help(); 815 std::string help = app.help();
822 - EXPECT_THAT(help, Not(HasSubstr("TEXT")));  
823 - EXPECT_THAT(help, HasSubstr("PATH")); 816 + EXPECT_THAT(help, HasSubstr("TEXT:(PATH(existing)) OR (PATH"));
824 } 817 }
825 818
826 // #113 Part 2 819 // #113 Part 2
tests/SetTest.cpp
@@ -4,20 +4,31 @@ @@ -4,20 +4,31 @@
4 static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers"); 4 static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
5 static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers"); 5 static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
6 static_assert(CLI::is_shared_ptr<int>::value == false, "is_shared_ptr should work on non-pointers"); 6 static_assert(CLI::is_shared_ptr<int>::value == false, "is_shared_ptr should work on non-pointers");
  7 +static_assert(CLI::is_shared_ptr<const std::shared_ptr<int>>::value == true,
  8 + "is_shared_ptr should work on const shared pointers");
  9 +static_assert(CLI::is_shared_ptr<const int *>::value == false, "is_shared_ptr should work on const pointers");
  10 +static_assert(CLI::is_shared_ptr<const int &>::value == false, "is_shared_ptr should work on const references");
  11 +static_assert(CLI::is_shared_ptr<int &>::value == false, "is_shared_ptr should work on non-const references");
7 12
8 static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true, 13 static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true,
9 "is_copyable_ptr should work on shared pointers"); 14 "is_copyable_ptr should work on shared pointers");
10 static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers"); 15 static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers");
11 static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers"); 16 static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers");
  17 +static_assert(CLI::is_copyable_ptr<const std::shared_ptr<int>>::value == true,
  18 + "is_copyable_ptr should work on const shared pointers");
  19 +static_assert(CLI::is_copyable_ptr<const int *>::value == true, "is_copyable_ptr should work on const pointers");
  20 +static_assert(CLI::is_copyable_ptr<const int &>::value == false, "is_copyable_ptr should work on const references");
  21 +static_assert(CLI::is_copyable_ptr<int &>::value == false, "is_copyable_ptr should work on non-const references");
12 22
13 static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs"); 23 static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs");
  24 +static_assert(CLI::detail::pair_adaptor<std::vector<std::string>>::value == false, "Should not have pairs");
14 static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs"); 25 static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs");
15 static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs"); 26 static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs");
16 27
17 TEST_F(TApp, SimpleMaps) { 28 TEST_F(TApp, SimpleMaps) {
18 int value; 29 int value;
19 std::map<std::string, int> map = {{"one", 1}, {"two", 2}}; 30 std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
20 - auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); 31 + auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
21 args = {"-s", "one"}; 32 args = {"-s", "one"};
22 run(); 33 run();
23 EXPECT_EQ(1u, app.count("-s")); 34 EXPECT_EQ(1u, app.count("-s"));
@@ -29,7 +40,7 @@ TEST_F(TApp, SimpleMaps) { @@ -29,7 +40,7 @@ TEST_F(TApp, SimpleMaps) {
29 TEST_F(TApp, StringStringMap) { 40 TEST_F(TApp, StringStringMap) {
30 std::string value; 41 std::string value;
31 std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}}; 42 std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
32 - app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); 43 + app.add_option("-s,--set", value)->transform(CLI::CheckedTransformer(map));
33 args = {"-s", "a"}; 44 args = {"-s", "a"};
34 run(); 45 run();
35 EXPECT_EQ(value, "b"); 46 EXPECT_EQ(value, "b");
@@ -39,7 +50,7 @@ TEST_F(TApp, StringStringMap) { @@ -39,7 +50,7 @@ TEST_F(TApp, StringStringMap) {
39 EXPECT_EQ(value, "c"); 50 EXPECT_EQ(value, "c");
40 51
41 args = {"-s", "c"}; 52 args = {"-s", "c"};
42 - EXPECT_THROW(run(), CLI::ValidationError); 53 + EXPECT_EQ(value, "c");
43 } 54 }
44 55
45 TEST_F(TApp, StringStringMapNoModify) { 56 TEST_F(TApp, StringStringMapNoModify) {
@@ -63,7 +74,7 @@ enum SimpleEnum { SE_one = 1, SE_two = 2 }; @@ -63,7 +74,7 @@ enum SimpleEnum { SE_one = 1, SE_two = 2 };
63 TEST_F(TApp, EnumMap) { 74 TEST_F(TApp, EnumMap) {
64 SimpleEnum value; 75 SimpleEnum value;
65 std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}}; 76 std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}};
66 - auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); 77 + auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
67 args = {"-s", "one"}; 78 args = {"-s", "one"};
68 run(); 79 run();
69 EXPECT_EQ(1u, app.count("-s")); 80 EXPECT_EQ(1u, app.count("-s"));
@@ -77,7 +88,7 @@ enum class SimpleEnumC { one = 1, two = 2 }; @@ -77,7 +88,7 @@ enum class SimpleEnumC { one = 1, two = 2 };
77 TEST_F(TApp, EnumCMap) { 88 TEST_F(TApp, EnumCMap) {
78 SimpleEnumC value; 89 SimpleEnumC value;
79 std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}}; 90 std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}};
80 - auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); 91 + auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
81 args = {"-s", "one"}; 92 args = {"-s", "one"};
82 run(); 93 run();
83 EXPECT_EQ(1u, app.count("-s")); 94 EXPECT_EQ(1u, app.count("-s"));
@@ -86,6 +97,155 @@ TEST_F(TApp, EnumCMap) { @@ -86,6 +97,155 @@ TEST_F(TApp, EnumCMap) {
86 EXPECT_EQ(value, SimpleEnumC::one); 97 EXPECT_EQ(value, SimpleEnumC::one);
87 } 98 }
88 99
  100 +TEST_F(TApp, structMap) {
  101 + struct tstruct {
  102 + int val2;
  103 + double val3;
  104 + std::string v4;
  105 + };
  106 + std::string struct_name;
  107 + std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
  108 + auto opt = app.add_option("-s,--set", struct_name)->check(CLI::IsMember(map));
  109 + args = {"-s", "sone"};
  110 + run();
  111 + EXPECT_EQ(1u, app.count("-s"));
  112 + EXPECT_EQ(1u, app.count("--set"));
  113 + EXPECT_EQ(1u, opt->count());
  114 + EXPECT_EQ(struct_name, "sone");
  115 +
  116 + args = {"-s", "sthree"};
  117 + EXPECT_THROW(run(), CLI::ValidationError);
  118 +}
  119 +
  120 +TEST_F(TApp, structMapChange) {
  121 + struct tstruct {
  122 + int val2;
  123 + double val3;
  124 + std::string v4;
  125 + };
  126 + std::string struct_name;
  127 + std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
  128 + auto opt = app.add_option("-s,--set", struct_name)
  129 + ->transform(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
  130 + args = {"-s", "s one"};
  131 + run();
  132 + EXPECT_EQ(1u, app.count("-s"));
  133 + EXPECT_EQ(1u, app.count("--set"));
  134 + EXPECT_EQ(1u, opt->count());
  135 + EXPECT_EQ(struct_name, "sone");
  136 +
  137 + args = {"-s", "sthree"};
  138 + EXPECT_THROW(run(), CLI::ValidationError);
  139 +
  140 + args = {"-s", "S_t_w_o"};
  141 + run();
  142 + EXPECT_EQ(struct_name, "stwo");
  143 + args = {"-s", "S two"};
  144 + run();
  145 + EXPECT_EQ(struct_name, "stwo");
  146 +}
  147 +
  148 +TEST_F(TApp, structMapNoChange) {
  149 + struct tstruct {
  150 + int val2;
  151 + double val3;
  152 + std::string v4;
  153 + };
  154 + std::string struct_name;
  155 + std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
  156 + auto opt = app.add_option("-s,--set", struct_name)
  157 + ->check(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
  158 + args = {"-s", "SONE"};
  159 + run();
  160 + EXPECT_EQ(1u, app.count("-s"));
  161 + EXPECT_EQ(1u, app.count("--set"));
  162 + EXPECT_EQ(1u, opt->count());
  163 + EXPECT_EQ(struct_name, "SONE");
  164 +
  165 + args = {"-s", "sthree"};
  166 + EXPECT_THROW(run(), CLI::ValidationError);
  167 +
  168 + args = {"-s", "S_t_w_o"};
  169 + run();
  170 + EXPECT_EQ(struct_name, "S_t_w_o");
  171 +
  172 + args = {"-s", "S two"};
  173 + run();
  174 + EXPECT_EQ(struct_name, "S two");
  175 +}
  176 +
  177 +TEST_F(TApp, NonCopyableMap) {
  178 +
  179 + std::string map_name;
  180 + std::map<std::string, std::unique_ptr<double>> map;
  181 + map["e1"] = std::unique_ptr<double>(new double(5.7));
  182 + map["e3"] = std::unique_ptr<double>(new double(23.8));
  183 + auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
  184 + args = {"-s", "e1"};
  185 + run();
  186 + EXPECT_EQ(1u, app.count("-s"));
  187 + EXPECT_EQ(1u, app.count("--set"));
  188 + EXPECT_EQ(1u, opt->count());
  189 + EXPECT_EQ(map_name, "e1");
  190 +
  191 + args = {"-s", "e45"};
  192 + EXPECT_THROW(run(), CLI::ValidationError);
  193 +}
  194 +
  195 +TEST_F(TApp, NonCopyableMapWithFunction) {
  196 +
  197 + std::string map_name;
  198 + std::map<std::string, std::unique_ptr<double>> map;
  199 + map["e1"] = std::unique_ptr<double>(new double(5.7));
  200 + map["e3"] = std::unique_ptr<double>(new double(23.8));
  201 + auto opt = app.add_option("-s,--set", map_name)->transform(CLI::IsMember(&map, CLI::ignore_underscore));
  202 + args = {"-s", "e_1"};
  203 + run();
  204 + EXPECT_EQ(1u, app.count("-s"));
  205 + EXPECT_EQ(1u, app.count("--set"));
  206 + EXPECT_EQ(1u, opt->count());
  207 + EXPECT_EQ(map_name, "e1");
  208 +
  209 + args = {"-s", "e45"};
  210 + EXPECT_THROW(run(), CLI::ValidationError);
  211 +}
  212 +
  213 +TEST_F(TApp, NonCopyableMapNonStringMap) {
  214 +
  215 + std::string map_name;
  216 + std::map<int, std::unique_ptr<double>> map;
  217 + map[4] = std::unique_ptr<double>(new double(5.7));
  218 + map[17] = std::unique_ptr<double>(new double(23.8));
  219 + auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
  220 + args = {"-s", "4"};
  221 + run();
  222 + EXPECT_EQ(1u, app.count("-s"));
  223 + EXPECT_EQ(1u, app.count("--set"));
  224 + EXPECT_EQ(1u, opt->count());
  225 + EXPECT_EQ(map_name, "4");
  226 +
  227 + args = {"-s", "e45"};
  228 + EXPECT_THROW(run(), CLI::ValidationError);
  229 +}
  230 +
  231 +TEST_F(TApp, CopyableMapMove) {
  232 +
  233 + std::string map_name;
  234 + std::map<int, double> map;
  235 + map[4] = 5.7;
  236 + map[17] = 23.8;
  237 + auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(std::move(map)));
  238 + args = {"-s", "4"};
  239 + run();
  240 + EXPECT_EQ(1u, app.count("-s"));
  241 + EXPECT_EQ(1u, app.count("--set"));
  242 + EXPECT_EQ(1u, opt->count());
  243 + EXPECT_EQ(map_name, "4");
  244 +
  245 + args = {"-s", "e45"};
  246 + EXPECT_THROW(run(), CLI::ValidationError);
  247 +}
  248 +
89 TEST_F(TApp, SimpleSets) { 249 TEST_F(TApp, SimpleSets) {
90 std::string value; 250 std::string value;
91 auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})}); 251 auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
tests/TransformTest.cpp 0 โ†’ 100644
  1 +#include "app_helper.hpp"
  2 +
  3 +#include <unordered_map>
  4 +
  5 +TEST_F(TApp, SimpleTransform) {
  6 + int value;
  7 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
  8 + args = {"-s", "one"};
  9 + run();
  10 + EXPECT_EQ(1u, app.count("-s"));
  11 + EXPECT_EQ(1u, opt->count());
  12 + EXPECT_EQ(value, 1);
  13 +}
  14 +
  15 +TEST_F(TApp, SimpleTransformInitList) {
  16 + int value;
  17 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}));
  18 + args = {"-s", "one"};
  19 + run();
  20 + EXPECT_EQ(1u, app.count("-s"));
  21 + EXPECT_EQ(1u, opt->count());
  22 + EXPECT_EQ(value, 1);
  23 +}
  24 +
  25 +TEST_F(TApp, SimpleNumericalTransform) {
  26 + int value;
  27 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer(CLI::TransformPairs<int>{{"one", 1}}));
  28 + args = {"-s", "one"};
  29 + run();
  30 + EXPECT_EQ(1u, app.count("-s"));
  31 + EXPECT_EQ(1u, opt->count());
  32 + EXPECT_EQ(value, 1);
  33 +}
  34 +
  35 +TEST_F(TApp, EnumTransform) {
  36 + enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
  37 + test value;
  38 + auto opt = app.add_option("-s", value)
  39 + ->transform(CLI::Transformer(
  40 + CLI::TransformPairs<test>{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}}));
  41 + args = {"-s", "val1"};
  42 + run();
  43 + EXPECT_EQ(1u, app.count("-s"));
  44 + EXPECT_EQ(1u, opt->count());
  45 + EXPECT_EQ(value, test::val1);
  46 +
  47 + args = {"-s", "val2"};
  48 + run();
  49 + EXPECT_EQ(value, test::val2);
  50 +
  51 + args = {"-s", "val3"};
  52 + run();
  53 + EXPECT_EQ(value, test::val3);
  54 +
  55 + args = {"-s", "val4"};
  56 + EXPECT_THROW(run(), CLI::ConversionError);
  57 +
  58 + // transformer doesn't do any checking so this still works
  59 + args = {"-s", "5"};
  60 + run();
  61 + EXPECT_EQ(static_cast<int16_t>(value), int16_t(5));
  62 +}
  63 +
  64 +TEST_F(TApp, EnumCheckedTransform) {
  65 + enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
  66 + test value;
  67 + auto opt = app.add_option("-s", value)
  68 + ->transform(CLI::CheckedTransformer(
  69 + CLI::TransformPairs<test>{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}}));
  70 + args = {"-s", "val1"};
  71 + run();
  72 + EXPECT_EQ(1u, app.count("-s"));
  73 + EXPECT_EQ(1u, opt->count());
  74 + EXPECT_EQ(value, test::val1);
  75 +
  76 + args = {"-s", "val2"};
  77 + run();
  78 + EXPECT_EQ(value, test::val2);
  79 +
  80 + args = {"-s", "val3"};
  81 + run();
  82 + EXPECT_EQ(value, test::val3);
  83 +
  84 + args = {"-s", "17"};
  85 + run();
  86 + EXPECT_EQ(value, test::val3);
  87 +
  88 + args = {"-s", "val4"};
  89 + EXPECT_THROW(run(), CLI::ValidationError);
  90 +
  91 + args = {"-s", "5"};
  92 + EXPECT_THROW(run(), CLI::ValidationError);
  93 +}
  94 +
  95 +TEST_F(TApp, SimpleTransformFn) {
  96 + int value;
  97 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
  98 + args = {"-s", "ONE"};
  99 + run();
  100 + EXPECT_EQ(1u, app.count("-s"));
  101 + EXPECT_EQ(1u, opt->count());
  102 + EXPECT_EQ(value, 1);
  103 +}
  104 +
  105 +TEST_F(TApp, SimpleNumericalTransformFn) {
  106 + int value;
  107 + auto opt =
  108 + app.add_option("-s", value)
  109 + ->transform(CLI::Transformer(std::vector<std::pair<std::string, int>>{{"one", 1}}, CLI::ignore_case));
  110 + args = {"-s", "ONe"};
  111 + run();
  112 + EXPECT_EQ(1u, app.count("-s"));
  113 + EXPECT_EQ(1u, opt->count());
  114 + EXPECT_EQ(value, 1);
  115 +}
  116 +
  117 +TEST_F(TApp, EnumTransformFn) {
  118 + enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
  119 + test value;
  120 + auto opt = app.add_option("-s", value)
  121 + ->transform(CLI::Transformer(
  122 + CLI::TransformPairs<test>{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}},
  123 + CLI::ignore_case,
  124 + CLI::ignore_underscore));
  125 + args = {"-s", "val_1"};
  126 + run();
  127 + EXPECT_EQ(1u, app.count("-s"));
  128 + EXPECT_EQ(1u, opt->count());
  129 + EXPECT_EQ(value, test::val1);
  130 +
  131 + args = {"-s", "VAL_2"};
  132 + run();
  133 + EXPECT_EQ(value, test::val2);
  134 +
  135 + args = {"-s", "VAL3"};
  136 + run();
  137 + EXPECT_EQ(value, test::val3);
  138 +
  139 + args = {"-s", "val_4"};
  140 + EXPECT_THROW(run(), CLI::ConversionError);
  141 +}
  142 +
  143 +TEST_F(TApp, EnumTransformFnMap) {
  144 + enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17 };
  145 + std::map<std::string, test> map{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}};
  146 + test value;
  147 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
  148 + args = {"-s", "val_1"};
  149 + run();
  150 + EXPECT_EQ(1u, app.count("-s"));
  151 + EXPECT_EQ(1u, opt->count());
  152 + EXPECT_EQ(value, test::val1);
  153 +
  154 + args = {"-s", "VAL_2"};
  155 + run();
  156 + EXPECT_EQ(value, test::val2);
  157 +
  158 + args = {"-s", "VAL3"};
  159 + run();
  160 + EXPECT_EQ(value, test::val3);
  161 +
  162 + args = {"-s", "val_4"};
  163 + EXPECT_THROW(run(), CLI::ConversionError);
  164 +}
  165 +
  166 +TEST_F(TApp, EnumTransformFnPtrMap) {
  167 + enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
  168 + std::map<std::string, test> map{{"val1", test::val1}, {"val2", test::val2}, {"val3", test::val3}};
  169 + test value;
  170 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer(&map, CLI::ignore_case, CLI::ignore_underscore));
  171 + args = {"-s", "val_1"};
  172 + run();
  173 + EXPECT_EQ(1u, app.count("-s"));
  174 + EXPECT_EQ(1u, opt->count());
  175 + EXPECT_EQ(value, test::val1);
  176 +
  177 + args = {"-s", "VAL_2"};
  178 + run();
  179 + EXPECT_EQ(value, test::val2);
  180 +
  181 + args = {"-s", "VAL3"};
  182 + run();
  183 + EXPECT_EQ(value, test::val3);
  184 +
  185 + args = {"-s", "val_4"};
  186 + EXPECT_THROW(run(), CLI::ConversionError);
  187 +
  188 + map["val4"] = test::val4;
  189 + run();
  190 + EXPECT_EQ(value, test::val4);
  191 +}
  192 +
  193 +TEST_F(TApp, EnumTransformFnSharedPtrMap) {
  194 + enum class test : int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
  195 + auto map = std::make_shared<std::unordered_map<std::string, test>>();
  196 + auto &mp = *map;
  197 + mp["val1"] = test::val1;
  198 + mp["val2"] = test::val2;
  199 + mp["val3"] = test::val3;
  200 +
  201 + test value;
  202 + auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
  203 + args = {"-s", "val_1"};
  204 + run();
  205 + EXPECT_EQ(1u, app.count("-s"));
  206 + EXPECT_EQ(1u, opt->count());
  207 + EXPECT_EQ(value, test::val1);
  208 +
  209 + args = {"-s", "VAL_2"};
  210 + run();
  211 + EXPECT_EQ(value, test::val2);
  212 +
  213 + args = {"-s", "VAL3"};
  214 + run();
  215 + EXPECT_EQ(value, test::val3);
  216 +
  217 + args = {"-s", "val_4"};
  218 + EXPECT_THROW(run(), CLI::ConversionError);
  219 +
  220 + mp["val4"] = test::val4;
  221 + run();
  222 + EXPECT_EQ(value, test::val4);
  223 +}
  224 +
  225 +// Test a cascade of transform functions
  226 +TEST_F(TApp, TransformCascade) {
  227 +
  228 + std::string output;
  229 + auto opt = app.add_option("-s", output);
  230 + opt->transform(CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case));
  231 + opt->transform(
  232 + CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore));
  233 + opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case));
  234 + opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}));
  235 + args = {"-s", "abcd"};
  236 + run();
  237 + EXPECT_EQ(output, "abcd");
  238 +
  239 + args = {"-s", "Bbc"};
  240 + run();
  241 + EXPECT_EQ(output, "bbcd");
  242 +
  243 + args = {"-s", "C_B"};
  244 + run();
  245 + EXPECT_EQ(output, "cbcd");
  246 +
  247 + args = {"-s", "A"};
  248 + run();
  249 + EXPECT_EQ(output, "abcd");
  250 +}
  251 +
  252 +// Test a cascade of transform functions
  253 +TEST_F(TApp, TransformCascadeDeactivate) {
  254 +
  255 + std::string output;
  256 + auto opt = app.add_option("-s", output);
  257 + opt->transform(
  258 + CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case).name("tform1"));
  259 + opt->transform(
  260 + CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore)
  261 + .name("tform2")
  262 + .active(false));
  263 + opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case).name("tform3"));
  264 + opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}).name("check"));
  265 + args = {"-s", "abcd"};
  266 + run();
  267 + EXPECT_EQ(output, "abcd");
  268 +
  269 + args = {"-s", "Bbc"};
  270 + run();
  271 + EXPECT_EQ(output, "bbcd");
  272 +
  273 + args = {"-s", "C_B"};
  274 + EXPECT_THROW(run(), CLI::ValidationError);
  275 +
  276 + auto validator = opt->get_validator("tform2");
  277 + EXPECT_FALSE(validator->get_active());
  278 + EXPECT_EQ(validator->get_name(), "tform2");
  279 + validator->active();
  280 + EXPECT_TRUE(validator->get_active());
  281 + args = {"-s", "C_B"};
  282 + run();
  283 + EXPECT_EQ(output, "cbcd");
  284 +
  285 + opt->get_validator("check")->active(false);
  286 + args = {"-s", "gsdgsgs"};
  287 + run();
  288 + EXPECT_EQ(output, "gsdgsgs");
  289 +
  290 + EXPECT_THROW(opt->get_validator("sdfsdf"), CLI::OptionNotFound);
  291 +}
  292 +
  293 +TEST_F(TApp, IntTransformFn) {
  294 + std::string value;
  295 + app.add_option("-s", value)
  296 + ->transform(
  297 + CLI::CheckedTransformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}, [](int in) { return in - 10; }));
  298 + args = {"-s", "25"};
  299 + run();
  300 + EXPECT_EQ(value, "5");
  301 +
  302 + args = {"-s", "6"};
  303 + run();
  304 + EXPECT_EQ(value, "6");
  305 +
  306 + args = {"-s", "45"};
  307 + EXPECT_THROW(run(), CLI::ValidationError);
  308 +
  309 + args = {"-s", "val_4"};
  310 + EXPECT_THROW(run(), CLI::ValidationError);
  311 +}
  312 +
  313 +TEST_F(TApp, IntTransformNonConvertible) {
  314 + std::string value;
  315 + app.add_option("-s", value)->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}));
  316 + args = {"-s", "15"};
  317 + run();
  318 + EXPECT_EQ(value, "5");
  319 +
  320 + args = {"-s", "18"};
  321 + run();
  322 + EXPECT_EQ(value, "6");
  323 +
  324 + // value can't be converted to int so it is just ignored
  325 + args = {"-s", "abcd"};
  326 + run();
  327 + EXPECT_EQ(value, "abcd");
  328 +}
  329 +
  330 +TEST_F(TApp, IntTransformNonMerge) {
  331 + std::string value;
  332 + app.add_option("-s", value)
  333 + ->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) &
  334 + CLI::Transformer(std::map<int, int>{{25, 5}, {28, 6}, {31, 7}}),
  335 + "merge");
  336 + args = {"-s", "15"};
  337 + run();
  338 + EXPECT_EQ(value, "5");
  339 +
  340 + args = {"-s", "18"};
  341 + run();
  342 + EXPECT_EQ(value, "6");
  343 +
  344 + // value can't be converted to int so it is just ignored
  345 + args = {"-s", "abcd"};
  346 + run();
  347 + EXPECT_EQ(value, "abcd");
  348 +
  349 + args = {"-s", "25"};
  350 + run();
  351 + EXPECT_EQ(value, "5");
  352 +
  353 + args = {"-s", "31"};
  354 + run();
  355 + EXPECT_EQ(value, "7");
  356 +
  357 + auto help = app.help();
  358 + EXPECT_TRUE(help.find("15->5") != std::string::npos);
  359 + EXPECT_TRUE(help.find("25->5") != std::string::npos);
  360 +
  361 + auto validator = app.get_option("-s")->get_validator();
  362 + help = validator->get_description();
  363 + EXPECT_TRUE(help.find("15->5") != std::string::npos);
  364 + EXPECT_TRUE(help.find("25->5") != std::string::npos);
  365 +
  366 + auto validator2 = app.get_option("-s")->get_validator("merge");
  367 + EXPECT_EQ(validator2, validator);
  368 +}
  369 +
  370 +TEST_F(TApp, IntTransformMergeWithCustomValidator) {
  371 + std::string value;
  372 + auto opt = app.add_option("-s", value)
  373 + ->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) |
  374 + CLI::Validator(
  375 + [](std::string &element) {
  376 + if(element == "frog") {
  377 + element = "hops";
  378 + }
  379 + return std::string{};
  380 + },
  381 + std::string{}),
  382 + "check");
  383 + args = {"-s", "15"};
  384 + run();
  385 + EXPECT_EQ(value, "5");
  386 +
  387 + args = {"-s", "18"};
  388 + run();
  389 + EXPECT_EQ(value, "6");
  390 +
  391 + // value can't be converted to int so it is just ignored
  392 + args = {"-s", "frog"};
  393 + run();
  394 + EXPECT_EQ(value, "hops");
  395 +
  396 + args = {"-s", "25"};
  397 + run();
  398 + EXPECT_EQ(value, "25");
  399 +
  400 + auto help = app.help();
  401 + EXPECT_TRUE(help.find("15->5") != std::string::npos);
  402 + EXPECT_TRUE(help.find("OR") == std::string::npos);
  403 +
  404 + auto validator = opt->get_validator("check");
  405 + EXPECT_EQ(validator->get_name(), "check");
  406 + validator->active(false);
  407 + help = app.help();
  408 + EXPECT_TRUE(help.find("15->5") == std::string::npos);
  409 +}
  410 +
  411 +TEST_F(TApp, BoundTests) {
  412 + double value;
  413 + app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
  414 + args = {"-s", "15"};
  415 + run();
  416 + EXPECT_EQ(value, 5.9);
  417 +
  418 + args = {"-s", "3.689"};
  419 + run();
  420 + EXPECT_EQ(value, std::stod("3.689"));
  421 +
  422 + // value can't be converted to int so it is just ignored
  423 + args = {"-s", "abcd"};
  424 + EXPECT_THROW(run(), CLI::ValidationError);
  425 +
  426 + args = {"-s", "2.5"};
  427 + run();
  428 + EXPECT_EQ(value, 3.4);
  429 +
  430 + auto help = app.help();
  431 + EXPECT_TRUE(help.find("bounded to") != std::string::npos);
  432 + EXPECT_TRUE(help.find("[3.4 - 5.9]") != std::string::npos);
  433 +}