Container options (#423)

* Update options.md book chapter and the readme to better reflect current usage and the modifications to the add_options templates.

add support in add_option for wrapper types, such as std::optional, boost::optional or other types with a value_type trait.  Add support for generalized containers beyond vector,  add support for nested tuples and vectors, and complex numbers directly in add_option.  This includes several new type traits and object categories.

Upgrade the google test version to better support templated tests.

add support for vector argument separator `%%`

* update formatting to match recent changes

* Apply suggestions from code review

Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
This commit is contained in:
Philip Top 2020-03-22 11:06:34 -07:00 committed by GitHub
parent f346f29802
commit 27da2f952e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2385 additions and 1003 deletions

View File

@ -195,14 +195,15 @@ While all options internally are the same type, there are several ways to add an
app.add_option(option_name, help_str="")
app.add_option(option_name,
variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string or that takes an int 🆕, double 🆕, or string in a constructor. Also allowed are tuples 🆕, std::array 🆕 or std::pair 🆕.
variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string or that takes an int 🆕, double 🆕, or string in a constructor. Also allowed are tuples 🆕, std::array 🆕 or std::pair 🆕. Also supported are complex numbers🚧, wrapper types🚧, and containers besides vector🚧 of any other supported type.
help_string="")
app.add_option_function<type>(option_name,
function <void(const type &value)>, // type can be any type supported by add_option
help_string="")
app.add_complex(... // Special case: support for complex numbers
app.add_complex(... // Special case: support for complex numbers ⚠️. Complex numbers are now fully supported in the add_option so this function is redundant.
// 🆕 There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
app.add_option<typename T, typename XC>(option_name,
T &output, // output must be assignable or constructible from a value of type XC
@ -213,7 +214,7 @@ app.add_flag(option_name,
help_string="")
app.add_flag(option_name,
variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or any singular object with a defined conversion from a string like add_option
variable_to_bind_to, // bool, int, float, complex, containers, enum, or string-like, or any singular object with a defined conversion from a string like add_option
help_string="")
app.add_flag_function(option_name,
@ -245,9 +246,9 @@ app.add_option<vtype,std:string>("--vs",v1);
app.add_option<vtype,int>("--vi",v1);
app.add_option<vtype,double>("--vf",v1);
```
otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std\::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` are supported directly, other optional types can be added using the two parameter template. See [CLI11 Advanced Topics/Custom Converters][] for information on how this could be done and how you can add your own converters for additional types.
Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which `value_type` definition. See [CLI11 Advanced Topics/Custom Converters][] for information on how you can add your own converters for additional types.
Vector types can also be used in the two parameter template overload
```
@ -308,7 +309,7 @@ Before parsing, you can set the following options:
- `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
- `->description(str)`: Set/change the description.
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy).
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy).
- `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails
- `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
- `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
@ -319,7 +320,7 @@ Before parsing, you can set the following options:
- `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses.
- `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
- `default_str(string)`: Set the default string directly. This string will also be used as a default value if no arguments are passed and the value is requested.
- `default_val(value)`: 🆕 Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or a stream operator).
- `default_val(value)`: 🆕 Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator).
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.

View File

@ -9,7 +9,7 @@ int int_option{0};
app.add_option("-i", int_option, "Optional description");
```
This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option. If you do not add this, you do not even need your option value to be printable[^1].
This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option.
```cpp
int int_option{0};
@ -20,11 +20,15 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi
| Type | CLI11 |
|-------------|-------|
| int-like | Integer conversion up to 64-bit, can be unsigned |
| float-like | Floating point conversions |
| string-like | Anything else that can be shifted into a StringStream |
| vector-like | A vector of the above three types (see below) |
| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number |
| string-like | std\::string, or anything that can be constructed from or assigned a std\::string |
| complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" |
| enumeration | any enum or enum class type is supported through conversion from the underlying type(typically int, though it can be specified otherwise) |
| container-like | a container(like vector) of any available types including other containers |
| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of type in this list |
| tuple | a tuple, pair, or array, or other type with a tuple size and tuple_type operations defined and the members being a type contained in this list |
| function | A function that takes an array of strings and returns a string that describes the conversion failure or empty for success. May be the empty function. (`{}`) |
| streamable | any other type with a `<<` operator will also work |
By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers.
@ -46,15 +50,15 @@ To make a positional option, you simply give CLI11 one name that does not start
This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional.
## Vectors of options
## Containers of options
If you use a vector instead of a plain option, you can accept more than one value on the command line. By default, a vector accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restore with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options:[^2]
If you use a vector or other container instead of a plain option, you can accept more than one value on the command line. By default, a container accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restored with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options.
| Separate names | Combined names |
|-------------------|-----------------|
| `--vec 1 --vec 2` | `--vec 1 2` |
The original version did allow the option system to access information on the grouping of options received, but was removed for simplicity.
It is also possible to specify a minimum and maximum number through `->expected(Min,Max)`. It is also possible to specify a min and max type size for the elements of the container. It most cases these values will be automatically determined but a user can manually restrict them.
An example of setting up a vector option:
@ -65,6 +69,43 @@ app.add_option("--vec", int_vec, "My vector option");
Vectors will be replaced by the parsed content if the option is given on the command line.
A definition of a container for purposes of CLI11 is a type with a `end()`, `insert(...)`, `clear()` and `value_type` definitions. This includes `vector`, `set`, `deque`, `list`, `forward_iist`, `map`, `unordered_map` and a few others from the standard library, and many other containers from the boost library.
### containers of containers
Containers of containers are also supported.
```cpp
std::vector<std::vector<int>> int_vec;
app.add_option("--vec", int_vec, "My vector of vectors option");
```
CLI11 inserts a separator sequence at the start of each argument call to separate the vectors. So unless the separators are injected as part of the command line each call of the option on the command line will result in a separate element of the outer vector. This can be manually controlled via `inject_separator(true|false)` but in nearly all cases this should be left to the defaults. To insert of a separator from the command line add a `%%` where the separation should occur.
```
cmd --vec_of_vec 1 2 3 4 %% 1 2
```
would then result in a container of size 2 with the first element containing 4 values and the second 2.
This separator is also the only way to get values into something like
```cpp
std::pair<std::vector<int>,std::vector<int>> two_vecs;
app.add_option("--vec", two_vecs, "pair of vectors");
```
without calling the argument twice.
Further levels of nesting containers should compile but intermediate layers will only have a single element in the container, so is probably not that useful.
### Nested types
Types can be nested For example
```cpp
std::map<int, std::pair<int,std::string>> map;
app.add_option("--dict", map, "map of pairs");
```
will require 3 arguments for each invocation, and multiple sets of 3 arguments can be entered for a single invocation on the command line.
```cpp
std::map<int, std::pair<int,std::vector<std::string>>> map;
app.add_option("--dict", map, "map of pairs");
```
will result in a requirement for 2 integers on each invocation and absorb an unlimited number of strings including 0.
## Option modifiers
@ -75,25 +116,32 @@ When you call `add_option`, you get a pointer to the added option. You can use t
| `->required()` | The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. |
| `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. |
| `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. |
| `->type_size(N)` | specify that each block of values would consist of N elements |
| `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements |
| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. |
| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. |
| `->envname(name)` | Gets the value from the environment if present and not passed on the command line. |
| `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. |
| `->description(string)` | Set/change the description |
| `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). |
| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7). |
| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments). |
| `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next three lines for shortcuts to set this more easily. |
| `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` |
| `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' |
| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next four lines for shortcuts to set this more easily. |
| `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. |
| `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` |
| `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. |
| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. |
| `->check(CLI::ExistingFile)` | Requires that the file exists if given. |
| `->check(CLI::ExistingDirectory)` | Requires that the directory exists. |
| `->check(CLI::NonexistentPath)` | Requires that the path does not exist. |
| `->check(CLI::Range(min,max))` | Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. |
| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. this also sets the delimiter |
| `->check(Validator)` | perform a check on the returned results to verify they meet some criteria. See [Validators](./validators.md) for more info |
| `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info |
| `->each(void(std::string))` | Run a function on each parsed value, *in order*. |
| `->default_str(string)` | set a default string for use in the help and as a default value if no arguments are passed and a value is requested |
| `->default_function(string())` | Advanced: Change the function that `capture_default_str()` uses. |
| `->default_val(value)` | Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). |
The `->check(...)` modifiers adds a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
The `->check(...)` and `->transform(...)` modifiers can also take a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
## Using the `CLI::Option` pointer
@ -110,12 +158,17 @@ if(* opt)
## Inheritance of defaults
One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are four settings that can be set and inherited:
One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are a number of settings that can be set and inherited:
* `group`: The group name starts as "Options"
* `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag.
* `ignore_case`: Allow any mixture of cases for the option or flag name
* `ignore_underscore`: Allow any number of underscores in the option or flag name
* `configurable`: Specify whether an option can be configured through a config file
* `disable_flag_override`: do not allow flag values to be overridden on the command line
* `always_capture_default`: specify that the default values should be automatically captured.
* `delimiter`: A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3")
An example of usage:
@ -129,29 +182,7 @@ app.get_group() // is "Required"
Groups are mostly for visual organization, but an empty string for a group name will hide the option.
## Listing of specialty options:
Besides `add_option` and `add_flag`, there are several special ways to create options for sets and complex numbers.
### Sets
You can add a set with `add_set`, where you give a variable to set and a `std::set` of choices to pick from. There also is a `add_set_ignore_case` version which ignores case when set matching. If you use an existing set instead of an inline one, you can edit the set after adding it and changes will be reflected in the set checking and help message.
```cpp
int val{0};
app.add_set("--even", val, {0,2,4,6,8});
```
### Complex numbers
You can also add a complex number. This type just needs to support a `(T x, T y)` constructor and be printable. You can also pass one extra argument that will set the label of the type; by default it is "COMPLEX".
```cpp
std::complex<float> val{0.0F,0.0F};
app.add_complex("--cplx", val);
```
### Windows style options (New in CLI11 1.7)
### Windows style options
You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line:
@ -160,12 +191,13 @@ You can also set the app setting `app->allow_windows_style_options()` to allow w
* `/long` (long flag)
* `/file filename` (space)
* `/file:filename` (colon)
* `/long_flag:false` (long flag with : to override the default value)
Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manor as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined.
Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manner as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined.
## Parse configuration
How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse.
How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the type or function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse.
### examples
How options manage this is best illustrated through some examples
@ -173,7 +205,7 @@ How options manage this is best illustrated through some examples
std::string val;
app.add_option("--opt",val,"description");
```
creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size of 1. meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types.
creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size min and max of 1. Meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types.
now for example
```cpp
@ -203,6 +235,20 @@ app.add_flag("--opt",val,"description");
Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed.
```cpp
std::complex<double> val;
app.add_option("--opt",val,"description");
```
triggers the complex number type which has a min of 1 and max of 2, so 1 or 2 strings can be passed. Complex number conversion supports arguments of the form "1+2j" or "1","2", or "1" "2i". The imaginary number symbols `i` and `j` are interchangeable in this context.
```cpp
std::vector<std::vector<int>> val;
app.add_option("--opt",val,"description");
```
has a type size of 1 to (1<<30).
### Customization
The `type_size(N)`, `type_size(Nmin, Nmax)`, `expected(N)`, `expected(Nmin,Nmax)`, and `allow_extra_args()` can be used to customize an option. For example
@ -214,5 +260,9 @@ opt->expected(0,1);
```
will create a hybrid option, that can exist on its own in which case the value "vvv" is used or if a value is given that value will be used.
[^1]: For example, enums are not printable to `std::cout`.
[^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values.
There are some additional options that can be specified to modify an option for specific cases
- `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set.
## Unusual circumstances
There are a few cases where some things break down in the type system managing options and definitions. Using the `add_option` method defines a lambda function to extract a default value if required. In most cases this either straightforward or a failure is detected automatically and handled. But in a few cases a streaming template is available that several layers down may not actually be defined. The conditions in CLI11 cannot detect this circumstance automatically and will result in compile error. One specific known case is `boost::optional` if the boost optional_io header is included. This header defines a template for all boost optional values even if they do no actually have a streaming operator. For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template. For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases.

2
extern/googletest vendored

@ -1 +1 @@
Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2
Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e

View File

@ -610,21 +610,39 @@ class App {
// to structs used in the evaluation can be temporary so that would cause issues.
auto Tcount = detail::type_count<AssignTo>::value;
auto XCcount = detail::type_count<ConvertTo>::value;
opt->type_size((std::max)(Tcount, XCcount));
opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount));
opt->expected(detail::expected_count<ConvertTo>::value);
opt->run_callback_for_default();
return opt;
}
/// Add option for assigning to a variable
template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy>
Option *add_option_no_stream(std::string option_name,
AssignTo &variable, ///< The variable to set
std::string option_description = "") {
auto fun = [&variable](const CLI::results_t &res) { // comment for spacing
return detail::lexical_conversion<AssignTo, AssignTo>(res, variable);
};
Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; });
opt->type_name(detail::type_name<AssignTo>());
opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value);
opt->expected(detail::expected_count<AssignTo>::value);
opt->run_callback_for_default();
return opt;
}
/// Add option for a callback of a specific type
template <typename T>
template <typename ArgType>
Option *add_option_function(std::string option_name,
const std::function<void(const T &)> &func, ///< the callback to execute
const std::function<void(const ArgType &)> &func, ///< the callback to execute
std::string option_description = "") {
auto fun = [func](const CLI::results_t &res) {
T variable;
bool result = detail::lexical_conversion<T, T>(res, variable);
ArgType variable;
bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable);
if(result) {
func(variable);
}
@ -632,15 +650,15 @@ class App {
};
Option *opt = add_option(option_name, std::move(fun), option_description, false);
opt->type_name(detail::type_name<T>());
opt->type_size(detail::type_count<T>::value);
opt->expected(detail::expected_count<T>::value);
opt->type_name(detail::type_name<ArgType>());
opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value);
opt->expected(detail::expected_count<ArgType>::value);
return opt;
}
/// Add option with no description or variable assignment
Option *add_option(std::string option_name) {
return add_option(option_name, CLI::callback_t(), std::string{}, false);
return add_option(option_name, CLI::callback_t{}, std::string{}, false);
}
/// Add option with description but with no variable assignment or callback
@ -749,7 +767,7 @@ class App {
/// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
/// that can be converted from a string
template <typename T,
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value &&
enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
(!std::is_integral<T>::value || is_bool<T>::value) &&
!std::is_constructible<std::function<void(int)>, T>::value,
detail::enabler> = detail::dummy>
@ -873,7 +891,7 @@ class App {
return opt;
}
/// Add a complex number
/// Add a complex number DEPRECATED --use add_option instead
template <typename T, typename XC = double>
Option *add_complex(std::string option_name,
T &variable,
@ -2660,7 +2678,12 @@ class App {
// Get a reference to the pointer to make syntax bearable
Option_p &op = *op_ptr;
/// if we require a separator add it here
if(op->get_inject_separator()) {
if(!op->results().empty() && !op->results().back().empty()) {
op->add_result(std::string{});
}
}
int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
int max_num = op->get_items_expected_max();
@ -2725,7 +2748,7 @@ class App {
}
// if we only partially completed a type then add an empty string for later processing
if(min_num > 0 && op->get_type_size_max() != min_num && collected % op->get_type_size_max() != 0) {
if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) {
op->add_result(std::string{});
}

View File

@ -315,7 +315,7 @@ class Option : public OptionBase<Option> {
/// results after reduction
results_t proc_results_{};
/// enumeration for the option state machine
enum class option_state {
enum class option_state : char {
parsing = 0, //!< The option is currently collecting parsed results
validated = 2, //!< the results have been validated
reduced = 4, //!< a subset of results has been generated
@ -329,6 +329,8 @@ class Option : public OptionBase<Option> {
bool flag_like_{false};
/// Control option to run the callback to set the default
bool run_callback_for_default_{false};
/// flag indicating a separator needs to be injected after each argument call
bool inject_separator_{false};
///@}
/// Making an option by hand is not defined, it must be made by the App class
@ -658,6 +660,9 @@ class Option : public OptionBase<Option> {
/// The maximum number of arguments the option expects
int get_type_size_max() const { return type_size_max_; }
/// The number of arguments the option expects
int get_inject_separator() const { return inject_separator_; }
/// The environment variable associated to this value
std::string get_envname() const { return envname_; }
@ -963,8 +968,8 @@ class Option : public OptionBase<Option> {
return this;
}
/// Get a copy of the results
results_t results() const { return results_; }
/// Get the current complete results set
const results_t &results() const { return results_; }
/// Get a copy of the results
results_t reduced_results() const {
@ -986,8 +991,7 @@ class Option : public OptionBase<Option> {
}
/// Get the results as a specified type
template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy>
void results(T &output) const {
template <typename T> void results(T &output) const {
bool retval;
if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
@ -1054,6 +1058,8 @@ class Option : public OptionBase<Option> {
type_size_max_ = option_type_size;
if(type_size_max_ < detail::expected_max_vector_size) {
type_size_min_ = option_type_size;
} else {
inject_separator_ = true;
}
if(type_size_max_ == 0)
required_ = false;
@ -1079,9 +1085,15 @@ class Option : public OptionBase<Option> {
if(type_size_max_ == 0) {
required_ = false;
}
if(type_size_max_ >= detail::expected_max_vector_size) {
inject_separator_ = true;
}
return this;
}
/// Set the value of the separator injection flag
void inject_separator(bool value = true) { inject_separator_ = value; }
/// Set a capture function for the default. Mostly used by App.
Option *default_function(const std::function<std::string()> &func) {
default_function_ = func;
@ -1157,7 +1169,7 @@ class Option : public OptionBase<Option> {
}
for(std::string &result : res) {
if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) {
if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
index = 0; // reset index for variable size chunks
continue;
}

View File

@ -190,6 +190,12 @@ inline bool valid_name_string(const std::string &str) {
return true;
}
/// check if a string is a container segment separator (empty or "%%"
inline bool is_separator(const std::string &str) {
static const std::string sep("%%");
return (str.empty() || str == sep);
}
/// Verify that str consists of letters only
inline bool isalpha(const std::string &str) {
return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });

File diff suppressed because it is too large Load Diff

View File

@ -454,98 +454,6 @@ TEST_F(TApp, SepInt) {
EXPECT_EQ(i, 4);
}
TEST_F(TApp, OneStringAgain) {
std::string str;
app.add_option("-s,--string", str);
args = {"--string", "mystring"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--string"));
EXPECT_EQ(str, "mystring");
}
TEST_F(TApp, OneStringFunction) {
std::string str;
app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; });
args = {"--string", "mystring"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--string"));
EXPECT_EQ(str, "mystring");
}
TEST_F(TApp, doubleFunction) {
double res{0.0};
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "-354.356"};
run();
EXPECT_EQ(res, 300.356);
// get the original value as entered as an integer
EXPECT_EQ(app["--val"]->as<float>(), -354.356f);
}
TEST_F(TApp, doubleFunctionFail) {
double res;
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "not_double"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, doubleVectorFunction) {
std::vector<double> res;
app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
EXPECT_EQ(res.size(), 3u);
EXPECT_EQ(res[0], 10.0);
EXPECT_EQ(res[2], 12.0);
}
TEST_F(TApp, doubleVectorFunctionFail) {
std::vector<double> res;
std::string vstring = "--val";
app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "five", "--val", "nine", "--val", "7"};
EXPECT_THROW(run(), CLI::ConversionError);
// check that getting the results through the results function generates the same error
EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError);
auto strvec = app[vstring]->as<std::vector<std::string>>();
EXPECT_EQ(strvec.size(), 3u);
}
TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) {
std::vector<double> res;
auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
EXPECT_EQ(res.size(), 3u);
EXPECT_EQ(res[0], 10.0);
EXPECT_EQ(res[2], 12.0);
EXPECT_FALSE(opt->get_run_callback_for_default());
opt->run_callback_for_default();
opt->default_val(std::vector<int>{2, 1, -2});
EXPECT_EQ(res[0], 7.0);
EXPECT_EQ(res[2], 3.0);
EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError);
auto vec = opt->as<std::vector<double>>();
ASSERT_EQ(vec.size(), 3U);
EXPECT_EQ(vec[0], 5.0);
EXPECT_EQ(vec[2], 7.0);
opt->check(CLI::Number);
opt->run_callback_for_default(false);
EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError);
}
TEST_F(TApp, DefaultStringAgain) {
std::string str = "previous";
app.add_option("-s,--string", str);
@ -647,35 +555,6 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) {
EXPECT_EQ(1u, app.count("-A"));
}
TEST_F(TApp, BoolAndIntFlags) {
bool bflag{false};
int iflag{0};
unsigned int uflag{0};
app.add_flag("-b", bflag);
app.add_flag("-i", iflag);
app.add_flag("-u", uflag);
args = {"-b", "-i", "-u"};
run();
EXPECT_TRUE(bflag);
EXPECT_EQ(1, iflag);
EXPECT_EQ((unsigned int)1, uflag);
args = {"-b", "-b"};
ASSERT_NO_THROW(run());
EXPECT_TRUE(bflag);
bflag = false;
args = {"-iiiuu"};
run();
EXPECT_FALSE(bflag);
EXPECT_EQ(3, iflag);
EXPECT_EQ((unsigned int)2, uflag);
}
TEST_F(TApp, FlagLikeOption) {
bool val{false};
auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
@ -724,32 +603,6 @@ TEST_F(TApp, BoolOnlyFlag) {
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, BoolOption) {
bool bflag{false};
app.add_option("-b", bflag);
args = {"-b", "false"};
run();
EXPECT_FALSE(bflag);
args = {"-b", "1"};
run();
EXPECT_TRUE(bflag);
args = {"-b", "-7"};
run();
EXPECT_FALSE(bflag);
// cause an out of bounds error internally
args = {"-b", "751615654161688126132138844896646748852"};
run();
EXPECT_TRUE(bflag);
args = {"-b", "-751615654161688126132138844896646748852"};
run();
EXPECT_FALSE(bflag);
}
TEST_F(TApp, ShortOpts) {
unsigned long long funnyint{0};
@ -892,37 +745,6 @@ TEST_F(TApp, TakeLastOptMulti) {
EXPECT_EQ(vals, std::vector<int>({2, 3}));
}
TEST_F(TApp, vectorDefaults) {
std::vector<int> vals{4, 5};
auto opt = app.add_option("--long", vals, "", true);
args = {"--long", "[1,2,3]"};
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
args.clear();
run();
auto res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({4, 5}));
app.clear();
opt->expected(1)->take_last();
res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({5}));
opt->take_first();
res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({4}));
opt->expected(0, 1)->take_last();
run();
EXPECT_EQ(res, std::vector<int>({4}));
res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({5}));
}
TEST_F(TApp, TakeLastOptMulti_alternative_path) {
std::vector<int> vals;
app.add_option("--long", vals)->expected(2, -1)->take_last();
@ -1452,27 +1274,6 @@ TEST_F(TApp, CallbackFlags) {
EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
}
TEST_F(TApp, CallbackBoolFlags) {
bool value{false};
auto func = [&value]() { value = true; };
auto cback = app.add_flag_callback("--val", func);
args = {"--val"};
run();
EXPECT_TRUE(value);
value = false;
args = {"--val=false"};
run();
EXPECT_FALSE(value);
EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"--val", "--val=false"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, CallbackFlagsFalse) {
std::int64_t value = 0;
@ -1747,107 +1548,6 @@ TEST_F(TApp, NotFileExists) {
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
}
TEST_F(TApp, pair_check) {
std::string myfile{"pair_check_file.txt"};
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
EXPECT_TRUE(ok);
EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
std::pair<std::string, int> findex;
auto v0 = CLI::ExistingFile;
v0.application_index(0);
auto v1 = CLI::PositiveNumber;
v1.application_index(1);
app.add_option("--file", findex)->check(v0)->check(v1);
args = {"--file", myfile, "2"};
EXPECT_NO_THROW(run());
EXPECT_EQ(findex.first, myfile);
EXPECT_EQ(findex.second, 2);
args = {"--file", myfile, "-3"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--file", myfile, "2"};
std::remove(myfile.c_str());
EXPECT_THROW(run(), CLI::ValidationError);
}
// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
TEST_F(TApp, pair_check_take_first) {
std::string myfile{"pair_check_file2.txt"};
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
EXPECT_TRUE(ok);
EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
std::pair<std::string, int> findex;
auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound);
opt->get_validator(0)->application_index(0);
opt->get_validator(1)->application_index(1);
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
// should only check the last one
EXPECT_NO_THROW(run());
EXPECT_EQ(findex.first, myfile);
EXPECT_EQ(findex.second, 2);
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, VectorFixedString) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3);
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, VectorDefaultedFixedString) {
std::vector<std::string> strvec{"one"};
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, VectorIndexedValidator) {
std::vector<int> vvec;
CLI::Option *opt = app.add_option("-v", vvec);
args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
run();
EXPECT_EQ(4u, app.count("-v"));
EXPECT_EQ(4u, vvec.size());
opt->check(CLI::PositiveNumber.application_index(0));
opt->check((!CLI::PositiveNumber).application_index(1));
EXPECT_NO_THROW(run());
EXPECT_EQ(4u, vvec.size());
// v[3] would be negative
opt->check(CLI::PositiveNumber.application_index(3));
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, DefaultedResult) {
std::string sval = "NA";
int ival{0};
@ -1866,93 +1566,6 @@ TEST_F(TApp, DefaultedResult) {
EXPECT_EQ(newIval, 442);
}
TEST_F(TApp, VectorUnlimString) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec);
EXPECT_EQ(1, opt->get_expected());
EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
args = {"-s", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
// From https://github.com/CLIUtils/CLI11/issues/420
TEST_F(TApp, stringLikeTests) {
struct nType {
explicit nType(const std::string &a_value) : m_value{a_value} {}
explicit operator std::string() const { return std::string{"op str"}; }
std::string m_value;
};
nType m_type{"abc"};
app.add_option("--type", m_type, "type")->capture_default_str();
run();
EXPECT_EQ(app["--type"]->as<std::string>(), "op str");
args = {"--type", "bca"};
run();
EXPECT_EQ(std::string(m_type), "op str");
EXPECT_EQ(m_type.m_value, "bca");
}
TEST_F(TApp, VectorExpectedRange) {
std::vector<std::string> strvec;
CLI::Option *opt = app.add_option("--string", strvec);
opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
args = {"--string", "mystring"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
EXPECT_EQ(opt->get_expected_max(), 4);
EXPECT_EQ(opt->get_expected_min(), 2);
opt->expected(4, 2); // just test the handling of reversed arguments
EXPECT_EQ(opt->get_expected_max(), 4);
EXPECT_EQ(opt->get_expected_min(), 2);
opt->expected(-5);
EXPECT_EQ(opt->get_expected_max(), 5);
EXPECT_EQ(opt->get_expected_min(), 5);
opt->expected(-5, 7);
EXPECT_EQ(opt->get_expected_max(), 7);
EXPECT_EQ(opt->get_expected_min(), 5);
}
TEST_F(TApp, VectorFancyOpts) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3);
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
args = {"one", "two"};
EXPECT_THROW(run(), CLI::RequiredError);
EXPECT_THROW(run(), CLI::ParseError);
}
TEST_F(TApp, OriginalOrder) {
std::vector<int> st1;
CLI::Option *op1 = app.add_option("-a", st1);
@ -2406,170 +2019,6 @@ TEST_F(TApp, EachItem) {
EXPECT_EQ(results, dummy);
}
// #87
TEST_F(TApp, CustomDoubleOption) {
std::pair<int, double> custom_opt;
auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
return true;
});
opt->type_name("INT FLOAT")->type_size(2);
args = {"12", "1.5"};
run();
EXPECT_EQ(custom_opt.first, 12);
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
}
// now with tuple support this is possible
TEST_F(TApp, CustomDoubleOptionAlt) {
std::pair<int, double> custom_opt;
app.add_option("posit", custom_opt);
args = {"12", "1.5"};
run();
EXPECT_EQ(custom_opt.first, 12);
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorPair) {
std::vector<std::pair<int, std::string>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
args = {"--dict", "1", "str1", "--dict", "3", "str3"};
run();
EXPECT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].first, 1);
EXPECT_EQ(custom_opt[1].second, "str3");
args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"};
run();
EXPECT_EQ(custom_opt.size(), 3u);
EXPECT_EQ(custom_opt[2].first, -1);
EXPECT_EQ(custom_opt[2].second, "str4");
opt->check(CLI::PositiveNumber.application_index(0));
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, vectorPairFail) {
std::vector<std::pair<int, std::string>> custom_opt;
app.add_option("--dict", custom_opt);
args = {"--dict", "1", "str1", "--dict", "str3", "1"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, vectorPairTypeRange) {
std::vector<std::pair<int, std::string>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
opt->type_size(2, 1); // just test switched arguments
EXPECT_EQ(opt->get_type_size_min(), 1);
EXPECT_EQ(opt->get_type_size_max(), 2);
args = {"--dict", "1", "str1", "--dict", "3", "str3"};
run();
EXPECT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].first, 1);
EXPECT_EQ(custom_opt[1].second, "str3");
args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
run();
EXPECT_EQ(custom_opt.size(), 3u);
EXPECT_TRUE(custom_opt[1].second.empty());
EXPECT_EQ(custom_opt[2].first, -1);
EXPECT_EQ(custom_opt[2].second, "str4");
opt->type_size(-2, -1); // test negative arguments
EXPECT_EQ(opt->get_type_size_min(), 1);
EXPECT_EQ(opt->get_type_size_max(), 2);
// this type size spec should run exactly as before
run();
EXPECT_EQ(custom_opt.size(), 3u);
EXPECT_TRUE(custom_opt[1].second.empty());
EXPECT_EQ(custom_opt[2].first, -1);
EXPECT_EQ(custom_opt[2].second, "str4");
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorTuple) {
std::vector<std::tuple<int, std::string, double>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
run();
EXPECT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(std::get<0>(custom_opt[0]), 1);
EXPECT_EQ(std::get<1>(custom_opt[1]), "str3");
EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7);
args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
run();
EXPECT_EQ(custom_opt.size(), 3u);
EXPECT_EQ(std::get<0>(custom_opt[2]), -1);
EXPECT_EQ(std::get<1>(custom_opt[2]), "str4");
EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87);
opt->check(CLI::PositiveNumber.application_index(0));
EXPECT_THROW(run(), CLI::ValidationError);
args.back() = "haha";
args[9] = "45";
EXPECT_THROW(run(), CLI::ConversionError);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorVector) {
std::vector<std::vector<int>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
run();
EXPECT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].size(), 3u);
EXPECT_EQ(custom_opt[1].size(), 2u);
args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
"3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
run();
EXPECT_EQ(custom_opt.size(), 4u);
EXPECT_EQ(custom_opt[0].size(), 3u);
EXPECT_EQ(custom_opt[1].size(), 2u);
EXPECT_EQ(custom_opt[2].size(), 1u);
EXPECT_EQ(custom_opt[3].size(), 10u);
opt->check(CLI::PositiveNumber.application_index(9));
EXPECT_THROW(run(), CLI::ValidationError);
args.pop_back();
EXPECT_NO_THROW(run());
args.back() = "haha";
EXPECT_THROW(run(), CLI::ConversionError);
}
// #128
TEST_F(TApp, RepeatingMultiArgumentOptions) {
std::vector<std::string> entries;

View File

@ -0,0 +1,141 @@
#include "app_helper.hpp"
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/container/slist.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/container/stable_vector.hpp>
#include <boost/container/static_vector.hpp>
#include <boost/container/vector.hpp>
#include <string>
#include <vector>
#include "gmock/gmock.h"
using namespace boost::container;
template <class T> class TApp_container_single_boost : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_single_boost() : TApp(){};
};
using containerTypes_single_boost =
::testing::Types<small_vector<int, 2>, small_vector<int, 3>, flat_set<int>, stable_vector<int>, slist<int>>;
TYPED_TEST_SUITE(TApp_container_single_boost, containerTypes_single_boost, );
TYPED_TEST(TApp_container_single_boost, containerInt_boost) {
auto &cv = TApp_container_single_boost<TypeParam>::cval;
CLI::Option *opt = (TApp::app).add_option("-v", cv);
TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
TApp::run();
EXPECT_EQ(4u, (TApp::app).count("-v"));
EXPECT_EQ(4u, cv.size());
opt->check(CLI::PositiveNumber.application_index(0));
opt->check((!CLI::PositiveNumber).application_index(1));
EXPECT_NO_THROW(TApp::run());
EXPECT_EQ(4u, cv.size());
// v[3] would be negative
opt->check(CLI::PositiveNumber.application_index(3));
EXPECT_THROW(TApp::run(), CLI::ValidationError);
}
template <class T> class TApp_container_pair_boost : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_pair_boost() : TApp(){};
};
using isp = std::pair<int, std::string>;
using containerTypes_pair_boost = ::testing::
Types<stable_vector<isp>, small_vector<isp, 2>, flat_set<isp>, slist<isp>, vector<isp>, flat_map<int, std::string>>;
TYPED_TEST_SUITE(TApp_container_pair_boost, containerTypes_pair_boost, );
TYPED_TEST(TApp_container_pair_boost, containerPair_boost) {
auto &cv = TApp_container_pair_boost<TypeParam>::cval;
(TApp::app).add_option("--dict", cv);
TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"};
TApp::run();
EXPECT_EQ(cv.size(), 2u);
TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
TApp::run();
EXPECT_EQ(cv.size(), 3u);
}
template <class T> class TApp_container_tuple_boost : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_tuple_boost() : TApp(){};
};
using tup_obj = std::tuple<int, std::string, double>;
using containerTypes_tuple_boost =
::testing::Types<small_vector<tup_obj, 3>, stable_vector<tup_obj>, flat_set<tup_obj>, slist<tup_obj>>;
TYPED_TEST_SUITE(TApp_container_tuple_boost, containerTypes_tuple_boost, );
TYPED_TEST(TApp_container_tuple_boost, containerTuple_boost) {
auto &cv = TApp_container_tuple_boost<TypeParam>::cval;
(TApp::app).add_option("--dict", cv);
TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
TApp::run();
EXPECT_EQ(cv.size(), 2u);
TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
TApp::run();
EXPECT_EQ(cv.size(), 3u);
}
using icontainer1 = vector<int>;
using icontainer2 = flat_set<int>;
using icontainer3 = slist<int>;
using containerTypes_container_boost = ::testing::Types<std::vector<icontainer1>,
slist<icontainer1>,
flat_set<icontainer1>,
small_vector<icontainer1, 2>,
std::vector<icontainer2>,
slist<icontainer2>,
flat_set<icontainer2>,
stable_vector<icontainer2>,
static_vector<icontainer3, 10>,
slist<icontainer3>,
flat_set<icontainer3>,
static_vector<icontainer3, 10>>;
template <class T> class TApp_container_container_boost : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_container_boost() : TApp(){};
};
TYPED_TEST_SUITE(TApp_container_container_boost, containerTypes_container_boost, );
TYPED_TEST(TApp_container_container_boost, containerContainer_boost) {
auto &cv = TApp_container_container_boost<TypeParam>::cval;
(TApp::app).add_option("--dict", cv);
TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
TApp::run();
EXPECT_EQ(cv.size(), 2u);
TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
"3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
TApp::run();
EXPECT_EQ(cv.size(), 4u);
}

View File

@ -32,13 +32,19 @@ endif()
set(GOOGLE_TEST_INDIVIDUAL OFF)
include(AddGoogletest)
# Add boost to test boost::optional if available
find_package(Boost 1.61)
set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>)
set(CLI11_TESTS
HelpersTest
ConfigFileTest
OptionTypeTest
SimpleTest
AppTest
SetTest
TransformTest
TransformTest
CreationTest
SubcommandTest
HelpTest
@ -47,6 +53,7 @@ set(CLI11_TESTS
OptionalTest
DeprecatedTest
StringParseTest
ComplexTypeTest
TrueFalseTest
OptionGroupTest
)
@ -55,6 +62,10 @@ if(WIN32)
list(APPEND CLI11_TESTS WindowsTest)
endif()
if (Boost_FOUND)
list(APPEND CLI11_TESTS BoostOptionTypeTest)
endif()
set(CLI11_MULTIONLY_TESTS TimerTest)
# Only affects current directory, so safe
@ -140,20 +151,29 @@ file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
"set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")"
)
# Add boost to test boost::optional if available
find_package(Boost 1.61)
set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>)
target_compile_definitions(informational PRIVATE ${boost-optional-def})
target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def})
message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}")
if(TARGET Boost::boost)
message(STATUS "including boost target")
target_link_libraries(informational PRIVATE Boost::boost)
target_link_libraries(OptionalTest PRIVATE Boost::boost)
target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost)
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
target_link_libraries(OptionalTest_Single PRIVATE Boost::boost)
target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost)
endif()
elseif(BOOST_FOUND)
message(STATUS "no boost target")
target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS})
if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
endif()
endif()
if(CMAKE_BUILD_TYPE STREQUAL Coverage)

185
tests/ComplexTypeTest.cpp Normal file
View File

@ -0,0 +1,185 @@
#include "app_helper.hpp"
#include "gmock/gmock.h"
#include <complex>
#include <cstdint>
using ::testing::HasSubstr;
using cx = std::complex<double>;
CLI::Option *
add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
double x, y;
bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
if(worked)
variable = cx(x, y);
return worked;
};
CLI::Option *opt = app.add_option(name, fun, description, defaulted);
opt->type_name("COMPLEX")->type_size(2);
if(defaulted) {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return opt;
}
TEST_F(TApp, AddingComplexParser) {
cx comp{0, 0};
add_option(app, "-c,--complex", comp);
args = {"-c", "1.5", "2.5"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(2.5, comp.imag());
}
TEST_F(TApp, DefaultedComplex) {
cx comp{1, 2};
add_option(app, "-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_DOUBLE_EQ(1, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
}
// an example of custom complex number converter that can be used to add new parsing options
#if defined(__has_include)
#if __has_include(<regex>)
// an example of custom converter that can be used to add new parsing options
#define HAS_REGEX_INCLUDE
#endif
#endif
#ifdef HAS_REGEX_INCLUDE
// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
// simplify compilation and prevent a much more complicated #if expression
#include <regex>
namespace CLI {
namespace detail {
// On MSVC and possibly some other new compilers this can be a free standing function without the template
// specialization but this is compiler dependent
template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
// regular expression to handle complex numbers of various formats
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
std::smatch m;
double x{0.0}, y{0.0};
bool worked;
std::regex_search(input, m, creg);
if(m.size() == 9) {
worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
if(worked) {
if(*m[5].first == '-') {
y = -y;
}
}
} else {
if((input.back() == 'j') || (input.back() == 'i')) {
auto strval = input.substr(0, input.size() - 1);
CLI::detail::trim(strval);
worked = CLI::detail::lexical_cast(strval, y);
} else {
std::string ival = input;
CLI::detail::trim(ival);
worked = CLI::detail::lexical_cast(ival, x);
}
}
if(worked) {
output = cx{x, y};
}
return worked;
}
} // namespace detail
} // namespace CLI
TEST_F(TApp, AddingComplexParserDetail) {
bool skip_tests = false;
try { // check if the library actually supports regex, it is possible to link against a non working regex in the
// standard library
std::smatch m;
std::string input = "1.5+2.5j";
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
auto rsearch = std::regex_search(input, m, creg);
if(!rsearch) {
skip_tests = true;
} else {
EXPECT_EQ(m.size(), 9u);
}
} catch(...) {
skip_tests = true;
}
static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation");
if(!skip_tests) {
cx comp{0, 0};
app.add_option("-c,--complex", comp, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(2.5, comp.imag());
args = {"-c", "1.5-2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(-2.5, comp.imag());
}
}
#endif
// defining a new complex class
class complex_new {
public:
complex_new() = default;
complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
double real() { return val1_; }
double imag() { return val2_; }
private:
double val1_{0.0};
double val2_{0.0};
};
TEST_F(TApp, newComplex) {
complex_new cval;
static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type");
static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number,
"complex new does not result in complex number categorization");
app.add_option("-c,--complex", cval, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, cval.real());
EXPECT_DOUBLE_EQ(2.5, cval.imag());
args = {"-c", "1.5-2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, cval.real());
EXPECT_DOUBLE_EQ(-2.5, cval.imag());
}

View File

@ -6,8 +6,10 @@
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <map>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
class NotStreamable {};
@ -52,6 +54,57 @@ TEST(TypeTools, type_size) {
EXPECT_EQ(V, 5);
V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count<std::tuple<std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count<std::tuple<int, std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 3);
V = CLI::detail::type_count<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 4);
// maps
V = CLI::detail::type_count<std::map<int, std::pair<int, double>>>::value;
EXPECT_EQ(V, 3);
// three level tuples
V = CLI::detail::type_count<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value;
EXPECT_EQ(V, 5);
V = CLI::detail::type_count<std::pair<int, std::vector<int>>>::value;
EXPECT_GE(V, CLI::detail::expected_max_vector_size);
V = CLI::detail::type_count<std::vector<std::vector<int>>>::value;
EXPECT_EQ(V, CLI::detail::expected_max_vector_size);
}
TEST(TypeTools, type_size_min) {
auto V = CLI::detail::type_count_min<int>::value;
EXPECT_EQ(V, 1);
V = CLI::detail::type_count_min<void>::value;
EXPECT_EQ(V, 0);
V = CLI::detail::type_count_min<std::vector<double>>::value;
EXPECT_EQ(V, 1);
V = CLI::detail::type_count_min<std::tuple<double, int>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count_min<std::tuple<std::string, double, int>>::value;
EXPECT_EQ(V, 3);
V = CLI::detail::type_count_min<std::array<std::string, 5>>::value;
EXPECT_EQ(V, 5);
V = CLI::detail::type_count_min<std::vector<std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count_min<std::tuple<std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count_min<std::tuple<int, std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 3);
V = CLI::detail::type_count_min<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value;
EXPECT_EQ(V, 4);
// maps
V = CLI::detail::type_count_min<std::map<int, std::pair<int, double>>>::value;
EXPECT_EQ(V, 3);
// three level tuples
V = CLI::detail::type_count_min<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value;
EXPECT_EQ(V, 5);
V = CLI::detail::type_count_min<std::pair<int, std::vector<int>>>::value;
EXPECT_EQ(V, 2);
V = CLI::detail::type_count_min<std::vector<std::vector<int>>>::value;
EXPECT_EQ(V, 1);
V = CLI::detail::type_count_min<std::vector<std::vector<std::pair<int, int>>>>::value;
EXPECT_EQ(V, 2);
}
TEST(TypeTools, expected_count) {
@ -862,6 +915,10 @@ TEST(Types, TypeName) {
CLI::detail::object_category::tuple_value,
"pair<int,string> does not read like a tuple");
static_assert(CLI::detail::classify_object<std::tuple<std::string, double>>::value ==
CLI::detail::object_category::tuple_value,
"tuple<string,double> does not read like a tuple");
std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>();
EXPECT_EQ("[INT,TEXT]", pair_name);
@ -869,7 +926,7 @@ TEST(Types, TypeName) {
EXPECT_EQ("UINT", vector_name);
auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value;
EXPECT_EQ(vclass, CLI::detail::object_category::vector_value);
EXPECT_EQ(vclass, CLI::detail::object_category::container_value);
auto tclass = CLI::detail::classify_object<std::tuple<double>>::value;
EXPECT_EQ(tclass, CLI::detail::object_category::number_constructible);
@ -883,6 +940,18 @@ TEST(Types, TypeName) {
tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
EXPECT_EQ("[INT,TEXT]", tuple_name);
tuple_name = CLI::detail::type_name<std::tuple<const int, std::string>>();
EXPECT_EQ("[INT,TEXT]", tuple_name);
tuple_name = CLI::detail::type_name<const std::tuple<int, std::string>>();
EXPECT_EQ("[INT,TEXT]", tuple_name);
tuple_name = CLI::detail::type_name<std::tuple<std::string, double>>();
EXPECT_EQ("[TEXT,FLOAT]", tuple_name);
tuple_name = CLI::detail::type_name<const std::tuple<std::string, double>>();
EXPECT_EQ("[TEXT,FLOAT]", tuple_name);
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name);
@ -911,6 +980,8 @@ TEST(Types, TypeName) {
"tuple<test> does not classify as a tuple");
std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
EXPECT_EQ("ENUM", enum_name2);
std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>();
EXPECT_EQ("[INT,[TEXT,FLOAT]]", umapName);
}
TEST(Types, OverflowSmall) {
@ -995,11 +1066,11 @@ TEST(Types, LexicalCastParsable) {
std::complex<double> output;
EXPECT_TRUE(CLI::detail::lexical_cast(input, output));
EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const
EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + gcc 4.8 due to missing const
EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output));
EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble
EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const
EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + gcc 4.8 due to missing const
EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
@ -1173,6 +1244,26 @@ TEST(Types, LexicalConversionComplex) {
EXPECT_EQ(x.imag(), 3.5);
}
static_assert(CLI::detail::is_wrapper<std::vector<double>>::value, "vector double should be a wrapper");
static_assert(CLI::detail::is_wrapper<std::vector<std::string>>::value, "vector string should be a wrapper");
static_assert(CLI::detail::is_wrapper<std::string>::value, "string should be a wrapper");
static_assert(!CLI::detail::is_wrapper<double>::value, "double should not be a wrapper");
static_assert(CLI::detail::is_mutable_container<std::vector<double>>::value, "vector class should be a container");
static_assert(CLI::detail::is_mutable_container<std::vector<std::string>>::value, "vector class should be a container");
static_assert(!CLI::detail::is_mutable_container<std::string>::value, "string should be a container");
static_assert(!CLI::detail::is_mutable_container<double>::value, "double should not be a container");
static_assert(!CLI::detail::is_mutable_container<std::array<double, 5>>::value, "array should not be a container");
static_assert(CLI::detail::is_mutable_container<std::vector<int>>::value, "vector int should be a container");
static_assert(CLI::detail::is_readable_container<std::vector<int> &>::value,
"vector int & should be a readable container");
static_assert(CLI::detail::is_readable_container<const std::vector<int>>::value,
"const vector int should be a readable container");
static_assert(CLI::detail::is_readable_container<const std::vector<int> &>::value,
"const vector int & should be a readable container");
TEST(FixNewLines, BasicCheck) {
std::string input = "one\ntwo";
std::string output = "one\n; two";

View File

@ -7,58 +7,7 @@ using ::testing::HasSubstr;
using cx = std::complex<double>;
CLI::Option *
add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
CLI::callback_t fun = [&variable](CLI::results_t res) {
double x, y;
bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
if(worked)
variable = cx(x, y);
return worked;
};
CLI::Option *opt = app.add_option(name, fun, description, defaulted);
opt->type_name("COMPLEX")->type_size(2);
if(defaulted) {
std::stringstream out;
out << variable;
opt->default_str(out.str());
}
return opt;
}
TEST_F(TApp, AddingComplexParser) {
cx comp{0, 0};
add_option(app, "-c,--complex", comp);
args = {"-c", "1.5", "2.5"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(2.5, comp.imag());
}
TEST_F(TApp, DefaultComplex) {
cx comp{1, 2};
add_option(app, "-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_DOUBLE_EQ(1, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
}
TEST_F(TApp, BuiltinComplex) {
TEST_F(TApp, Complex) {
cx comp{1, 2};
app.add_complex("-c,--complex", comp, "", true);
@ -78,7 +27,27 @@ TEST_F(TApp, BuiltinComplex) {
EXPECT_DOUBLE_EQ(3, comp.imag());
}
TEST_F(TApp, BuiltinComplexFloat) {
TEST_F(TApp, ComplexOption) {
cx comp{1, 2};
app.add_option("-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_THAT(help, HasSubstr("COMPLEX"));
EXPECT_DOUBLE_EQ(1, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
}
TEST_F(TApp, ComplexFloat) {
std::complex<float> comp{1, 2};
app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true);
@ -98,7 +67,27 @@ TEST_F(TApp, BuiltinComplexFloat) {
EXPECT_FLOAT_EQ(3, comp.imag());
}
TEST_F(TApp, BuiltinComplexWithDelimiter) {
TEST_F(TApp, ComplexFloatOption) {
std::complex<float> comp{1, 2};
app.add_option("-c,--complex", comp, "", true);
args = {"-c", "4", "3"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_THAT(help, HasSubstr("COMPLEX"));
EXPECT_FLOAT_EQ(1, comp.real());
EXPECT_FLOAT_EQ(2, comp.imag());
run();
EXPECT_FLOAT_EQ(4, comp.real());
EXPECT_FLOAT_EQ(3, comp.imag());
}
TEST_F(TApp, ComplexWithDelimiter) {
cx comp{1, 2};
app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
@ -130,7 +119,39 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) {
EXPECT_DOUBLE_EQ(-4, comp.imag());
}
TEST_F(TApp, BuiltinComplexIgnoreI) {
TEST_F(TApp, ComplexWithDelimiterOption) {
cx comp{1, 2};
app.add_option("-c,--complex", comp, "", true)->delimiter('+');
args = {"-c", "4+3i"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_THAT(help, HasSubstr("COMPLEX"));
EXPECT_DOUBLE_EQ(1, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
args = {"-c", "5+-3i"};
run();
EXPECT_DOUBLE_EQ(5, comp.real());
EXPECT_DOUBLE_EQ(-3, comp.imag());
args = {"-c", "6", "-4i"};
run();
EXPECT_DOUBLE_EQ(6, comp.real());
EXPECT_DOUBLE_EQ(-4, comp.imag());
}
TEST_F(TApp, ComplexIgnoreI) {
cx comp{1, 2};
app.add_complex("-c,--complex", comp);
@ -142,7 +163,19 @@ TEST_F(TApp, BuiltinComplexIgnoreI) {
EXPECT_DOUBLE_EQ(3, comp.imag());
}
TEST_F(TApp, BuiltinComplexSingleArg) {
TEST_F(TApp, ComplexIgnoreIOption) {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
args = {"-c", "4", "3i"};
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
}
TEST_F(TApp, ComplexSingleArg) {
cx comp{1, 2};
app.add_complex("-c,--complex", comp);
@ -176,7 +209,41 @@ TEST_F(TApp, BuiltinComplexSingleArg) {
EXPECT_DOUBLE_EQ(-2.7, comp.imag());
}
TEST_F(TApp, BuiltinComplexSingleImag) {
TEST_F(TApp, ComplexSingleArgOption) {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
args = {"-c", "4"};
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(0, comp.imag());
args = {"-c", "4-2i"};
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(-2, comp.imag());
args = {"-c", "4+2i"};
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
args = {"-c", "-4+2j"};
run();
EXPECT_DOUBLE_EQ(-4, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
args = {"-c", "-4.2-2j"};
run();
EXPECT_DOUBLE_EQ(-4.2, comp.real());
EXPECT_DOUBLE_EQ(-2, comp.imag());
args = {"-c", "-4.2-2.7i"};
run();
EXPECT_DOUBLE_EQ(-4.2, comp.real());
EXPECT_DOUBLE_EQ(-2.7, comp.imag());
}
TEST_F(TApp, ComplexSingleImag) {
cx comp{1, 2};
app.add_complex("-c,--complex", comp);
@ -199,6 +266,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) {
EXPECT_DOUBLE_EQ(0, comp.imag());
}
TEST_F(TApp, ComplexSingleImagOption) {
cx comp{1, 2};
app.add_option("-c,--complex", comp);
args = {"-c", "4j"};
run();
EXPECT_DOUBLE_EQ(0, comp.real());
EXPECT_DOUBLE_EQ(4, comp.imag());
args = {"-c", "-4j"};
run();
EXPECT_DOUBLE_EQ(0, comp.real());
EXPECT_DOUBLE_EQ(-4, comp.imag());
args = {"-c", "-4"};
run();
EXPECT_DOUBLE_EQ(-4, comp.real());
EXPECT_DOUBLE_EQ(0, comp.imag());
args = {"-c", "+4"};
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(0, comp.imag());
}
/// Simple class containing two strings useful for testing lexical cast and conversions
class spair {
public:
@ -245,98 +335,6 @@ TEST_F(TApp, custom_string_converterFail) {
EXPECT_THROW(run(), CLI::ConversionError);
}
// an example of custom complex number converter that can be used to add new parsing options
#if defined(__has_include)
#if __has_include(<regex>)
// an example of custom converter that can be used to add new parsing options
#define HAS_REGEX_INCLUDE
#endif
#endif
#ifdef HAS_REGEX_INCLUDE
// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
// simplify compilation and prevent a much more complicated #if expression
#include <regex>
namespace CLI {
namespace detail {
// On MSVC and possibly some other new compilers this can be a free standing function without the template
// specialization but this is compiler dependent
template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
// regular expression to handle complex numbers of various formats
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
std::smatch m;
double x{0.0}, y{0.0};
bool worked;
std::regex_search(input, m, creg);
if(m.size() == 9) {
worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
if(worked) {
if(*m[5].first == '-') {
y = -y;
}
}
} else {
if((input.back() == 'j') || (input.back() == 'i')) {
auto strval = input.substr(0, input.size() - 1);
CLI::detail::trim(strval);
worked = CLI::detail::lexical_cast(strval, y);
} else {
std::string ival = input;
CLI::detail::trim(ival);
worked = CLI::detail::lexical_cast(ival, x);
}
}
if(worked) {
output = cx{x, y};
}
return worked;
}
} // namespace detail
} // namespace CLI
TEST_F(TApp, AddingComplexParserDetail) {
bool skip_tests = false;
try { // check if the library actually supports regex, it is possible to link against a non working regex in the
// standard library
std::smatch m;
std::string input = "1.5+2.5j";
static const std::regex creg(
R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
auto rsearch = std::regex_search(input, m, creg);
if(!rsearch) {
skip_tests = true;
} else {
EXPECT_EQ(m.size(), 9u);
}
} catch(...) {
skip_tests = true;
}
if(!skip_tests) {
cx comp{0, 0};
app.add_option("-c,--complex", comp, "add a complex number option");
args = {"-c", "1.5+2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(2.5, comp.imag());
args = {"-c", "1.5-2.5j"};
run();
EXPECT_DOUBLE_EQ(1.5, comp.real());
EXPECT_DOUBLE_EQ(-2.5, comp.imag());
}
}
#endif
/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the
/// option assignments
template <class X> class objWrapper {
@ -523,3 +521,107 @@ TEST_F(TApp, uint16Wrapper) {
EXPECT_THROW(run(), CLI::ConversionError);
}
template <class T> class SimpleWrapper {
public:
SimpleWrapper() : val_{} {};
explicit SimpleWrapper(const T &initial) : val_{initial} {};
T &getRef() { return val_; };
using value_type = T;
private:
T val_;
};
TEST_F(TApp, wrapperInt) {
SimpleWrapper<int> wrap;
app.add_option("--val", wrap);
args = {"--val", "2"};
run();
EXPECT_EQ(wrap.getRef(), 2);
}
TEST_F(TApp, wrapperString) {
SimpleWrapper<std::string> wrap;
app.add_option("--val", wrap);
args = {"--val", "str"};
run();
EXPECT_EQ(wrap.getRef(), "str");
}
TEST_F(TApp, wrapperVector) {
SimpleWrapper<std::vector<int>> wrap;
app.add_option("--val", wrap);
args = {"--val", "1", "2", "3", "4"};
run();
auto v1 = wrap.getRef();
auto v2 = std::vector<int>{1, 2, 3, 4};
EXPECT_EQ(v1, v2);
}
TEST_F(TApp, wrapperwrapperString) {
SimpleWrapper<SimpleWrapper<std::string>> wrap;
app.add_option("--val", wrap);
args = {"--val", "arg"};
run();
auto v1 = wrap.getRef().getRef();
auto v2 = "arg";
EXPECT_EQ(v1, v2);
}
TEST_F(TApp, wrapperwrapperVector) {
SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap;
auto opt = app.add_option("--val", wrap);
args = {"--val", "1", "2", "3", "4"};
run();
auto v1 = wrap.getRef().getRef();
auto v2 = std::vector<int>{1, 2, 3, 4};
EXPECT_EQ(v1, v2);
opt->type_size(0, 5);
args = {"--val"};
run();
EXPECT_TRUE(wrap.getRef().getRef().empty());
args = {"--val", "happy", "sad"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, wrapperComplex) {
SimpleWrapper<std::complex<double>> wrap;
app.add_option("--val", wrap);
args = {"--val", "1", "2"};
run();
auto &v1 = wrap.getRef();
auto v2 = std::complex<double>{1, 2};
EXPECT_EQ(v1.real(), v2.real());
EXPECT_EQ(v1.imag(), v2.imag());
args = {"--val", "1.4-4j"};
run();
v2 = std::complex<double>{1.4, -4};
EXPECT_EQ(v1.real(), v2.real());
EXPECT_EQ(v1.imag(), v2.imag());
}
TEST_F(TApp, vectorComplex) {
std::vector<std::complex<double>> vcomplex;
app.add_option("--val", vcomplex);
args = {"--val", "1", "2", "--val", "1.4-4j"};
run();
ASSERT_EQ(vcomplex.size(), 2U);
EXPECT_EQ(vcomplex[0].real(), 1.0);
EXPECT_EQ(vcomplex[0].imag(), 2.0);
EXPECT_EQ(vcomplex[1].real(), 1.4);
EXPECT_EQ(vcomplex[1].imag(), -4.0);
}

834
tests/OptionTypeTest.cpp Normal file
View File

@ -0,0 +1,834 @@
#include "app_helper.hpp"
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "gmock/gmock.h"
TEST_F(TApp, OneStringAgain) {
std::string str;
app.add_option("-s,--string", str);
args = {"--string", "mystring"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--string"));
EXPECT_EQ(str, "mystring");
}
TEST_F(TApp, OneStringFunction) {
std::string str;
app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; });
args = {"--string", "mystring"};
run();
EXPECT_EQ(1u, app.count("-s"));
EXPECT_EQ(1u, app.count("--string"));
EXPECT_EQ(str, "mystring");
}
TEST_F(TApp, doubleFunction) {
double res{0.0};
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "-354.356"};
run();
EXPECT_EQ(res, 300.356);
// get the original value as entered as an integer
EXPECT_EQ(app["--val"]->as<float>(), -354.356f);
}
TEST_F(TApp, doubleFunctionFail) {
double res;
app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
args = {"--val", "not_double"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, doubleVectorFunction) {
std::vector<double> res;
app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
EXPECT_EQ(res.size(), 3u);
EXPECT_EQ(res[0], 10.0);
EXPECT_EQ(res[2], 12.0);
}
TEST_F(TApp, doubleVectorFunctionFail) {
std::vector<double> res;
std::string vstring = "--val";
app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "five", "--val", "nine", "--val", "7"};
EXPECT_THROW(run(), CLI::ConversionError);
// check that getting the results through the results function generates the same error
EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError);
auto strvec = app[vstring]->as<std::vector<std::string>>();
EXPECT_EQ(strvec.size(), 3u);
}
TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) {
std::vector<double> res;
auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
res = val;
std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
});
args = {"--val", "5", "--val", "6", "--val", "7"};
run();
EXPECT_EQ(res.size(), 3u);
EXPECT_EQ(res[0], 10.0);
EXPECT_EQ(res[2], 12.0);
EXPECT_FALSE(opt->get_run_callback_for_default());
opt->run_callback_for_default();
opt->default_val(std::vector<int>{2, 1, -2});
EXPECT_EQ(res[0], 7.0);
EXPECT_EQ(res[2], 3.0);
EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError);
auto vec = opt->as<std::vector<double>>();
ASSERT_EQ(vec.size(), 3U);
EXPECT_EQ(vec[0], 5.0);
EXPECT_EQ(vec[2], 7.0);
opt->check(CLI::Number);
opt->run_callback_for_default(false);
EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError);
}
TEST_F(TApp, BoolAndIntFlags) {
bool bflag{false};
int iflag{0};
unsigned int uflag{0};
app.add_flag("-b", bflag);
app.add_flag("-i", iflag);
app.add_flag("-u", uflag);
args = {"-b", "-i", "-u"};
run();
EXPECT_TRUE(bflag);
EXPECT_EQ(1, iflag);
EXPECT_EQ((unsigned int)1, uflag);
args = {"-b", "-b"};
ASSERT_NO_THROW(run());
EXPECT_TRUE(bflag);
bflag = false;
args = {"-iiiuu"};
run();
EXPECT_FALSE(bflag);
EXPECT_EQ(3, iflag);
EXPECT_EQ((unsigned int)2, uflag);
}
TEST_F(TApp, BoolOption) {
bool bflag{false};
app.add_option("-b", bflag);
args = {"-b", "false"};
run();
EXPECT_FALSE(bflag);
args = {"-b", "1"};
run();
EXPECT_TRUE(bflag);
args = {"-b", "-7"};
run();
EXPECT_FALSE(bflag);
// cause an out of bounds error internally
args = {"-b", "751615654161688126132138844896646748852"};
run();
EXPECT_TRUE(bflag);
args = {"-b", "-751615654161688126132138844896646748852"};
run();
EXPECT_FALSE(bflag);
}
TEST_F(TApp, vectorDefaults) {
std::vector<int> vals{4, 5};
auto opt = app.add_option("--long", vals, "", true);
args = {"--long", "[1,2,3]"};
run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
args.clear();
run();
auto res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({4, 5}));
app.clear();
opt->expected(1)->take_last();
res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({5}));
opt->take_first();
res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({4}));
opt->expected(0, 1)->take_last();
run();
EXPECT_EQ(res, std::vector<int>({4}));
res = app["--long"]->as<std::vector<int>>();
EXPECT_EQ(res, std::vector<int>({5}));
}
TEST_F(TApp, CallbackBoolFlags) {
bool value{false};
auto func = [&value]() { value = true; };
auto cback = app.add_flag_callback("--val", func);
args = {"--val"};
run();
EXPECT_TRUE(value);
value = false;
args = {"--val=false"};
run();
EXPECT_FALSE(value);
EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"--val", "--val=false"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
}
TEST_F(TApp, pair_check) {
std::string myfile{"pair_check_file.txt"};
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
EXPECT_TRUE(ok);
EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
std::pair<std::string, int> findex;
auto v0 = CLI::ExistingFile;
v0.application_index(0);
auto v1 = CLI::PositiveNumber;
v1.application_index(1);
app.add_option("--file", findex)->check(v0)->check(v1);
args = {"--file", myfile, "2"};
EXPECT_NO_THROW(run());
EXPECT_EQ(findex.first, myfile);
EXPECT_EQ(findex.second, 2);
args = {"--file", myfile, "-3"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--file", myfile, "2"};
std::remove(myfile.c_str());
EXPECT_THROW(run(), CLI::ValidationError);
}
// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
TEST_F(TApp, pair_check_take_first) {
std::string myfile{"pair_check_file2.txt"};
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
EXPECT_TRUE(ok);
EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
std::pair<std::string, int> findex;
auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound);
opt->get_validator(0)->application_index(0);
opt->get_validator(1)->application_index(1);
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
// should only check the last one
EXPECT_NO_THROW(run());
EXPECT_EQ(findex.first, myfile);
EXPECT_EQ(findex.second, 2);
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, VectorFixedString) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3);
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, VectorDefaultedFixedString) {
std::vector<std::string> strvec{"one"};
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
TEST_F(TApp, VectorIndexedValidator) {
std::vector<int> vvec;
CLI::Option *opt = app.add_option("-v", vvec);
args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
run();
EXPECT_EQ(4u, app.count("-v"));
EXPECT_EQ(4u, vvec.size());
opt->check(CLI::PositiveNumber.application_index(0));
opt->check((!CLI::PositiveNumber).application_index(1));
EXPECT_NO_THROW(run());
EXPECT_EQ(4u, vvec.size());
// v[3] would be negative
opt->check(CLI::PositiveNumber.application_index(3));
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, VectorUnlimString) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec);
EXPECT_EQ(1, opt->get_expected());
EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
args = {"-s", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
}
// From https://github.com/CLIUtils/CLI11/issues/420
TEST_F(TApp, stringLikeTests) {
struct nType {
explicit nType(const std::string &a_value) : m_value{a_value} {}
explicit operator std::string() const { return std::string{"op str"}; }
std::string m_value;
};
nType m_type{"abc"};
app.add_option("--type", m_type, "type")->capture_default_str();
run();
EXPECT_EQ(app["--type"]->as<std::string>(), "op str");
args = {"--type", "bca"};
run();
EXPECT_EQ(std::string(m_type), "op str");
EXPECT_EQ(m_type.m_value, "bca");
}
TEST_F(TApp, VectorExpectedRange) {
std::vector<std::string> strvec;
CLI::Option *opt = app.add_option("--string", strvec);
opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
args = {"--string", "mystring"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"};
EXPECT_THROW(run(), CLI::ArgumentMismatch);
EXPECT_EQ(opt->get_expected_max(), 4);
EXPECT_EQ(opt->get_expected_min(), 2);
opt->expected(4, 2); // just test the handling of reversed arguments
EXPECT_EQ(opt->get_expected_max(), 4);
EXPECT_EQ(opt->get_expected_min(), 2);
opt->expected(-5);
EXPECT_EQ(opt->get_expected_max(), 5);
EXPECT_EQ(opt->get_expected_min(), 5);
opt->expected(-5, 7);
EXPECT_EQ(opt->get_expected_max(), 7);
EXPECT_EQ(opt->get_expected_min(), 5);
}
TEST_F(TApp, VectorFancyOpts) {
std::vector<std::string> strvec;
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3);
EXPECT_EQ(3, opt->get_expected());
args = {"--string", "mystring", "mystring2", "mystring3"};
run();
EXPECT_EQ(3u, app.count("--string"));
EXPECT_EQ(answer, strvec);
args = {"one", "two"};
EXPECT_THROW(run(), CLI::RequiredError);
EXPECT_THROW(run(), CLI::ParseError);
}
// #87
TEST_F(TApp, CustomDoubleOption) {
std::pair<int, double> custom_opt;
auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
return true;
});
opt->type_name("INT FLOAT")->type_size(2);
args = {"12", "1.5"};
run();
EXPECT_EQ(custom_opt.first, 12);
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
}
// now with tuple support this is possible
TEST_F(TApp, CustomDoubleOptionAlt) {
std::pair<int, double> custom_opt;
app.add_option("posit", custom_opt);
args = {"12", "1.5"};
run();
EXPECT_EQ(custom_opt.first, 12);
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorPair) {
std::vector<std::pair<int, std::string>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
args = {"--dict", "1", "str1", "--dict", "3", "str3"};
run();
ASSERT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].first, 1);
EXPECT_EQ(custom_opt[1].second, "str3");
args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"};
run();
ASSERT_EQ(custom_opt.size(), 3u);
EXPECT_EQ(custom_opt[2].first, -1);
EXPECT_EQ(custom_opt[2].second, "str4");
opt->check(CLI::PositiveNumber.application_index(0));
EXPECT_THROW(run(), CLI::ValidationError);
}
TEST_F(TApp, vectorPairFail) {
std::vector<std::pair<int, std::string>> custom_opt;
app.add_option("--dict", custom_opt);
args = {"--dict", "1", "str1", "--dict", "str3", "1"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, vectorPairTypeRange) {
std::vector<std::pair<int, std::string>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
opt->type_size(2, 1); // just test switched arguments
EXPECT_EQ(opt->get_type_size_min(), 1);
EXPECT_EQ(opt->get_type_size_max(), 2);
args = {"--dict", "1", "str1", "--dict", "3", "str3"};
run();
ASSERT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].first, 1);
EXPECT_EQ(custom_opt[1].second, "str3");
args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
run();
ASSERT_EQ(custom_opt.size(), 3u);
EXPECT_TRUE(custom_opt[1].second.empty());
EXPECT_EQ(custom_opt[2].first, -1);
EXPECT_EQ(custom_opt[2].second, "str4");
opt->type_size(-2, -1); // test negative arguments
EXPECT_EQ(opt->get_type_size_min(), 1);
EXPECT_EQ(opt->get_type_size_max(), 2);
// this type size spec should run exactly as before
run();
ASSERT_EQ(custom_opt.size(), 3u);
EXPECT_TRUE(custom_opt[1].second.empty());
EXPECT_EQ(custom_opt[2].first, -1);
EXPECT_EQ(custom_opt[2].second, "str4");
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorTuple) {
std::vector<std::tuple<int, std::string, double>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
run();
ASSERT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(std::get<0>(custom_opt[0]), 1);
EXPECT_EQ(std::get<1>(custom_opt[1]), "str3");
EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7);
args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
run();
ASSERT_EQ(custom_opt.size(), 3u);
EXPECT_EQ(std::get<0>(custom_opt[2]), -1);
EXPECT_EQ(std::get<1>(custom_opt[2]), "str4");
EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87);
opt->check(CLI::PositiveNumber.application_index(0));
EXPECT_THROW(run(), CLI::ValidationError);
args.back() = "haha";
args[9] = "45";
EXPECT_THROW(run(), CLI::ConversionError);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorVector) {
std::vector<std::vector<int>> custom_opt;
auto opt = app.add_option("--dict", custom_opt);
args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
run();
ASSERT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].size(), 3u);
EXPECT_EQ(custom_opt[1].size(), 2u);
args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
"3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
run();
ASSERT_EQ(custom_opt.size(), 4u);
EXPECT_EQ(custom_opt[0].size(), 3u);
EXPECT_EQ(custom_opt[1].size(), 2u);
EXPECT_EQ(custom_opt[2].size(), 1u);
EXPECT_EQ(custom_opt[3].size(), 10u);
opt->check(CLI::PositiveNumber.application_index(9));
EXPECT_THROW(run(), CLI::ValidationError);
args.pop_back();
EXPECT_NO_THROW(run());
args.back() = "haha";
EXPECT_THROW(run(), CLI::ConversionError);
args = {"--dict", "1", "2", "4", "%%", "3", "1", "%%", "3", "%%", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3"};
run();
ASSERT_EQ(custom_opt.size(), 4u);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, vectorVectorFixedSize) {
std::vector<std::vector<int>> custom_opt;
auto opt = app.add_option("--dict", custom_opt)->type_size(4);
args = {"--dict", "1", "2", "4", "3", "--dict", "3", "1", "2", "8"};
run();
ASSERT_EQ(custom_opt.size(), 2u);
EXPECT_EQ(custom_opt[0].size(), 4u);
EXPECT_EQ(custom_opt[1].size(), 4u);
args = {"--dict", "1", "2", "4", "--dict", "3", "1", "7", "6"};
EXPECT_THROW(run(), CLI::ConversionError);
// this should reset it
opt->type_size(CLI::detail::expected_max_vector_size);
opt->type_size(1, CLI::detail::expected_max_vector_size);
EXPECT_NO_THROW(run());
ASSERT_EQ(custom_opt.size(), 2U);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, tuplePair) {
std::tuple<std::pair<int, double>> custom_opt;
app.add_option("--pr", custom_opt);
args = {"--pr", "1", "2"};
run();
EXPECT_EQ(std::get<0>(custom_opt).first, 1);
EXPECT_EQ(std::get<0>(custom_opt).second, 2.0);
}
// now with independent type sizes and expected this is possible
TEST_F(TApp, tupleintPair) {
std::tuple<int, std::pair<int, double>> custom_opt;
app.add_option("--pr", custom_opt);
args = {"--pr", "3", "1", "2"};
run();
EXPECT_EQ(std::get<0>(custom_opt), 3);
EXPECT_EQ(std::get<1>(custom_opt).first, 1);
EXPECT_EQ(std::get<1>(custom_opt).second, 2.0);
}
static_assert(CLI::detail::is_mutable_container<std::set<std::string>>::value, "set should be a container");
static_assert(CLI::detail::is_mutable_container<std::map<std::string, std::string>>::value,
"map should be a container");
static_assert(CLI::detail::is_mutable_container<std::unordered_map<std::string, double>>::value,
"unordered_map should be a container");
static_assert(CLI::detail::is_mutable_container<std::list<std::pair<int, std::string>>>::value,
"list should be a container");
static_assert(CLI::detail::type_count<std::set<std::string>>::value == 1, "set should have a type size of 1");
static_assert(CLI::detail::type_count<std::set<std::tuple<std::string, int, int>>>::value == 3,
"tuple set should have size of 3");
static_assert(CLI::detail::type_count<std::map<std::string, std::string>>::value == 2,
"map should have a type size of 2");
static_assert(CLI::detail::type_count<std::unordered_map<std::string, double>>::value == 2,
"unordered_map should have a type size of 2");
static_assert(CLI::detail::type_count<std::list<std::pair<int, std::string>>>::value == 2,
"list<int,string> should have a type size of 2");
static_assert(CLI::detail::type_count<std::map<std::string, std::pair<int, std::string>>>::value == 3,
"map<string,pair<int,string>> should have a type size of 3");
template <class T> class TApp_container_single : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_single() : TApp(){};
};
using containerTypes_single =
::testing::Types<std::vector<int>, std::deque<int>, std::set<int>, std::list<int>, std::unordered_set<int>>;
TYPED_TEST_SUITE(TApp_container_single, containerTypes_single, );
TYPED_TEST(TApp_container_single, containerInt) {
auto &cv = TApp_container_single<TypeParam>::cval;
CLI::Option *opt = (TApp::app).add_option("-v", cv);
TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
TApp::run();
EXPECT_EQ(4u, (TApp::app).count("-v"));
EXPECT_EQ(4u, cv.size());
opt->check(CLI::PositiveNumber.application_index(0));
opt->check((!CLI::PositiveNumber).application_index(1));
EXPECT_NO_THROW(TApp::run());
EXPECT_EQ(4u, cv.size());
// v[3] would be negative
opt->check(CLI::PositiveNumber.application_index(3));
EXPECT_THROW(TApp::run(), CLI::ValidationError);
}
template <class T> class TApp_container_pair : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_pair() : TApp(){};
};
using isp = std::pair<int, std::string>;
using containerTypes_pair = ::testing::Types<std::vector<isp>,
std::deque<isp>,
std::set<isp>,
std::list<isp>,
std::map<int, std::string>,
std::unordered_map<int, std::string>>;
TYPED_TEST_SUITE(TApp_container_pair, containerTypes_pair, );
TYPED_TEST(TApp_container_pair, containerPair) {
auto &cv = TApp_container_pair<TypeParam>::cval;
(TApp::app).add_option("--dict", cv);
TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"};
TApp::run();
EXPECT_EQ(cv.size(), 2u);
TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
TApp::run();
EXPECT_EQ(cv.size(), 3u);
}
template <class T> class TApp_container_tuple : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_tuple() : TApp(){};
};
using tup_obj = std::tuple<int, std::string, double>;
using containerTypes_tuple = ::testing::Types<std::vector<tup_obj>,
std::deque<tup_obj>,
std::set<tup_obj>,
std::list<tup_obj>,
std::map<int, std::pair<std::string, double>>,
std::unordered_map<int, std::tuple<std::string, double>>>;
TYPED_TEST_SUITE(TApp_container_tuple, containerTypes_tuple, );
TYPED_TEST(TApp_container_tuple, containerTuple) {
auto &cv = TApp_container_tuple<TypeParam>::cval;
(TApp::app).add_option("--dict", cv);
TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
TApp::run();
EXPECT_EQ(cv.size(), 2u);
TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
TApp::run();
EXPECT_EQ(cv.size(), 3u);
}
using icontainer1 = std::vector<int>;
using icontainer2 = std::list<int>;
using icontainer3 = std::set<int>;
using icontainer4 = std::pair<int, std::vector<int>>;
using containerTypes_container = ::testing::Types<std::vector<icontainer1>,
std::list<icontainer1>,
std::set<icontainer1>,
std::deque<icontainer1>,
std::vector<icontainer2>,
std::list<icontainer2>,
std::set<icontainer2>,
std::deque<icontainer2>,
std::vector<icontainer3>,
std::list<icontainer3>,
std::set<icontainer3>,
std::deque<icontainer3>>;
template <class T> class TApp_container_container : public TApp {
public:
using container_type = T;
container_type cval{};
TApp_container_container() : TApp(){};
};
TYPED_TEST_SUITE(TApp_container_container, containerTypes_container, );
TYPED_TEST(TApp_container_container, containerContainer) {
auto &cv = TApp_container_container<TypeParam>::cval;
(TApp::app).add_option("--dict", cv);
TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
TApp::run();
EXPECT_EQ(cv.size(), 2u);
TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
"3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
TApp::run();
EXPECT_EQ(cv.size(), 4u);
}
TEST_F(TApp, containerContainer) {
std::vector<icontainer4> cv;
app.add_option("--dict", cv);
args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
run();
EXPECT_EQ(cv.size(), 2u);
args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "", "--dict",
"3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
run();
EXPECT_EQ(cv.size(), 4u);
}
TEST_F(TApp, unknownContainerWrapper) {
class vopt {
public:
vopt() = default;
vopt(const std::vector<double> &vdub) : val_{vdub} {};
std::vector<double> val_{};
};
vopt cv;
app.add_option<vopt, std::vector<double>>("--vv", cv);
args = {"--vv", "1", "2", "4"};
run();
EXPECT_EQ(cv.val_.size(), 3u);
args = {"--vv", ""};
run();
EXPECT_TRUE(cv.val_.empty());
}
TEST_F(TApp, tupleTwoVectors) {
std::tuple<std::vector<int>, std::vector<int>> cv;
app.add_option("--vv", cv);
args = {"--vv", "1", "2", "4"};
run();
EXPECT_EQ(std::get<0>(cv).size(), 3U);
EXPECT_TRUE(std::get<1>(cv).empty());
args = {"--vv", "1", "2", "%%", "4", "4", "5"};
run();
EXPECT_EQ(std::get<0>(cv).size(), 2U);
EXPECT_EQ(std::get<1>(cv).size(), 3U);
}

View File

@ -1,3 +1,4 @@
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <iostream>
@ -72,6 +73,44 @@ TEST_F(TApp, StdOptionalTest) {
EXPECT_EQ(*opt, 3);
}
TEST_F(TApp, StdOptionalVectorEmptyDirect) {
std::optional<std::vector<int>> opt;
app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
// app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
run();
EXPECT_FALSE(opt);
args = {"-v"};
opt = std::vector<int>{4, 3};
run();
EXPECT_FALSE(opt);
args = {"-v", "1", "4", "5"};
run();
EXPECT_TRUE(opt);
std::vector<int> expV{1, 4, 5};
EXPECT_EQ(*opt, expV);
}
TEST_F(TApp, StdOptionalComplexDirect) {
std::optional<std::complex<double>> opt;
app.add_option("-c,--complex", opt)->type_size(0, 2);
run();
EXPECT_FALSE(opt);
args = {"-c"};
opt = std::complex<double>{4.0, 3.0};
run();
EXPECT_FALSE(opt);
args = {"-c", "1+2j"};
run();
EXPECT_TRUE(opt);
std::complex<double> val{1, 2};
EXPECT_EQ(*opt, val);
args = {"-c", "3", "-4"};
run();
EXPECT_TRUE(opt);
std::complex<double> val2{3, -4};
EXPECT_EQ(*opt, val2);
}
#ifdef _MSC_VER
#pragma warning(default : 4244)
#endif
@ -165,11 +204,16 @@ TEST_F(TApp, BoostOptionalStringTest) {
EXPECT_TRUE(opt);
EXPECT_EQ(*opt, "strv");
}
namespace boost {
using CLI::enums::operator<<;
}
TEST_F(TApp, BoostOptionalEnumTest) {
enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 };
boost::optional<eval> opt;
boost::optional<eval> opt, opt2;
auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
app.add_option_no_stream("-e,--eval", opt2);
optptr->capture_default_str();
auto dstring = optptr->get_default_str();
@ -206,6 +250,7 @@ TEST_F(TApp, BoostOptionalVector) {
TEST_F(TApp, BoostOptionalVectorEmpty) {
boost::optional<std::vector<int>> opt;
app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
// app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
run();
EXPECT_FALSE(opt);
args = {"-v"};
@ -219,6 +264,44 @@ TEST_F(TApp, BoostOptionalVectorEmpty) {
EXPECT_EQ(*opt, expV);
}
TEST_F(TApp, BoostOptionalVectorEmptyDirect) {
boost::optional<std::vector<int>> opt;
app.add_option_no_stream("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
// app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
run();
EXPECT_FALSE(opt);
args = {"-v"};
opt = std::vector<int>{4, 3};
run();
EXPECT_FALSE(opt);
args = {"-v", "1", "4", "5"};
run();
EXPECT_TRUE(opt);
std::vector<int> expV{1, 4, 5};
EXPECT_EQ(*opt, expV);
}
TEST_F(TApp, BoostOptionalComplexDirect) {
boost::optional<std::complex<double>> opt;
app.add_option("-c,--complex", opt)->type_size(0, 2);
run();
EXPECT_FALSE(opt);
args = {"-c"};
opt = std::complex<double>{4.0, 3.0};
run();
EXPECT_FALSE(opt);
args = {"-c", "1+2j"};
run();
EXPECT_TRUE(opt);
std::complex<double> val{1, 2};
EXPECT_EQ(*opt, val);
args = {"-c", "3", "-4"};
run();
EXPECT_TRUE(opt);
std::complex<double> val2{3, -4};
EXPECT_EQ(*opt, val2);
}
#endif
#if !CLI11_OPTIONAL

View File

@ -1,7 +1,7 @@
#include "app_helper.hpp"
/// This allows a set of strings to be run over by a test
struct TApp_TBO : public TApp, public ::testing::WithParamInterface<const char *> {};
struct TApp_TBO : public TApp_base, testing::TestWithParam<const char *> {};
TEST_P(TApp_TBO, TrueBoolOption) {
bool value{false}; // Not used, but set just in case
@ -13,10 +13,10 @@ TEST_P(TApp_TBO, TrueBoolOption) {
}
// Change to INSTANTIATE_TEST_SUITE_P in GTest master
INSTANTIATE_TEST_CASE_P(TrueBoolOptions, TApp_TBO, ::testing::Values("true", "on", "True", "ON"), );
INSTANTIATE_TEST_SUITE_P(TrueBoolOptions_test, TApp_TBO, testing::Values("true", "on", "True", "ON"));
/// This allows a set of strings to be run over by a test
struct TApp_FBO : public TApp, public ::testing::WithParamInterface<const char *> {};
struct TApp_FBO : public TApp_base, public ::testing::TestWithParam<const char *> {};
TEST_P(TApp_FBO, FalseBoolOptions) {
bool value{true}; // Not used, but set just in case
@ -27,4 +27,4 @@ TEST_P(TApp_FBO, FalseBoolOptions) {
EXPECT_FALSE(value);
}
INSTANTIATE_TEST_CASE_P(FalseBoolOptions, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"), );
INSTANTIATE_TEST_SUITE_P(FalseBoolOptions_test, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"));

View File

@ -11,10 +11,11 @@
using input_t = std::vector<std::string>;
struct TApp : public ::testing::Test {
class TApp_base {
public:
CLI::App app{"My Test Program"};
input_t args{};
virtual ~TApp_base() = default;
void run() {
// It is okay to re-parse - clear is called automatically before a parse.
input_t newargs = args;
@ -23,6 +24,8 @@ struct TApp : public ::testing::Test {
}
};
class TApp : public TApp_base, public ::testing::Test {};
class TempFile {
std::string _name{};