mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-01-15 22:58:02 +00:00
Compare commits
2 Commits
967bfe0e02
...
27da2f952e
Author | SHA1 | Date | |
---|---|---|---|
|
27da2f952e | ||
|
f346f29802 |
17
README.md
17
README.md
@ -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.
|
||||
@ -714,7 +715,7 @@ sub.subcommand = true
|
||||
Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. 🆕 Subcommands can be triggered from configuration files if the `configurable` flag was set on the subcommand. Then the use of `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line.
|
||||
|
||||
To print a configuration file from the passed
|
||||
arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details.
|
||||
arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details.
|
||||
|
||||
### Inheriting defaults
|
||||
|
||||
|
@ -80,7 +80,27 @@ The main differences are in vector notation and comment character. Note: CLI11
|
||||
|
||||
## Writing out a configure file
|
||||
|
||||
To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions.
|
||||
To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include option descriptions and the App description
|
||||
|
||||
```cpp
|
||||
|
||||
CLI::App app;
|
||||
app.add_option(...);
|
||||
// several other options
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
//the config printout should be after the parse to capture the given arguments
|
||||
std::cout<<app.config_to_str(true,true);
|
||||
```
|
||||
|
||||
if a prefix is needed to print before the options, for example to print a config for just a subcommand, the config formatter can be obtained directly.
|
||||
|
||||
```cpp
|
||||
|
||||
auto fmtr=app.get_config_formatter();
|
||||
//std::string to_config(const App *app, bool default_also, bool write_description, std::string prefix)
|
||||
fmtr->to_config(&app,true,true,"sub.");
|
||||
//prefix can be used to set a prefix before each argument, like "sub."
|
||||
```
|
||||
|
||||
### Customization of configure file output
|
||||
The default config parser/generator has some customization points that allow variations on the TOML format. The default formatter has a base configuration that matches the TOML format. It defines 5 characters that define how different aspects of the configuration are handled
|
||||
@ -115,14 +135,10 @@ The default configuration file will read INI files, but will write out files in
|
||||
```cpp
|
||||
app.config_formatter(std::make_shared<CLI::ConfigINI>());
|
||||
```
|
||||
which makes use of a predefined modification of the ConfigBase class which TOML also uses.
|
||||
which makes use of a predefined modification of the ConfigBase class which TOML also uses. If a custom formatter is used that is not inheriting from the from ConfigBase class `get_config_formatter_base() will return a nullptr, so some care must be exercised in its us with custom configurations.
|
||||
|
||||
## Custom formats
|
||||
|
||||
{% hint style='info' %}
|
||||
New in CLI11 1.6
|
||||
{% endhint %}
|
||||
|
||||
You can invent a custom format and set that instead of the default INI formatter. You need to inherit from `CLI::Config` and implement the following two functions:
|
||||
|
||||
```cpp
|
||||
@ -145,3 +161,11 @@ See [`examples/json.cpp`](https://github.com/CLIUtils/CLI11/blob/master/examples
|
||||
Configuration files can be used to trigger subcommands if a subcommand is set to configure. By default configuration file just set the default values of a subcommand. But if the `configure()` option is set on a subcommand then the if the subcommand is utilized via a `[subname]` block in the configuration file it will act as if it were called from the command line. Subsubcommands can be triggered via [subname.subsubname]. Using the `[[subname]]` will be as if the subcommand were triggered multiple times from the command line. This functionality can allow the configuration file to act as a scripting file.
|
||||
|
||||
For custom configuration files this behavior can be triggered by specifying the parent subcommands in the structure and `++` as the name to open a new subcommand scope and `--` to close it. These names trigger the different callbacks of configurable subcommands.
|
||||
|
||||
## Implementation Notes
|
||||
The config file input works with any form of the option given: Long, short, positional, or the environment variable name. When generating a config file it will create a name in following priority.
|
||||
|
||||
1. First long name
|
||||
1. Positional name
|
||||
1. First short name
|
||||
1. Environment name
|
||||
|
@ -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
2
extern/googletest
vendored
@ -1 +1 @@
|
||||
Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2
|
||||
Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e
|
@ -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,
|
||||
@ -1491,7 +1509,7 @@ class App {
|
||||
return this;
|
||||
}
|
||||
/// Produce a string that could be read in as a config of the current values of the App. Set default_also to
|
||||
/// include default arguments. Prefix will add a string to the beginning of each option.
|
||||
/// include default arguments. write_descriptions will print a description for the App and for each option.
|
||||
std::string config_to_str(bool default_also = false, bool write_description = false) const {
|
||||
return config_formatter_->to_config(this, default_also, write_description, "");
|
||||
}
|
||||
@ -2335,6 +2353,14 @@ class App {
|
||||
return true;
|
||||
}
|
||||
Option *op = get_option_no_throw("--" + item.name);
|
||||
if(op == nullptr) {
|
||||
if(item.name.size() == 1) {
|
||||
op = get_option_no_throw("-" + item.name);
|
||||
}
|
||||
}
|
||||
if(op == nullptr) {
|
||||
op = get_option_no_throw(item.name);
|
||||
}
|
||||
if(op == nullptr) {
|
||||
// If the option was not present
|
||||
if(get_allow_config_extras() == config_extras_mode::capture)
|
||||
@ -2652,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();
|
||||
|
||||
@ -2717,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{});
|
||||
}
|
||||
|
||||
|
@ -282,14 +282,14 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
||||
}
|
||||
for(const Option *opt : app->get_options({})) {
|
||||
|
||||
// Only process option with a long-name and configurable
|
||||
if(!opt->get_lnames().empty() && opt->get_configurable()) {
|
||||
// Only process options that are configurable
|
||||
if(opt->get_configurable()) {
|
||||
if(opt->get_group() != group) {
|
||||
if(!(group == "Options" && opt->get_group().empty())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::string name = prefix + opt->get_lnames()[0];
|
||||
std::string name = prefix + opt->get_single_name();
|
||||
std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd);
|
||||
|
||||
if(value.empty() && default_also) {
|
||||
|
@ -300,7 +300,7 @@ class Option : public OptionBase<Option> {
|
||||
/// @name Other
|
||||
///@{
|
||||
|
||||
/// Remember the parent app
|
||||
/// link back up to the parent App for fallthrough
|
||||
App *parent_{nullptr};
|
||||
|
||||
/// Options store a callback to do all the work
|
||||
@ -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_; }
|
||||
|
||||
@ -681,7 +686,19 @@ class Option : public OptionBase<Option> {
|
||||
|
||||
/// Get the flag names with specified default values
|
||||
const std::vector<std::string> &get_fnames() const { return fnames_; }
|
||||
|
||||
/// Get a single name for the option, first of lname, pname, sname, envname
|
||||
const std::string &get_single_name() const {
|
||||
if(!lnames_.empty()) {
|
||||
return lnames_[0];
|
||||
}
|
||||
if(!pname_.empty()) {
|
||||
return pname_;
|
||||
}
|
||||
if(!snames_.empty()) {
|
||||
return snames_[0];
|
||||
}
|
||||
return envname_;
|
||||
}
|
||||
/// The number of times the option expects to be included
|
||||
int get_expected() const { return expected_min_; }
|
||||
|
||||
@ -836,23 +853,33 @@ class Option : public OptionBase<Option> {
|
||||
bool operator==(const Option &other) const { return !matching_name(other).empty(); }
|
||||
|
||||
/// Check a name. Requires "-" or "--" for short / long, supports positional name
|
||||
bool check_name(std::string name) const {
|
||||
bool check_name(const std::string &name) const {
|
||||
|
||||
if(name.length() > 2 && name[0] == '-' && name[1] == '-')
|
||||
return check_lname(name.substr(2));
|
||||
if(name.length() > 1 && name.front() == '-')
|
||||
return check_sname(name.substr(1));
|
||||
if(!pname_.empty()) {
|
||||
std::string local_pname = pname_;
|
||||
std::string local_name = name;
|
||||
if(ignore_underscore_) {
|
||||
local_pname = detail::remove_underscore(local_pname);
|
||||
local_name = detail::remove_underscore(local_name);
|
||||
}
|
||||
if(ignore_case_) {
|
||||
local_pname = detail::to_lower(local_pname);
|
||||
local_name = detail::to_lower(local_name);
|
||||
}
|
||||
if(local_name == local_pname) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string local_pname = pname_;
|
||||
if(ignore_underscore_) {
|
||||
local_pname = detail::remove_underscore(local_pname);
|
||||
name = detail::remove_underscore(name);
|
||||
if(!envname_.empty()) {
|
||||
// this needs to be the original since envname_ shouldn't match on case insensitivity
|
||||
return (name == envname_);
|
||||
}
|
||||
if(ignore_case_) {
|
||||
local_pname = detail::to_lower(local_pname);
|
||||
name = detail::to_lower(name);
|
||||
}
|
||||
return name == local_pname;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Requires "-" to be removed from string
|
||||
@ -941,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 {
|
||||
@ -964,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_;
|
||||
@ -1032,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;
|
||||
@ -1057,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;
|
||||
@ -1135,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;
|
||||
}
|
||||
|
@ -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
@ -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);
|
||||
@ -2131,6 +1744,26 @@ TEST_F(TApp, Env) {
|
||||
EXPECT_THROW(run(), CLI::RequiredError);
|
||||
}
|
||||
|
||||
// curiously check if an environmental only option works
|
||||
TEST_F(TApp, EnvOnly) {
|
||||
|
||||
put_env("CLI11_TEST_ENV_TMP", "2");
|
||||
|
||||
int val{1};
|
||||
CLI::Option *vopt = app.add_option("", val)->envname("CLI11_TEST_ENV_TMP");
|
||||
|
||||
run();
|
||||
|
||||
EXPECT_EQ(2, val);
|
||||
EXPECT_EQ(1u, vopt->count());
|
||||
|
||||
vopt->required();
|
||||
run();
|
||||
|
||||
unset_env("CLI11_TEST_ENV_TMP");
|
||||
EXPECT_THROW(run(), CLI::RequiredError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, RangeInt) {
|
||||
int x{0};
|
||||
app.add_option("--one", x)->check(CLI::Range(3, 6));
|
||||
@ -2386,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;
|
||||
|
141
tests/BoostOptionTypeTest.cpp
Normal file
141
tests/BoostOptionTypeTest.cpp
Normal 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);
|
||||
}
|
@ -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
185
tests/ComplexTypeTest.cpp
Normal 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());
|
||||
}
|
@ -1207,6 +1207,57 @@ TEST_F(TApp, IniFlagDual) {
|
||||
EXPECT_THROW(run(), CLI::ConversionError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IniShort) {
|
||||
|
||||
TempFile tmpini{"TestIniTmp.ini"};
|
||||
|
||||
int key{0};
|
||||
app.add_option("--flag,-f", key);
|
||||
app.set_config("--config", tmpini);
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini};
|
||||
out << "f=3" << std::endl;
|
||||
}
|
||||
|
||||
ASSERT_NO_THROW(run());
|
||||
EXPECT_EQ(key, 3);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IniPositional) {
|
||||
|
||||
TempFile tmpini{"TestIniTmp.ini"};
|
||||
|
||||
int key{0};
|
||||
app.add_option("key", key);
|
||||
app.set_config("--config", tmpini);
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini};
|
||||
out << "key=3" << std::endl;
|
||||
}
|
||||
|
||||
ASSERT_NO_THROW(run());
|
||||
EXPECT_EQ(key, 3);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IniEnvironmental) {
|
||||
|
||||
TempFile tmpini{"TestIniTmp.ini"};
|
||||
|
||||
int key{0};
|
||||
app.add_option("key", key)->envname("CLI11_TEST_ENV_KEY_TMP");
|
||||
app.set_config("--config", tmpini);
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini};
|
||||
out << "CLI11_TEST_ENV_KEY_TMP=3" << std::endl;
|
||||
}
|
||||
|
||||
ASSERT_NO_THROW(run());
|
||||
EXPECT_EQ(key, 3);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IniFlagText) {
|
||||
|
||||
TempFile tmpini{"TestIniTmp.ini"};
|
||||
@ -1376,6 +1427,49 @@ TEST_F(TApp, TomlOutputSimple) {
|
||||
EXPECT_EQ("simple=3\n", str);
|
||||
}
|
||||
|
||||
TEST_F(TApp, TomlOutputShort) {
|
||||
|
||||
int v{0};
|
||||
app.add_option("-s", v);
|
||||
|
||||
args = {"-s3"};
|
||||
|
||||
run();
|
||||
|
||||
std::string str = app.config_to_str();
|
||||
EXPECT_EQ("s=3\n", str);
|
||||
}
|
||||
|
||||
TEST_F(TApp, TomlOutputPositional) {
|
||||
|
||||
int v{0};
|
||||
app.add_option("pos", v);
|
||||
|
||||
args = {"3"};
|
||||
|
||||
run();
|
||||
|
||||
std::string str = app.config_to_str();
|
||||
EXPECT_EQ("pos=3\n", str);
|
||||
}
|
||||
|
||||
// try the output with environmental only arguments
|
||||
TEST_F(TApp, TomlOutputEnvironmental) {
|
||||
|
||||
put_env("CLI11_TEST_ENV_TMP", "2");
|
||||
|
||||
int val{1};
|
||||
app.add_option(std::string{}, val)->envname("CLI11_TEST_ENV_TMP");
|
||||
|
||||
run();
|
||||
|
||||
EXPECT_EQ(2, val);
|
||||
std::string str = app.config_to_str();
|
||||
EXPECT_EQ("CLI11_TEST_ENV_TMP=2\n", str);
|
||||
|
||||
unset_env("CLI11_TEST_ENV_TMP");
|
||||
}
|
||||
|
||||
TEST_F(TApp, TomlOutputNoConfigurable) {
|
||||
|
||||
int v1{0}, v2{0};
|
||||
|
@ -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";
|
||||
|
@ -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
834
tests/OptionTypeTest.cpp
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -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"));
|
||||
|
@ -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{};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user