Commit 27da2f952ebc41c510d08b20142d837f6435f644
Committed by
GitHub
1 parent
f346f298
Container options (#423)
* Update options.md book chapter and the readme to better reflect current usage and the modifications to the add_options templates. add support in add_option for wrapper types, such as std::optional, boost::optional or other types with a value_type trait. Add support for generalized containers beyond vector, add support for nested tuples and vectors, and complex numbers directly in add_option. This includes several new type traits and object categories. Upgrade the google test version to better support templated tests. add support for vector argument separator `%%` * update formatting to match recent changes * Apply suggestions from code review Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
Showing
17 changed files
with
2342 additions
and
960 deletions
README.md
| ... | ... | @@ -195,14 +195,15 @@ While all options internally are the same type, there are several ways to add an |
| 195 | 195 | app.add_option(option_name, help_str="") |
| 196 | 196 | |
| 197 | 197 | app.add_option(option_name, |
| 198 | - 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 π. | |
| 198 | + 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. | |
| 199 | 199 | help_string="") |
| 200 | 200 | |
| 201 | 201 | app.add_option_function<type>(option_name, |
| 202 | 202 | function <void(const type &value)>, // type can be any type supported by add_option |
| 203 | 203 | help_string="") |
| 204 | 204 | |
| 205 | -app.add_complex(... // Special case: support for complex numbers | |
| 205 | +app.add_complex(... // Special case: support for complex numbers β οΈ. Complex numbers are now fully supported in the add_option so this function is redundant. | |
| 206 | + | |
| 206 | 207 | // π 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 |
| 207 | 208 | app.add_option<typename T, typename XC>(option_name, |
| 208 | 209 | T &output, // output must be assignable or constructible from a value of type XC |
| ... | ... | @@ -213,7 +214,7 @@ app.add_flag(option_name, |
| 213 | 214 | help_string="") |
| 214 | 215 | |
| 215 | 216 | app.add_flag(option_name, |
| 216 | - 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 | |
| 217 | + 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 | |
| 217 | 218 | help_string="") |
| 218 | 219 | |
| 219 | 220 | app.add_flag_function(option_name, |
| ... | ... | @@ -245,9 +246,9 @@ app.add_option<vtype,std:string>("--vs",v1); |
| 245 | 246 | app.add_option<vtype,int>("--vi",v1); |
| 246 | 247 | app.add_option<vtype,double>("--vf",v1); |
| 247 | 248 | ``` |
| 248 | -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. | |
| 249 | +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. | |
| 249 | 250 | |
| 250 | -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. | |
| 251 | +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. | |
| 251 | 252 | |
| 252 | 253 | Vector types can also be used in the two parameter template overload |
| 253 | 254 | ``` |
| ... | ... | @@ -308,7 +309,7 @@ Before parsing, you can set the following options: |
| 308 | 309 | - `->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. |
| 309 | 310 | - `->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. |
| 310 | 311 | - `->description(str)`: Set/change the description. |
| 311 | -- `->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). | |
| 312 | +- `->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). | |
| 312 | 313 | - `->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 |
| 313 | 314 | - `->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. |
| 314 | 315 | - `->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: |
| 319 | 320 | - `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. |
| 320 | 321 | - `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. |
| 321 | 322 | - `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. |
| 322 | -- `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). | |
| 323 | +- `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). | |
| 323 | 324 | |
| 324 | 325 | |
| 325 | 326 | 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. | ... | ... |
book/chapters/options.md
| ... | ... | @@ -9,7 +9,7 @@ int int_option{0}; |
| 9 | 9 | app.add_option("-i", int_option, "Optional description"); |
| 10 | 10 | ``` |
| 11 | 11 | |
| 12 | -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]. | |
| 12 | +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. | |
| 13 | 13 | |
| 14 | 14 | ```cpp |
| 15 | 15 | int int_option{0}; |
| ... | ... | @@ -20,11 +20,15 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi |
| 20 | 20 | |
| 21 | 21 | | Type | CLI11 | |
| 22 | 22 | |-------------|-------| |
| 23 | -| int-like | Integer conversion up to 64-bit, can be unsigned | | |
| 24 | -| float-like | Floating point conversions | | |
| 25 | -| string-like | Anything else that can be shifted into a StringStream | | |
| 26 | -| vector-like | A vector of the above three types (see below) | | |
| 23 | +| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number | | |
| 24 | +| string-like | std\::string, or anything that can be constructed from or assigned a std\::string | | |
| 25 | +| 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" | | |
| 26 | +| enumeration | any enum or enum class type is supported through conversion from the underlying type(typically int, though it can be specified otherwise) | | |
| 27 | +| container-like | a container(like vector) of any available types including other containers | | |
| 28 | +| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of type in this list | | |
| 29 | +| 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 | | |
| 27 | 30 | | 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. (`{}`) | |
| 31 | +| streamable | any other type with a `<<` operator will also work | | |
| 28 | 32 | |
| 29 | 33 | 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. |
| 30 | 34 | |
| ... | ... | @@ -46,15 +50,15 @@ To make a positional option, you simply give CLI11 one name that does not start |
| 46 | 50 | |
| 47 | 51 | This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional. |
| 48 | 52 | |
| 49 | -## Vectors of options | |
| 53 | +## Containers of options | |
| 50 | 54 | |
| 51 | -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] | |
| 55 | +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. | |
| 52 | 56 | |
| 53 | 57 | | Separate names | Combined names | |
| 54 | 58 | |-------------------|-----------------| |
| 55 | 59 | | `--vec 1 --vec 2` | `--vec 1 2` | |
| 56 | 60 | |
| 57 | -The original version did allow the option system to access information on the grouping of options received, but was removed for simplicity. | |
| 61 | +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. | |
| 58 | 62 | |
| 59 | 63 | An example of setting up a vector option: |
| 60 | 64 | |
| ... | ... | @@ -65,6 +69,43 @@ app.add_option("--vec", int_vec, "My vector option"); |
| 65 | 69 | |
| 66 | 70 | Vectors will be replaced by the parsed content if the option is given on the command line. |
| 67 | 71 | |
| 72 | +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. | |
| 73 | + | |
| 74 | +### containers of containers | |
| 75 | +Containers of containers are also supported. | |
| 76 | +```cpp | |
| 77 | +std::vector<std::vector<int>> int_vec; | |
| 78 | +app.add_option("--vec", int_vec, "My vector of vectors option"); | |
| 79 | +``` | |
| 80 | +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. | |
| 81 | +``` | |
| 82 | +cmd --vec_of_vec 1 2 3 4 %% 1 2 | |
| 83 | +``` | |
| 84 | +would then result in a container of size 2 with the first element containing 4 values and the second 2. | |
| 85 | + | |
| 86 | +This separator is also the only way to get values into something like | |
| 87 | +```cpp | |
| 88 | +std::pair<std::vector<int>,std::vector<int>> two_vecs; | |
| 89 | +app.add_option("--vec", two_vecs, "pair of vectors"); | |
| 90 | +``` | |
| 91 | +without calling the argument twice. | |
| 92 | + | |
| 93 | +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. | |
| 94 | + | |
| 95 | +### Nested types | |
| 96 | +Types can be nested For example | |
| 97 | +```cpp | |
| 98 | +std::map<int, std::pair<int,std::string>> map; | |
| 99 | +app.add_option("--dict", map, "map of pairs"); | |
| 100 | +``` | |
| 101 | + | |
| 102 | +will require 3 arguments for each invocation, and multiple sets of 3 arguments can be entered for a single invocation on the command line. | |
| 103 | + | |
| 104 | +```cpp | |
| 105 | +std::map<int, std::pair<int,std::vector<std::string>>> map; | |
| 106 | +app.add_option("--dict", map, "map of pairs"); | |
| 107 | +``` | |
| 108 | +will result in a requirement for 2 integers on each invocation and absorb an unlimited number of strings including 0. | |
| 68 | 109 | |
| 69 | 110 | ## Option modifiers |
| 70 | 111 | |
| ... | ... | @@ -75,25 +116,32 @@ When you call `add_option`, you get a pointer to the added option. You can use t |
| 75 | 116 | | `->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. | |
| 76 | 117 | | `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. | |
| 77 | 118 | | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. | |
| 119 | +| `->type_size(N)` | specify that each block of values would consist of N elements | | |
| 120 | +| `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements | | |
| 78 | 121 | | `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. | |
| 79 | 122 | | `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. | |
| 80 | 123 | | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. | |
| 81 | 124 | | `->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. | |
| 125 | +| `->description(string)` | Set/change the description | | |
| 82 | 126 | | `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). | |
| 83 | -| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7). | | |
| 127 | +| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments). | | |
| 84 | 128 | | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | |
| 85 | -| `->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. | | |
| 129 | +| `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` | | |
| 130 | +| `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' | | |
| 131 | +| `->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. | | |
| 86 | 132 | | `->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()`. | |
| 87 | 133 | | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | |
| 134 | +| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` | | |
| 88 | 135 | | `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. | |
| 89 | -| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. | | |
| 90 | -| `->check(CLI::ExistingFile)` | Requires that the file exists if given. | | |
| 91 | -| `->check(CLI::ExistingDirectory)` | Requires that the directory exists. | | |
| 92 | -| `->check(CLI::NonexistentPath)` | Requires that the path does not exist. | | |
| 93 | -| `->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. | | |
| 136 | +| `->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 | | |
| 137 | +| `->check(Validator)` | perform a check on the returned results to verify they meet some criteria. See [Validators](./validators.md) for more info | | |
| 138 | +| `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info | | |
| 94 | 139 | | `->each(void(std::string))` | Run a function on each parsed value, *in order*. | |
| 140 | +| `->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 | | |
| 141 | +| `->default_function(string())` | Advanced: Change the function that `capture_default_str()` uses. | | |
| 142 | +| `->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). | | |
| 95 | 143 | |
| 96 | -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. | |
| 144 | +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. | |
| 97 | 145 | |
| 98 | 146 | ## Using the `CLI::Option` pointer |
| 99 | 147 | |
| ... | ... | @@ -110,12 +158,17 @@ if(* opt) |
| 110 | 158 | |
| 111 | 159 | ## Inheritance of defaults |
| 112 | 160 | |
| 113 | -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: | |
| 161 | +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: | |
| 114 | 162 | |
| 115 | 163 | * `group`: The group name starts as "Options" |
| 116 | 164 | * `required`: If the option must be given. Defaults to `false`. Is ignored for flags. |
| 117 | 165 | * `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. |
| 118 | 166 | * `ignore_case`: Allow any mixture of cases for the option or flag name |
| 167 | +* `ignore_underscore`: Allow any number of underscores in the option or flag name | |
| 168 | +* `configurable`: Specify whether an option can be configured through a config file | |
| 169 | +* `disable_flag_override`: do not allow flag values to be overridden on the command line | |
| 170 | +* `always_capture_default`: specify that the default values should be automatically captured. | |
| 171 | +* `delimiter`: A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3") | |
| 119 | 172 | |
| 120 | 173 | An example of usage: |
| 121 | 174 | |
| ... | ... | @@ -129,29 +182,7 @@ app.get_group() // is "Required" |
| 129 | 182 | Groups are mostly for visual organization, but an empty string for a group name will hide the option. |
| 130 | 183 | |
| 131 | 184 | |
| 132 | -## Listing of specialty options: | |
| 133 | - | |
| 134 | -Besides `add_option` and `add_flag`, there are several special ways to create options for sets and complex numbers. | |
| 135 | - | |
| 136 | -### Sets | |
| 137 | - | |
| 138 | -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. | |
| 139 | - | |
| 140 | -```cpp | |
| 141 | -int val{0}; | |
| 142 | -app.add_set("--even", val, {0,2,4,6,8}); | |
| 143 | -``` | |
| 144 | - | |
| 145 | -### Complex numbers | |
| 146 | - | |
| 147 | -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". | |
| 148 | - | |
| 149 | -```cpp | |
| 150 | -std::complex<float> val{0.0F,0.0F}; | |
| 151 | -app.add_complex("--cplx", val); | |
| 152 | -``` | |
| 153 | - | |
| 154 | -### Windows style options (New in CLI11 1.7) | |
| 185 | +### Windows style options | |
| 155 | 186 | |
| 156 | 187 | 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: |
| 157 | 188 | |
| ... | ... | @@ -160,12 +191,13 @@ You can also set the app setting `app->allow_windows_style_options()` to allow w |
| 160 | 191 | * `/long` (long flag) |
| 161 | 192 | * `/file filename` (space) |
| 162 | 193 | * `/file:filename` (colon) |
| 194 | +* `/long_flag:false` (long flag with : to override the default value) | |
| 163 | 195 | |
| 164 | -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. | |
| 196 | +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. | |
| 165 | 197 | |
| 166 | 198 | ## Parse configuration |
| 167 | 199 | |
| 168 | -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. | |
| 200 | +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. | |
| 169 | 201 | |
| 170 | 202 | ### examples |
| 171 | 203 | How options manage this is best illustrated through some examples |
| ... | ... | @@ -173,7 +205,7 @@ How options manage this is best illustrated through some examples |
| 173 | 205 | std::string val; |
| 174 | 206 | app.add_option("--opt",val,"description"); |
| 175 | 207 | ``` |
| 176 | -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. | |
| 208 | +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. | |
| 177 | 209 | |
| 178 | 210 | now for example |
| 179 | 211 | ```cpp |
| ... | ... | @@ -203,6 +235,20 @@ app.add_flag("--opt",val,"description"); |
| 203 | 235 | |
| 204 | 236 | Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed. |
| 205 | 237 | |
| 238 | +```cpp | |
| 239 | +std::complex<double> val; | |
| 240 | +app.add_option("--opt",val,"description"); | |
| 241 | +``` | |
| 242 | + | |
| 243 | +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. | |
| 244 | + | |
| 245 | + | |
| 246 | +```cpp | |
| 247 | +std::vector<std::vector<int>> val; | |
| 248 | +app.add_option("--opt",val,"description"); | |
| 249 | +``` | |
| 250 | +has a type size of 1 to (1<<30). | |
| 251 | + | |
| 206 | 252 | ### Customization |
| 207 | 253 | |
| 208 | 254 | 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); |
| 214 | 260 | ``` |
| 215 | 261 | 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. |
| 216 | 262 | |
| 217 | -[^1]: For example, enums are not printable to `std::cout`. | |
| 218 | -[^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values. | |
| 263 | +There are some additional options that can be specified to modify an option for specific cases | |
| 264 | +- `->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. | |
| 265 | + | |
| 266 | +## Unusual circumstances | |
| 267 | +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. | |
| 268 | + | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -610,21 +610,39 @@ class App { |
| 610 | 610 | // to structs used in the evaluation can be temporary so that would cause issues. |
| 611 | 611 | auto Tcount = detail::type_count<AssignTo>::value; |
| 612 | 612 | auto XCcount = detail::type_count<ConvertTo>::value; |
| 613 | - opt->type_size((std::max)(Tcount, XCcount)); | |
| 613 | + opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount)); | |
| 614 | 614 | opt->expected(detail::expected_count<ConvertTo>::value); |
| 615 | 615 | opt->run_callback_for_default(); |
| 616 | 616 | return opt; |
| 617 | 617 | } |
| 618 | 618 | |
| 619 | + /// Add option for assigning to a variable | |
| 620 | + template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy> | |
| 621 | + Option *add_option_no_stream(std::string option_name, | |
| 622 | + AssignTo &variable, ///< The variable to set | |
| 623 | + std::string option_description = "") { | |
| 624 | + | |
| 625 | + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing | |
| 626 | + return detail::lexical_conversion<AssignTo, AssignTo>(res, variable); | |
| 627 | + }; | |
| 628 | + | |
| 629 | + Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; }); | |
| 630 | + opt->type_name(detail::type_name<AssignTo>()); | |
| 631 | + opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value); | |
| 632 | + opt->expected(detail::expected_count<AssignTo>::value); | |
| 633 | + opt->run_callback_for_default(); | |
| 634 | + return opt; | |
| 635 | + } | |
| 636 | + | |
| 619 | 637 | /// Add option for a callback of a specific type |
| 620 | - template <typename T> | |
| 638 | + template <typename ArgType> | |
| 621 | 639 | Option *add_option_function(std::string option_name, |
| 622 | - const std::function<void(const T &)> &func, ///< the callback to execute | |
| 640 | + const std::function<void(const ArgType &)> &func, ///< the callback to execute | |
| 623 | 641 | std::string option_description = "") { |
| 624 | 642 | |
| 625 | 643 | auto fun = [func](const CLI::results_t &res) { |
| 626 | - T variable; | |
| 627 | - bool result = detail::lexical_conversion<T, T>(res, variable); | |
| 644 | + ArgType variable; | |
| 645 | + bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable); | |
| 628 | 646 | if(result) { |
| 629 | 647 | func(variable); |
| 630 | 648 | } |
| ... | ... | @@ -632,15 +650,15 @@ class App { |
| 632 | 650 | }; |
| 633 | 651 | |
| 634 | 652 | Option *opt = add_option(option_name, std::move(fun), option_description, false); |
| 635 | - opt->type_name(detail::type_name<T>()); | |
| 636 | - opt->type_size(detail::type_count<T>::value); | |
| 637 | - opt->expected(detail::expected_count<T>::value); | |
| 653 | + opt->type_name(detail::type_name<ArgType>()); | |
| 654 | + opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value); | |
| 655 | + opt->expected(detail::expected_count<ArgType>::value); | |
| 638 | 656 | return opt; |
| 639 | 657 | } |
| 640 | 658 | |
| 641 | 659 | /// Add option with no description or variable assignment |
| 642 | 660 | Option *add_option(std::string option_name) { |
| 643 | - return add_option(option_name, CLI::callback_t(), std::string{}, false); | |
| 661 | + return add_option(option_name, CLI::callback_t{}, std::string{}, false); | |
| 644 | 662 | } |
| 645 | 663 | |
| 646 | 664 | /// Add option with description but with no variable assignment or callback |
| ... | ... | @@ -749,7 +767,7 @@ class App { |
| 749 | 767 | /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes |
| 750 | 768 | /// that can be converted from a string |
| 751 | 769 | template <typename T, |
| 752 | - enable_if_t<!is_vector<T>::value && !std::is_const<T>::value && | |
| 770 | + enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value && | |
| 753 | 771 | (!std::is_integral<T>::value || is_bool<T>::value) && |
| 754 | 772 | !std::is_constructible<std::function<void(int)>, T>::value, |
| 755 | 773 | detail::enabler> = detail::dummy> |
| ... | ... | @@ -873,7 +891,7 @@ class App { |
| 873 | 891 | return opt; |
| 874 | 892 | } |
| 875 | 893 | |
| 876 | - /// Add a complex number | |
| 894 | + /// Add a complex number DEPRECATED --use add_option instead | |
| 877 | 895 | template <typename T, typename XC = double> |
| 878 | 896 | Option *add_complex(std::string option_name, |
| 879 | 897 | T &variable, |
| ... | ... | @@ -2660,7 +2678,12 @@ class App { |
| 2660 | 2678 | |
| 2661 | 2679 | // Get a reference to the pointer to make syntax bearable |
| 2662 | 2680 | Option_p &op = *op_ptr; |
| 2663 | - | |
| 2681 | + /// if we require a separator add it here | |
| 2682 | + if(op->get_inject_separator()) { | |
| 2683 | + if(!op->results().empty() && !op->results().back().empty()) { | |
| 2684 | + op->add_result(std::string{}); | |
| 2685 | + } | |
| 2686 | + } | |
| 2664 | 2687 | int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); |
| 2665 | 2688 | int max_num = op->get_items_expected_max(); |
| 2666 | 2689 | |
| ... | ... | @@ -2725,7 +2748,7 @@ class App { |
| 2725 | 2748 | } |
| 2726 | 2749 | |
| 2727 | 2750 | // if we only partially completed a type then add an empty string for later processing |
| 2728 | - if(min_num > 0 && op->get_type_size_max() != min_num && collected % op->get_type_size_max() != 0) { | |
| 2751 | + if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) { | |
| 2729 | 2752 | op->add_result(std::string{}); |
| 2730 | 2753 | } |
| 2731 | 2754 | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -315,7 +315,7 @@ class Option : public OptionBase<Option> { |
| 315 | 315 | /// results after reduction |
| 316 | 316 | results_t proc_results_{}; |
| 317 | 317 | /// enumeration for the option state machine |
| 318 | - enum class option_state { | |
| 318 | + enum class option_state : char { | |
| 319 | 319 | parsing = 0, //!< The option is currently collecting parsed results |
| 320 | 320 | validated = 2, //!< the results have been validated |
| 321 | 321 | reduced = 4, //!< a subset of results has been generated |
| ... | ... | @@ -329,6 +329,8 @@ class Option : public OptionBase<Option> { |
| 329 | 329 | bool flag_like_{false}; |
| 330 | 330 | /// Control option to run the callback to set the default |
| 331 | 331 | bool run_callback_for_default_{false}; |
| 332 | + /// flag indicating a separator needs to be injected after each argument call | |
| 333 | + bool inject_separator_{false}; | |
| 332 | 334 | ///@} |
| 333 | 335 | |
| 334 | 336 | /// 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> { |
| 658 | 660 | /// The maximum number of arguments the option expects |
| 659 | 661 | int get_type_size_max() const { return type_size_max_; } |
| 660 | 662 | |
| 663 | + /// The number of arguments the option expects | |
| 664 | + int get_inject_separator() const { return inject_separator_; } | |
| 665 | + | |
| 661 | 666 | /// The environment variable associated to this value |
| 662 | 667 | std::string get_envname() const { return envname_; } |
| 663 | 668 | |
| ... | ... | @@ -963,8 +968,8 @@ class Option : public OptionBase<Option> { |
| 963 | 968 | return this; |
| 964 | 969 | } |
| 965 | 970 | |
| 966 | - /// Get a copy of the results | |
| 967 | - results_t results() const { return results_; } | |
| 971 | + /// Get the current complete results set | |
| 972 | + const results_t &results() const { return results_; } | |
| 968 | 973 | |
| 969 | 974 | /// Get a copy of the results |
| 970 | 975 | results_t reduced_results() const { |
| ... | ... | @@ -986,8 +991,7 @@ class Option : public OptionBase<Option> { |
| 986 | 991 | } |
| 987 | 992 | |
| 988 | 993 | /// Get the results as a specified type |
| 989 | - template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy> | |
| 990 | - void results(T &output) const { | |
| 994 | + template <typename T> void results(T &output) const { | |
| 991 | 995 | bool retval; |
| 992 | 996 | if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) { |
| 993 | 997 | const results_t &res = (proc_results_.empty()) ? results_ : proc_results_; |
| ... | ... | @@ -1054,6 +1058,8 @@ class Option : public OptionBase<Option> { |
| 1054 | 1058 | type_size_max_ = option_type_size; |
| 1055 | 1059 | if(type_size_max_ < detail::expected_max_vector_size) { |
| 1056 | 1060 | type_size_min_ = option_type_size; |
| 1061 | + } else { | |
| 1062 | + inject_separator_ = true; | |
| 1057 | 1063 | } |
| 1058 | 1064 | if(type_size_max_ == 0) |
| 1059 | 1065 | required_ = false; |
| ... | ... | @@ -1079,9 +1085,15 @@ class Option : public OptionBase<Option> { |
| 1079 | 1085 | if(type_size_max_ == 0) { |
| 1080 | 1086 | required_ = false; |
| 1081 | 1087 | } |
| 1088 | + if(type_size_max_ >= detail::expected_max_vector_size) { | |
| 1089 | + inject_separator_ = true; | |
| 1090 | + } | |
| 1082 | 1091 | return this; |
| 1083 | 1092 | } |
| 1084 | 1093 | |
| 1094 | + /// Set the value of the separator injection flag | |
| 1095 | + void inject_separator(bool value = true) { inject_separator_ = value; } | |
| 1096 | + | |
| 1085 | 1097 | /// Set a capture function for the default. Mostly used by App. |
| 1086 | 1098 | Option *default_function(const std::function<std::string()> &func) { |
| 1087 | 1099 | default_function_ = func; |
| ... | ... | @@ -1157,7 +1169,7 @@ class Option : public OptionBase<Option> { |
| 1157 | 1169 | } |
| 1158 | 1170 | |
| 1159 | 1171 | for(std::string &result : res) { |
| 1160 | - if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) { | |
| 1172 | + if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) { | |
| 1161 | 1173 | index = 0; // reset index for variable size chunks |
| 1162 | 1174 | continue; |
| 1163 | 1175 | } | ... | ... |
include/CLI/StringTools.hpp
| ... | ... | @@ -190,6 +190,12 @@ inline bool valid_name_string(const std::string &str) { |
| 190 | 190 | return true; |
| 191 | 191 | } |
| 192 | 192 | |
| 193 | +/// check if a string is a container segment separator (empty or "%%" | |
| 194 | +inline bool is_separator(const std::string &str) { | |
| 195 | + static const std::string sep("%%"); | |
| 196 | + return (str.empty() || str == sep); | |
| 197 | +} | |
| 198 | + | |
| 193 | 199 | /// Verify that str consists of letters only |
| 194 | 200 | inline bool isalpha(const std::string &str) { |
| 195 | 201 | return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -45,15 +45,6 @@ template <typename... Ts> using void_t = typename make_void<Ts...>::type; |
| 45 | 45 | /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine |
| 46 | 46 | template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type; |
| 47 | 47 | |
| 48 | -/// Check to see if something is a vector (fail check by default) | |
| 49 | -template <typename T> struct is_vector : std::false_type {}; | |
| 50 | - | |
| 51 | -/// Check to see if something is a vector (true if actually a vector) | |
| 52 | -template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {}; | |
| 53 | - | |
| 54 | -/// Check to see if something is a vector (true if actually a const vector) | |
| 55 | -template <class T, class A> struct is_vector<const std::vector<T, A>> : std::true_type {}; | |
| 56 | - | |
| 57 | 48 | /// Check to see if something is bool (fail check by default) |
| 58 | 49 | template <typename T> struct is_bool : std::false_type {}; |
| 59 | 50 | |
| ... | ... | @@ -195,6 +186,17 @@ template <typename T, typename S = std::istringstream> class is_istreamable { |
| 195 | 186 | static constexpr bool value = decltype(test<T, S>(0))::value; |
| 196 | 187 | }; |
| 197 | 188 | |
| 189 | +/// Check for complex | |
| 190 | +template <typename T> class is_complex { | |
| 191 | + template <typename TT> | |
| 192 | + static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type()); | |
| 193 | + | |
| 194 | + template <typename> static auto test(...) -> std::false_type; | |
| 195 | + | |
| 196 | + public: | |
| 197 | + static constexpr bool value = decltype(test<T>(0))::value; | |
| 198 | +}; | |
| 199 | + | |
| 198 | 200 | /// Templated operation to get a value from a stream |
| 199 | 201 | template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy> |
| 200 | 202 | bool from_stream(const std::string &istring, T &obj) { |
| ... | ... | @@ -209,12 +211,49 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) { |
| 209 | 211 | return false; |
| 210 | 212 | } |
| 211 | 213 | |
| 214 | +// check to see if an object is a mutable container (fail by default) | |
| 215 | +template <typename T, typename _ = void> struct is_mutable_container : std::false_type {}; | |
| 216 | + | |
| 217 | +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and | |
| 218 | +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed | |
| 219 | +/// from a std::string | |
| 220 | +template <typename T> | |
| 221 | +struct is_mutable_container< | |
| 222 | + T, | |
| 223 | + conditional_t<false, | |
| 224 | + void_t<typename T::value_type, | |
| 225 | + decltype(std::declval<T>().end()), | |
| 226 | + decltype(std::declval<T>().clear()), | |
| 227 | + decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), | |
| 228 | + std::declval<const typename T::value_type &>()))>, | |
| 229 | + void>> | |
| 230 | + : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {}; | |
| 231 | + | |
| 232 | +// check to see if an object is a mutable container (fail by default) | |
| 233 | +template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; | |
| 234 | + | |
| 235 | +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end | |
| 236 | +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from | |
| 237 | +/// a std::string | |
| 238 | +template <typename T> | |
| 239 | +struct is_readable_container< | |
| 240 | + T, | |
| 241 | + conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>> | |
| 242 | + : public std::true_type {}; | |
| 243 | + | |
| 244 | +// check to see if an object is a wrapper (fail by default) | |
| 245 | +template <typename T, typename _ = void> struct is_wrapper : std::false_type {}; | |
| 246 | + | |
| 247 | +// check if an object is a is a wrapper (it has a value_type defined) | |
| 248 | +template <typename T> | |
| 249 | +struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {}; | |
| 250 | + | |
| 212 | 251 | // Check for tuple like types, as in classes with a tuple_size type trait |
| 213 | 252 | template <typename S> class is_tuple_like { |
| 214 | 253 | template <typename SS> |
| 215 | 254 | // static auto test(int) |
| 216 | 255 | // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type()); |
| 217 | - static auto test(int) -> decltype(std::tuple_size<SS>::value, std::true_type{}); | |
| 256 | + static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{}); | |
| 218 | 257 | template <typename> static auto test(...) -> std::false_type; |
| 219 | 258 | |
| 220 | 259 | public: |
| ... | ... | @@ -249,20 +288,19 @@ std::string to_string(T &&value) { |
| 249 | 288 | /// If conversion is not supported, return an empty string (streaming is not supported for that type) |
| 250 | 289 | template <typename T, |
| 251 | 290 | enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value && |
| 252 | - !is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value, | |
| 291 | + !is_readable_container<typename std::remove_const<T>::type>::value, | |
| 253 | 292 | detail::enabler> = detail::dummy> |
| 254 | 293 | std::string to_string(T &&) { |
| 255 | 294 | return std::string{}; |
| 256 | 295 | } |
| 257 | 296 | |
| 258 | -/// convert a vector to a string | |
| 297 | +/// convert a readable container to a string | |
| 259 | 298 | template <typename T, |
| 260 | 299 | enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value && |
| 261 | - is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value, | |
| 300 | + is_readable_container<T>::value, | |
| 262 | 301 | detail::enabler> = detail::dummy> |
| 263 | 302 | std::string to_string(T &&variable) { |
| 264 | 303 | std::vector<std::string> defaults; |
| 265 | - defaults.reserve(variable.size()); | |
| 266 | 304 | auto cval = variable.begin(); |
| 267 | 305 | auto end = variable.end(); |
| 268 | 306 | while(cval != end) { |
| ... | ... | @@ -306,25 +344,142 @@ auto value_string(const T &value) -> decltype(to_string(value)) { |
| 306 | 344 | return to_string(value); |
| 307 | 345 | } |
| 308 | 346 | |
| 347 | +/// temple to get the underlying value type if it exists or use a default | |
| 348 | +template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; }; | |
| 349 | + | |
| 350 | +/// Type size for regular object types that do not look like a tuple | |
| 351 | +template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> { | |
| 352 | + using type = typename T::value_type; | |
| 353 | +}; | |
| 354 | + | |
| 309 | 355 | /// This will only trigger for actual void type |
| 310 | -template <typename T, typename Enable = void> struct type_count { static const int value{0}; }; | |
| 356 | +template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; }; | |
| 357 | + | |
| 358 | +/// Type size for regular object types that do not look like a tuple | |
| 359 | +template <typename T> | |
| 360 | +struct type_count_base<T, | |
| 361 | + typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value && | |
| 362 | + !std::is_void<T>::value>::type> { | |
| 363 | + static constexpr int value{1}; | |
| 364 | +}; | |
| 365 | + | |
| 366 | +/// the base tuple size | |
| 367 | +template <typename T> | |
| 368 | +struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> { | |
| 369 | + static constexpr int value{std::tuple_size<T>::value}; | |
| 370 | +}; | |
| 371 | + | |
| 372 | +/// Type count base for containers is the type_count_base of the individual element | |
| 373 | +template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> { | |
| 374 | + static constexpr int value{type_count_base<typename T::value_type>::value}; | |
| 375 | +}; | |
| 311 | 376 | |
| 312 | 377 | /// Set of overloads to get the type size of an object |
| 378 | + | |
| 379 | +/// forward declare the subtype_count structure | |
| 380 | +template <typename T> struct subtype_count; | |
| 381 | + | |
| 382 | +/// forward declare the subtype_count_min structure | |
| 383 | +template <typename T> struct subtype_count_min; | |
| 384 | + | |
| 385 | +/// This will only trigger for actual void type | |
| 386 | +template <typename T, typename Enable = void> struct type_count { static const int value{0}; }; | |
| 387 | + | |
| 388 | +/// Type size for regular object types that do not look like a tuple | |
| 389 | +template <typename T> | |
| 390 | +struct type_count<T, | |
| 391 | + typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value && | |
| 392 | + !std::is_void<T>::value>::type> { | |
| 393 | + static constexpr int value{1}; | |
| 394 | +}; | |
| 395 | + | |
| 396 | +/// Type size for complex since it sometimes looks like a wrapper | |
| 397 | +template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> { | |
| 398 | + static constexpr int value{2}; | |
| 399 | +}; | |
| 400 | + | |
| 401 | +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) | |
| 402 | +template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> { | |
| 403 | + static constexpr int value{subtype_count<typename T::value_type>::value}; | |
| 404 | +}; | |
| 405 | + | |
| 406 | +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) | |
| 407 | +template <typename T> | |
| 408 | +struct type_count<T, | |
| 409 | + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value && | |
| 410 | + !is_mutable_container<T>::value>::type> { | |
| 411 | + static constexpr int value{type_count<typename T::value_type>::value}; | |
| 412 | +}; | |
| 413 | + | |
| 414 | +/// 0 if the index > tuple size | |
| 415 | +template <typename T, std::size_t I> | |
| 416 | +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() { | |
| 417 | + return 0; | |
| 418 | +} | |
| 419 | + | |
| 420 | +/// Recursively generate the tuple type name | |
| 421 | +template <typename T, std::size_t I> | |
| 422 | + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() { | |
| 423 | + return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>(); | |
| 424 | +} | |
| 425 | + | |
| 426 | +/// Get the type size of the sum of type sizes for all the individual tuple types | |
| 313 | 427 | template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> { |
| 314 | - static constexpr int value{std::tuple_size<T>::value}; | |
| 428 | + static constexpr int value{tuple_type_size<T, 0>()}; | |
| 429 | +}; | |
| 430 | + | |
| 431 | +/// definition of subtype count | |
| 432 | +template <typename T> struct subtype_count { | |
| 433 | + static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value}; | |
| 315 | 434 | }; |
| 435 | + | |
| 436 | +/// This will only trigger for actual void type | |
| 437 | +template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; }; | |
| 438 | + | |
| 316 | 439 | /// Type size for regular object types that do not look like a tuple |
| 317 | 440 | template <typename T> |
| 318 | -struct type_count< | |
| 441 | +struct type_count_min< | |
| 319 | 442 | T, |
| 320 | - typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> { | |
| 443 | + typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value && | |
| 444 | + !is_complex<T>::value && !std::is_void<T>::value>::type> { | |
| 445 | + static constexpr int value{type_count<T>::value}; | |
| 446 | +}; | |
| 447 | + | |
| 448 | +/// Type size for complex since it sometimes looks like a wrapper | |
| 449 | +template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> { | |
| 321 | 450 | static constexpr int value{1}; |
| 322 | 451 | }; |
| 323 | 452 | |
| 324 | -/// Type size of types that look like a vector | |
| 325 | -template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> { | |
| 326 | - static constexpr int value{is_vector<typename T::value_type>::value ? expected_max_vector_size | |
| 327 | - : type_count<typename T::value_type>::value}; | |
| 453 | +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) | |
| 454 | +template <typename T> | |
| 455 | +struct type_count_min< | |
| 456 | + T, | |
| 457 | + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> { | |
| 458 | + static constexpr int value{subtype_count_min<typename T::value_type>::value}; | |
| 459 | +}; | |
| 460 | + | |
| 461 | +/// 0 if the index > tuple size | |
| 462 | +template <typename T, std::size_t I> | |
| 463 | +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() { | |
| 464 | + return 0; | |
| 465 | +} | |
| 466 | + | |
| 467 | +/// Recursively generate the tuple type name | |
| 468 | +template <typename T, std::size_t I> | |
| 469 | + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() { | |
| 470 | + return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>(); | |
| 471 | +} | |
| 472 | + | |
| 473 | +/// Get the type size of the sum of type sizes for all the individual tuple types | |
| 474 | +template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> { | |
| 475 | + static constexpr int value{tuple_type_size_min<T, 0>()}; | |
| 476 | +}; | |
| 477 | + | |
| 478 | +/// definition of subtype count | |
| 479 | +template <typename T> struct subtype_count_min { | |
| 480 | + static constexpr int value{is_mutable_container<T>::value | |
| 481 | + ? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0) | |
| 482 | + : type_count_min<T>::value}; | |
| 328 | 483 | }; |
| 329 | 484 | |
| 330 | 485 | /// This will only trigger for actual void type |
| ... | ... | @@ -332,14 +487,22 @@ template <typename T, typename Enable = void> struct expected_count { static con |
| 332 | 487 | |
| 333 | 488 | /// For most types the number of expected items is 1 |
| 334 | 489 | template <typename T> |
| 335 | -struct expected_count<T, typename std::enable_if<!is_vector<T>::value && !std::is_void<T>::value>::type> { | |
| 490 | +struct expected_count<T, | |
| 491 | + typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value && | |
| 492 | + !std::is_void<T>::value>::type> { | |
| 336 | 493 | static constexpr int value{1}; |
| 337 | 494 | }; |
| 338 | 495 | /// number of expected items in a vector |
| 339 | -template <typename T> struct expected_count<T, typename std::enable_if<is_vector<T>::value>::type> { | |
| 496 | +template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> { | |
| 340 | 497 | static constexpr int value{expected_max_vector_size}; |
| 341 | 498 | }; |
| 342 | 499 | |
| 500 | +/// number of expected items in a vector | |
| 501 | +template <typename T> | |
| 502 | +struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> { | |
| 503 | + static constexpr int value{expected_count<typename T::value_type>::value}; | |
| 504 | +}; | |
| 505 | + | |
| 343 | 506 | // Enumeration of the different supported categorizations of objects |
| 344 | 507 | enum class object_category : int { |
| 345 | 508 | integral_value = 2, |
| ... | ... | @@ -350,12 +513,15 @@ enum class object_category : int { |
| 350 | 513 | number_constructible = 12, |
| 351 | 514 | double_constructible = 14, |
| 352 | 515 | integer_constructible = 16, |
| 353 | - vector_value = 30, | |
| 354 | - tuple_value = 35, | |
| 355 | - // string assignable or greater used in a condition so anything string like must come last | |
| 356 | - string_assignable = 50, | |
| 357 | - string_constructible = 60, | |
| 358 | - other = 200, | |
| 516 | + // string like types | |
| 517 | + string_assignable = 23, | |
| 518 | + string_constructible = 24, | |
| 519 | + other = 45, | |
| 520 | + // special wrapper or container types | |
| 521 | + wrapper_value = 50, | |
| 522 | + complex_number = 60, | |
| 523 | + tuple_value = 70, | |
| 524 | + container_value = 80, | |
| 359 | 525 | |
| 360 | 526 | }; |
| 361 | 527 | |
| ... | ... | @@ -392,10 +558,9 @@ template <typename T> struct classify_object<T, typename std::enable_if<std::is_ |
| 392 | 558 | |
| 393 | 559 | /// String and similar direct assignment |
| 394 | 560 | template <typename T> |
| 395 | -struct classify_object< | |
| 396 | - T, | |
| 397 | - typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
| 398 | - std::is_assignable<T &, std::string>::value && !is_vector<T>::value>::type> { | |
| 561 | +struct classify_object<T, | |
| 562 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
| 563 | + std::is_assignable<T &, std::string>::value>::type> { | |
| 399 | 564 | static constexpr object_category value{object_category::string_assignable}; |
| 400 | 565 | }; |
| 401 | 566 | |
| ... | ... | @@ -404,8 +569,8 @@ template <typename T> |
| 404 | 569 | struct classify_object< |
| 405 | 570 | T, |
| 406 | 571 | typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 407 | - !std::is_assignable<T &, std::string>::value && | |
| 408 | - std::is_constructible<T, std::string>::value && !is_vector<T>::value>::type> { | |
| 572 | + !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) && | |
| 573 | + std::is_constructible<T, std::string>::value>::type> { | |
| 409 | 574 | static constexpr object_category value{object_category::string_constructible}; |
| 410 | 575 | }; |
| 411 | 576 | |
| ... | ... | @@ -414,23 +579,35 @@ template <typename T> struct classify_object<T, typename std::enable_if<std::is_ |
| 414 | 579 | static constexpr object_category value{object_category::enumeration}; |
| 415 | 580 | }; |
| 416 | 581 | |
| 582 | +template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> { | |
| 583 | + static constexpr object_category value{object_category::complex_number}; | |
| 584 | +}; | |
| 585 | + | |
| 417 | 586 | /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, |
| 418 | 587 | /// vectors, and enumerations |
| 419 | 588 | template <typename T> struct uncommon_type { |
| 420 | 589 | using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 421 | 590 | !std::is_assignable<T &, std::string>::value && |
| 422 | - !std::is_constructible<T, std::string>::value && !is_vector<T>::value && | |
| 423 | - !std::is_enum<T>::value, | |
| 591 | + !std::is_constructible<T, std::string>::value && !is_complex<T>::value && | |
| 592 | + !is_mutable_container<T>::value && !std::is_enum<T>::value, | |
| 424 | 593 | std::true_type, |
| 425 | 594 | std::false_type>::type; |
| 426 | 595 | static constexpr bool value = type::value; |
| 427 | 596 | }; |
| 428 | 597 | |
| 598 | +/// wrapper type | |
| 599 | +template <typename T> | |
| 600 | +struct classify_object<T, | |
| 601 | + typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value && | |
| 602 | + !is_tuple_like<T>::value && uncommon_type<T>::value)>::type> { | |
| 603 | + static constexpr object_category value{object_category::wrapper_value}; | |
| 604 | +}; | |
| 605 | + | |
| 429 | 606 | /// Assignable from double or int |
| 430 | 607 | template <typename T> |
| 431 | 608 | struct classify_object<T, |
| 432 | 609 | typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && |
| 433 | - is_direct_constructible<T, double>::value && | |
| 610 | + !is_wrapper<T>::value && is_direct_constructible<T, double>::value && | |
| 434 | 611 | is_direct_constructible<T, int>::value>::type> { |
| 435 | 612 | static constexpr object_category value{object_category::number_constructible}; |
| 436 | 613 | }; |
| ... | ... | @@ -439,7 +616,7 @@ struct classify_object<T, |
| 439 | 616 | template <typename T> |
| 440 | 617 | struct classify_object<T, |
| 441 | 618 | typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && |
| 442 | - !is_direct_constructible<T, double>::value && | |
| 619 | + !is_wrapper<T>::value && !is_direct_constructible<T, double>::value && | |
| 443 | 620 | is_direct_constructible<T, int>::value>::type> { |
| 444 | 621 | static constexpr object_category value{object_category::integer_constructible}; |
| 445 | 622 | }; |
| ... | ... | @@ -448,24 +625,30 @@ struct classify_object<T, |
| 448 | 625 | template <typename T> |
| 449 | 626 | struct classify_object<T, |
| 450 | 627 | typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && |
| 451 | - is_direct_constructible<T, double>::value && | |
| 628 | + !is_wrapper<T>::value && is_direct_constructible<T, double>::value && | |
| 452 | 629 | !is_direct_constructible<T, int>::value>::type> { |
| 453 | 630 | static constexpr object_category value{object_category::double_constructible}; |
| 454 | 631 | }; |
| 455 | 632 | |
| 456 | 633 | /// Tuple type |
| 457 | 634 | template <typename T> |
| 458 | -struct classify_object<T, | |
| 459 | - typename std::enable_if<(type_count<T>::value >= 2 && !is_vector<T>::value) || | |
| 460 | - (is_tuple_like<T>::value && uncommon_type<T>::value && | |
| 461 | - !is_direct_constructible<T, double>::value && | |
| 462 | - !is_direct_constructible<T, int>::value)>::type> { | |
| 635 | +struct classify_object< | |
| 636 | + T, | |
| 637 | + typename std::enable_if<is_tuple_like<T>::value && | |
| 638 | + ((type_count<T>::value >= 2 && !is_wrapper<T>::value) || | |
| 639 | + (uncommon_type<T>::value && !is_direct_constructible<T, double>::value && | |
| 640 | + !is_direct_constructible<T, int>::value))>::type> { | |
| 463 | 641 | static constexpr object_category value{object_category::tuple_value}; |
| 642 | + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be | |
| 643 | + // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which | |
| 644 | + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 | |
| 645 | + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out | |
| 646 | + // those cases that are caught by other object classifications | |
| 464 | 647 | }; |
| 465 | 648 | |
| 466 | -/// Vector type | |
| 467 | -template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> { | |
| 468 | - static constexpr object_category value{object_category::vector_value}; | |
| 649 | +/// container type | |
| 650 | +template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> { | |
| 651 | + static constexpr object_category value{object_category::container_value}; | |
| 469 | 652 | }; |
| 470 | 653 | |
| 471 | 654 | // Type name print |
| ... | ... | @@ -511,31 +694,53 @@ constexpr const char *type_name() { |
| 511 | 694 | return "BOOLEAN"; |
| 512 | 695 | } |
| 513 | 696 | |
| 697 | +/// Print name for enumeration types | |
| 698 | +template <typename T, | |
| 699 | + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy> | |
| 700 | +constexpr const char *type_name() { | |
| 701 | + return "COMPLEX"; | |
| 702 | +} | |
| 703 | + | |
| 514 | 704 | /// Print for all other types |
| 515 | 705 | template <typename T, |
| 516 | - enable_if_t<classify_object<T>::value >= object_category::string_assignable, detail::enabler> = detail::dummy> | |
| 706 | + enable_if_t<classify_object<T>::value >= object_category::string_assignable && | |
| 707 | + classify_object<T>::value <= object_category::other, | |
| 708 | + detail::enabler> = detail::dummy> | |
| 517 | 709 | constexpr const char *type_name() { |
| 518 | 710 | return "TEXT"; |
| 519 | 711 | } |
| 712 | +/// typename for tuple value | |
| 713 | +template <typename T, | |
| 714 | + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2, | |
| 715 | + detail::enabler> = detail::dummy> | |
| 716 | +std::string type_name(); // forward declaration | |
| 717 | + | |
| 718 | +/// Generate type name for a wrapper or container value | |
| 719 | +template <typename T, | |
| 720 | + enable_if_t<classify_object<T>::value == object_category::container_value || | |
| 721 | + classify_object<T>::value == object_category::wrapper_value, | |
| 722 | + detail::enabler> = detail::dummy> | |
| 723 | +std::string type_name(); // forward declaration | |
| 520 | 724 | |
| 521 | 725 | /// Print name for single element tuple types |
| 522 | 726 | template <typename T, |
| 523 | - enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count<T>::value == 1, | |
| 727 | + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value == 1, | |
| 524 | 728 | detail::enabler> = detail::dummy> |
| 525 | 729 | inline std::string type_name() { |
| 526 | - return type_name<typename std::tuple_element<0, T>::type>(); | |
| 730 | + return type_name<typename std::decay<typename std::tuple_element<0, T>::type>::type>(); | |
| 527 | 731 | } |
| 528 | 732 | |
| 529 | 733 | /// Empty string if the index > tuple size |
| 530 | 734 | template <typename T, std::size_t I> |
| 531 | -inline typename std::enable_if<I == type_count<T>::value, std::string>::type tuple_name() { | |
| 735 | +inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_name() { | |
| 532 | 736 | return std::string{}; |
| 533 | 737 | } |
| 534 | 738 | |
| 535 | 739 | /// Recursively generate the tuple type name |
| 536 | 740 | template <typename T, std::size_t I> |
| 537 | - inline typename std::enable_if < I<type_count<T>::value, std::string>::type tuple_name() { | |
| 538 | - std::string str = std::string(type_name<typename std::tuple_element<I, T>::type>()) + ',' + tuple_name<T, I + 1>(); | |
| 741 | +inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() { | |
| 742 | + std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) + | |
| 743 | + ',' + tuple_name<T, I + 1>(); | |
| 539 | 744 | if(str.back() == ',') |
| 540 | 745 | str.pop_back(); |
| 541 | 746 | return str; |
| ... | ... | @@ -543,17 +748,19 @@ template <typename T, std::size_t I> |
| 543 | 748 | |
| 544 | 749 | /// Print type name for tuples with 2 or more elements |
| 545 | 750 | template <typename T, |
| 546 | - enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count<T>::value >= 2, | |
| 547 | - detail::enabler> = detail::dummy> | |
| 548 | -std::string type_name() { | |
| 751 | + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2, | |
| 752 | + detail::enabler>> | |
| 753 | +inline std::string type_name() { | |
| 549 | 754 | auto tname = std::string(1, '[') + tuple_name<T, 0>(); |
| 550 | 755 | tname.push_back(']'); |
| 551 | 756 | return tname; |
| 552 | 757 | } |
| 553 | 758 | |
| 554 | -/// This one should not be used normally, since vector types print the internal type | |
| 759 | +/// get the type name for a type that has a value_type member | |
| 555 | 760 | template <typename T, |
| 556 | - enable_if_t<classify_object<T>::value == object_category::vector_value, detail::enabler> = detail::dummy> | |
| 761 | + enable_if_t<classify_object<T>::value == object_category::container_value || | |
| 762 | + classify_object<T>::value == object_category::wrapper_value, | |
| 763 | + detail::enabler>> | |
| 557 | 764 | inline std::string type_name() { |
| 558 | 765 | return type_name<typename T::value_type>(); |
| 559 | 766 | } |
| ... | ... | @@ -671,6 +878,38 @@ bool lexical_cast(const std::string &input, T &output) { |
| 671 | 878 | } |
| 672 | 879 | } |
| 673 | 880 | |
| 881 | +/// complex | |
| 882 | +template <typename T, | |
| 883 | + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy> | |
| 884 | +bool lexical_cast(const std::string &input, T &output) { | |
| 885 | + using XC = typename wrapped_type<T, double>::type; | |
| 886 | + XC x{0.0}, y{0.0}; | |
| 887 | + auto str1 = input; | |
| 888 | + bool worked = false; | |
| 889 | + auto nloc = str1.find_last_of("+-"); | |
| 890 | + if(nloc != std::string::npos && nloc > 0) { | |
| 891 | + worked = detail::lexical_cast(str1.substr(0, nloc), x); | |
| 892 | + str1 = str1.substr(nloc); | |
| 893 | + if(str1.back() == 'i' || str1.back() == 'j') | |
| 894 | + str1.pop_back(); | |
| 895 | + worked = worked && detail::lexical_cast(str1, y); | |
| 896 | + } else { | |
| 897 | + if(str1.back() == 'i' || str1.back() == 'j') { | |
| 898 | + str1.pop_back(); | |
| 899 | + worked = detail::lexical_cast(str1, y); | |
| 900 | + x = XC{0}; | |
| 901 | + } else { | |
| 902 | + worked = detail::lexical_cast(str1, x); | |
| 903 | + y = XC{0}; | |
| 904 | + } | |
| 905 | + } | |
| 906 | + if(worked) { | |
| 907 | + output = T{x, y}; | |
| 908 | + return worked; | |
| 909 | + } | |
| 910 | + return from_stream(input, output); | |
| 911 | +} | |
| 912 | + | |
| 674 | 913 | /// String and similar direct assignment |
| 675 | 914 | template <typename T, |
| 676 | 915 | enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy> |
| ... | ... | @@ -701,6 +940,18 @@ bool lexical_cast(const std::string &input, T &output) { |
| 701 | 940 | return true; |
| 702 | 941 | } |
| 703 | 942 | |
| 943 | +/// wrapper types | |
| 944 | +template <typename T, | |
| 945 | + enable_if_t<classify_object<T>::value == object_category::wrapper_value, detail::enabler> = detail::dummy> | |
| 946 | +bool lexical_cast(const std::string &input, T &output) { | |
| 947 | + typename T::value_type val; | |
| 948 | + if(lexical_cast(input, val)) { | |
| 949 | + output = T{val}; | |
| 950 | + return true; | |
| 951 | + } | |
| 952 | + return from_stream(input, output); | |
| 953 | +} | |
| 954 | + | |
| 704 | 955 | /// Assignable from double or int |
| 705 | 956 | template < |
| 706 | 957 | typename T, |
| ... | ... | @@ -756,38 +1007,40 @@ bool lexical_cast(const std::string &input, T &output) { |
| 756 | 1007 | } |
| 757 | 1008 | |
| 758 | 1009 | /// Assign a value through lexical cast operations |
| 759 | -template < | |
| 760 | - typename T, | |
| 761 | - typename XC, | |
| 762 | - enable_if_t<std::is_same<T, XC>::value && (classify_object<T>::value == object_category::string_assignable || | |
| 763 | - classify_object<T>::value == object_category::string_constructible), | |
| 764 | - detail::enabler> = detail::dummy> | |
| 765 | -bool lexical_assign(const std::string &input, T &output) { | |
| 1010 | +/// Strings can be empty so we need to do a little different | |
| 1011 | +template <typename AssignTo, | |
| 1012 | + typename ConvertTo, | |
| 1013 | + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && | |
| 1014 | + (classify_object<AssignTo>::value == object_category::string_assignable || | |
| 1015 | + classify_object<AssignTo>::value == object_category::string_constructible), | |
| 1016 | + detail::enabler> = detail::dummy> | |
| 1017 | +bool lexical_assign(const std::string &input, AssignTo &output) { | |
| 766 | 1018 | return lexical_cast(input, output); |
| 767 | 1019 | } |
| 768 | 1020 | |
| 769 | 1021 | /// Assign a value through lexical cast operations |
| 770 | -template <typename T, | |
| 771 | - typename XC, | |
| 772 | - enable_if_t<std::is_same<T, XC>::value && classify_object<T>::value != object_category::string_assignable && | |
| 773 | - classify_object<T>::value != object_category::string_constructible, | |
| 1022 | +template <typename AssignTo, | |
| 1023 | + typename ConvertTo, | |
| 1024 | + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && | |
| 1025 | + classify_object<AssignTo>::value != object_category::string_assignable && | |
| 1026 | + classify_object<AssignTo>::value != object_category::string_constructible, | |
| 774 | 1027 | detail::enabler> = detail::dummy> |
| 775 | -bool lexical_assign(const std::string &input, T &output) { | |
| 1028 | +bool lexical_assign(const std::string &input, AssignTo &output) { | |
| 776 | 1029 | if(input.empty()) { |
| 777 | - output = T{}; | |
| 1030 | + output = AssignTo{}; | |
| 778 | 1031 | return true; |
| 779 | 1032 | } |
| 780 | 1033 | return lexical_cast(input, output); |
| 781 | 1034 | } |
| 782 | 1035 | |
| 783 | 1036 | /// Assign a value converted from a string in lexical cast to the output value directly |
| 784 | -template < | |
| 785 | - typename T, | |
| 786 | - typename XC, | |
| 787 | - enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy> | |
| 788 | -bool lexical_assign(const std::string &input, T &output) { | |
| 789 | - XC val{}; | |
| 790 | - bool parse_result = (!input.empty()) ? lexical_cast<XC>(input, val) : true; | |
| 1037 | +template <typename AssignTo, | |
| 1038 | + typename ConvertTo, | |
| 1039 | + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, ConvertTo &>::value, | |
| 1040 | + detail::enabler> = detail::dummy> | |
| 1041 | +bool lexical_assign(const std::string &input, AssignTo &output) { | |
| 1042 | + ConvertTo val{}; | |
| 1043 | + bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true; | |
| 791 | 1044 | if(parse_result) { |
| 792 | 1045 | output = val; |
| 793 | 1046 | } |
| ... | ... | @@ -795,195 +1048,325 @@ bool lexical_assign(const std::string &input, T &output) { |
| 795 | 1048 | } |
| 796 | 1049 | |
| 797 | 1050 | /// Assign a value from a lexical cast through constructing a value and move assigning it |
| 798 | -template <typename T, | |
| 799 | - typename XC, | |
| 800 | - enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value && | |
| 801 | - std::is_move_assignable<T>::value, | |
| 802 | - detail::enabler> = detail::dummy> | |
| 803 | -bool lexical_assign(const std::string &input, T &output) { | |
| 804 | - XC val{}; | |
| 805 | - bool parse_result = input.empty() ? true : lexical_cast<XC>(input, val); | |
| 1051 | +template < | |
| 1052 | + typename AssignTo, | |
| 1053 | + typename ConvertTo, | |
| 1054 | + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, ConvertTo &>::value && | |
| 1055 | + std::is_move_assignable<AssignTo>::value, | |
| 1056 | + detail::enabler> = detail::dummy> | |
| 1057 | +bool lexical_assign(const std::string &input, AssignTo &output) { | |
| 1058 | + ConvertTo val{}; | |
| 1059 | + bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val); | |
| 806 | 1060 | if(parse_result) { |
| 807 | - output = T(val); // use () form of constructor to allow some implicit conversions | |
| 1061 | + output = AssignTo(val); // use () form of constructor to allow some implicit conversions | |
| 808 | 1062 | } |
| 809 | 1063 | return parse_result; |
| 810 | 1064 | } |
| 811 | -/// Lexical conversion if there is only one element | |
| 812 | -template < | |
| 813 | - typename T, | |
| 814 | - typename XC, | |
| 815 | - enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value && !is_vector<XC>::value, | |
| 816 | - detail::enabler> = detail::dummy> | |
| 817 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 818 | - return lexical_assign<T, XC>(strings[0], output); | |
| 1065 | + | |
| 1066 | +/// primary lexical conversion operation, 1 string to 1 type of some kind | |
| 1067 | +template <typename AssignTo, | |
| 1068 | + typename ConvertTo, | |
| 1069 | + enable_if_t<classify_object<ConvertTo>::value <= object_category::other && | |
| 1070 | + classify_object<AssignTo>::value <= object_category::wrapper_value, | |
| 1071 | + detail::enabler> = detail::dummy> | |
| 1072 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 1073 | + return lexical_assign<AssignTo, ConvertTo>(strings[0], output); | |
| 819 | 1074 | } |
| 820 | 1075 | |
| 821 | -/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor | |
| 822 | -template <typename T, | |
| 823 | - typename XC, | |
| 824 | - enable_if_t<type_count<T>::value == 1 && type_count<XC>::value == 2, detail::enabler> = detail::dummy> | |
| 825 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 826 | - typename std::tuple_element<0, XC>::type v1; | |
| 827 | - typename std::tuple_element<1, XC>::type v2; | |
| 1076 | +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element | |
| 1077 | +/// constructor | |
| 1078 | +template <typename AssignTo, | |
| 1079 | + typename ConvertTo, | |
| 1080 | + enable_if_t<(type_count<AssignTo>::value <= 2) && expected_count<AssignTo>::value == 1 && | |
| 1081 | + is_tuple_like<ConvertTo>::value && type_count_base<ConvertTo>::value == 2, | |
| 1082 | + detail::enabler> = detail::dummy> | |
| 1083 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 1084 | + // the remove const is to handle pair types coming from a container | |
| 1085 | + typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1; | |
| 1086 | + typename std::tuple_element<1, ConvertTo>::type v2; | |
| 828 | 1087 | bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1); |
| 829 | 1088 | if(strings.size() > 1) { |
| 830 | 1089 | retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2); |
| 831 | 1090 | } |
| 832 | 1091 | if(retval) { |
| 833 | - output = T{v1, v2}; | |
| 1092 | + output = AssignTo{v1, v2}; | |
| 834 | 1093 | } |
| 835 | 1094 | return retval; |
| 836 | 1095 | } |
| 837 | 1096 | |
| 838 | -/// Lexical conversion of a vector types | |
| 839 | -template <class T, | |
| 840 | - class XC, | |
| 841 | - enable_if_t<expected_count<T>::value == expected_max_vector_size && | |
| 842 | - expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 1, | |
| 1097 | +/// Lexical conversion of a container types of single elements | |
| 1098 | +template <class AssignTo, | |
| 1099 | + class ConvertTo, | |
| 1100 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && | |
| 1101 | + type_count<ConvertTo>::value == 1, | |
| 843 | 1102 | detail::enabler> = detail::dummy> |
| 844 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 845 | - output.clear(); | |
| 846 | - output.reserve(strings.size()); | |
| 1103 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 1104 | + output.erase(output.begin(), output.end()); | |
| 847 | 1105 | for(const auto &elem : strings) { |
| 848 | - | |
| 849 | - output.emplace_back(); | |
| 850 | - bool retval = lexical_assign<typename T::value_type, typename XC::value_type>(elem, output.back()); | |
| 1106 | + typename AssignTo::value_type out; | |
| 1107 | + bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out); | |
| 851 | 1108 | if(!retval) { |
| 852 | 1109 | return false; |
| 853 | 1110 | } |
| 1111 | + output.insert(output.end(), std::move(out)); | |
| 854 | 1112 | } |
| 855 | 1113 | return (!output.empty()); |
| 856 | 1114 | } |
| 857 | 1115 | |
| 858 | -/// Lexical conversion of a vector types with type size of two | |
| 859 | -template <class T, | |
| 860 | - class XC, | |
| 861 | - enable_if_t<expected_count<T>::value == expected_max_vector_size && | |
| 862 | - expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 2, | |
| 863 | - detail::enabler> = detail::dummy> | |
| 864 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 865 | - output.clear(); | |
| 866 | - for(std::size_t ii = 0; ii < strings.size(); ii += 2) { | |
| 1116 | +/// Lexical conversion for complex types | |
| 1117 | +template <class AssignTo, class ConvertTo, enable_if_t<is_complex<ConvertTo>::value, detail::enabler> = detail::dummy> | |
| 1118 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { | |
| 867 | 1119 | |
| 868 | - typename std::tuple_element<0, typename XC::value_type>::type v1; | |
| 869 | - typename std::tuple_element<1, typename XC::value_type>::type v2; | |
| 870 | - bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[ii], v1); | |
| 871 | - if(strings.size() > ii + 1) { | |
| 872 | - retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[ii + 1], v2); | |
| 1120 | + if(strings.size() >= 2 && !strings[1].empty()) { | |
| 1121 | + using XC2 = typename wrapped_type<ConvertTo, double>::type; | |
| 1122 | + XC2 x{0.0}, y{0.0}; | |
| 1123 | + auto str1 = strings[1]; | |
| 1124 | + if(str1.back() == 'i' || str1.back() == 'j') { | |
| 1125 | + str1.pop_back(); | |
| 873 | 1126 | } |
| 874 | - if(retval) { | |
| 875 | - output.emplace_back(v1, v2); | |
| 876 | - } else { | |
| 877 | - return false; | |
| 1127 | + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); | |
| 1128 | + if(worked) { | |
| 1129 | + output = ConvertTo{x, y}; | |
| 878 | 1130 | } |
| 1131 | + return worked; | |
| 1132 | + } else { | |
| 1133 | + return lexical_assign<AssignTo, ConvertTo>(strings[0], output); | |
| 879 | 1134 | } |
| 880 | - return (!output.empty()); | |
| 881 | 1135 | } |
| 882 | 1136 | |
| 883 | 1137 | /// Conversion to a vector type using a particular single type as the conversion type |
| 884 | -template <class T, | |
| 885 | - class XC, | |
| 886 | - enable_if_t<(expected_count<T>::value == expected_max_vector_size) && (expected_count<XC>::value == 1) && | |
| 887 | - (type_count<XC>::value == 1), | |
| 1138 | +template <class AssignTo, | |
| 1139 | + class ConvertTo, | |
| 1140 | + enable_if_t<is_mutable_container<AssignTo>::value && (expected_count<ConvertTo>::value == 1) && | |
| 1141 | + (type_count<ConvertTo>::value == 1), | |
| 888 | 1142 | detail::enabler> = detail::dummy> |
| 889 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 1143 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 890 | 1144 | bool retval = true; |
| 891 | 1145 | output.clear(); |
| 892 | 1146 | output.reserve(strings.size()); |
| 893 | 1147 | for(const auto &elem : strings) { |
| 894 | 1148 | |
| 895 | 1149 | output.emplace_back(); |
| 896 | - retval = retval && lexical_assign<typename T::value_type, XC>(elem, output.back()); | |
| 1150 | + retval = retval && lexical_assign<typename AssignTo::value_type, ConvertTo>(elem, output.back()); | |
| 897 | 1151 | } |
| 898 | 1152 | return (!output.empty()) && retval; |
| 899 | 1153 | } |
| 900 | -// This one is last since it can call other lexical_conversion functions | |
| 901 | -/// Lexical conversion if there is only one element but the conversion type is a vector | |
| 902 | -template <typename T, | |
| 903 | - typename XC, | |
| 904 | - enable_if_t<!is_tuple_like<T>::value && !is_vector<T>::value && is_vector<XC>::value, detail::enabler> = | |
| 905 | - detail::dummy> | |
| 906 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 1154 | + | |
| 1155 | +// forward declaration | |
| 1156 | + | |
| 1157 | +/// Lexical conversion of a container types with conversion type of two elements | |
| 1158 | +template <class AssignTo, | |
| 1159 | + class ConvertTo, | |
| 1160 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && | |
| 1161 | + type_count_base<ConvertTo>::value == 2, | |
| 1162 | + detail::enabler> = detail::dummy> | |
| 1163 | +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output); | |
| 1164 | + | |
| 1165 | +/// Lexical conversion of a vector types with type_size >2 forward declaration | |
| 1166 | +template <class AssignTo, | |
| 1167 | + class ConvertTo, | |
| 1168 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && | |
| 1169 | + type_count_base<ConvertTo>::value != 2 && | |
| 1170 | + ((type_count<ConvertTo>::value > 2) || | |
| 1171 | + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)), | |
| 1172 | + detail::enabler> = detail::dummy> | |
| 1173 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); | |
| 1174 | + | |
| 1175 | +/// Conversion for tuples | |
| 1176 | +template <class AssignTo, | |
| 1177 | + class ConvertTo, | |
| 1178 | + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value && | |
| 1179 | + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value || | |
| 1180 | + type_count<ConvertTo>::value > 2), | |
| 1181 | + detail::enabler> = detail::dummy> | |
| 1182 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); // forward declaration | |
| 1183 | + | |
| 1184 | +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large | |
| 1185 | +/// tuple | |
| 1186 | +template <typename AssignTo, | |
| 1187 | + typename ConvertTo, | |
| 1188 | + enable_if_t<!is_tuple_like<AssignTo>::value && !is_mutable_container<AssignTo>::value && | |
| 1189 | + classify_object<ConvertTo>::value != object_category::wrapper_value && | |
| 1190 | + (is_mutable_container<ConvertTo>::value || type_count<ConvertTo>::value > 2), | |
| 1191 | + detail::enabler> = detail::dummy> | |
| 1192 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 907 | 1193 | |
| 908 | 1194 | if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { |
| 909 | - XC val; | |
| 910 | - auto retval = lexical_conversion<XC, XC>(strings, val); | |
| 911 | - output = T{val}; | |
| 1195 | + ConvertTo val; | |
| 1196 | + auto retval = lexical_conversion<ConvertTo, ConvertTo>(strings, val); | |
| 1197 | + output = AssignTo{val}; | |
| 912 | 1198 | return retval; |
| 913 | 1199 | } |
| 914 | - output = T{}; | |
| 1200 | + output = AssignTo{}; | |
| 915 | 1201 | return true; |
| 916 | 1202 | } |
| 917 | 1203 | |
| 918 | 1204 | /// function template for converting tuples if the static Index is greater than the tuple size |
| 919 | -template <class T, class XC, std::size_t I> | |
| 920 | -inline typename std::enable_if<I >= type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &, | |
| 921 | - T &) { | |
| 1205 | +template <class AssignTo, class ConvertTo, std::size_t I> | |
| 1206 | +inline typename std::enable_if<(I >= type_count_base<AssignTo>::value), bool>::type | |
| 1207 | +tuple_conversion(const std::vector<std::string> &, AssignTo &) { | |
| 922 | 1208 | return true; |
| 923 | 1209 | } |
| 1210 | + | |
| 1211 | +/// Conversion of a tuple element where the type size ==1 and not a mutable container | |
| 1212 | +template <class AssignTo, class ConvertTo> | |
| 1213 | +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && type_count<ConvertTo>::value == 1, bool>::type | |
| 1214 | +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { | |
| 1215 | + auto retval = lexical_assign<AssignTo, ConvertTo>(strings[0], output); | |
| 1216 | + strings.erase(strings.begin()); | |
| 1217 | + return retval; | |
| 1218 | +} | |
| 1219 | + | |
| 1220 | +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container | |
| 1221 | +template <class AssignTo, class ConvertTo> | |
| 1222 | +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && (type_count<ConvertTo>::value > 1) && | |
| 1223 | + type_count<ConvertTo>::value == type_count_min<ConvertTo>::value, | |
| 1224 | + bool>::type | |
| 1225 | +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { | |
| 1226 | + auto retval = lexical_conversion<AssignTo, ConvertTo>(strings, output); | |
| 1227 | + strings.erase(strings.begin(), strings.begin() + type_count<ConvertTo>::value); | |
| 1228 | + return retval; | |
| 1229 | +} | |
| 1230 | + | |
| 1231 | +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes | |
| 1232 | +template <class AssignTo, class ConvertTo> | |
| 1233 | +inline typename std::enable_if<is_mutable_container<ConvertTo>::value || | |
| 1234 | + type_count<ConvertTo>::value != type_count_min<ConvertTo>::value, | |
| 1235 | + bool>::type | |
| 1236 | +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { | |
| 1237 | + | |
| 1238 | + std::size_t index{subtype_count_min<ConvertTo>::value}; | |
| 1239 | + const std::size_t mx_count{subtype_count<ConvertTo>::value}; | |
| 1240 | + const std::size_t mx{(std::max)(mx_count, strings.size())}; | |
| 1241 | + | |
| 1242 | + while(index < mx) { | |
| 1243 | + if(is_separator(strings[index])) { | |
| 1244 | + break; | |
| 1245 | + } | |
| 1246 | + ++index; | |
| 1247 | + } | |
| 1248 | + bool retval = lexical_conversion<AssignTo, ConvertTo>( | |
| 1249 | + std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output); | |
| 1250 | + strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1); | |
| 1251 | + return retval; | |
| 1252 | +} | |
| 1253 | + | |
| 924 | 1254 | /// Tuple conversion operation |
| 925 | -template <class T, class XC, std::size_t I> | |
| 926 | - inline typename std::enable_if < | |
| 927 | - I<type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &strings, T &output) { | |
| 1255 | +template <class AssignTo, class ConvertTo, std::size_t I> | |
| 1256 | +inline typename std::enable_if<(I < type_count_base<AssignTo>::value), bool>::type | |
| 1257 | +tuple_conversion(std::vector<std::string> strings, AssignTo &output) { | |
| 928 | 1258 | bool retval = true; |
| 929 | - if(strings.size() > I) { | |
| 930 | - retval = retval && lexical_assign<typename std::tuple_element<I, T>::type, | |
| 931 | - typename std::conditional<is_tuple_like<XC>::value, | |
| 932 | - typename std::tuple_element<I, XC>::type, | |
| 933 | - XC>::type>(strings[I], std::get<I>(output)); | |
| 1259 | + using ConvertToElement = typename std:: | |
| 1260 | + conditional<is_tuple_like<ConvertTo>::value, typename std::tuple_element<I, ConvertTo>::type, ConvertTo>::type; | |
| 1261 | + if(!strings.empty()) { | |
| 1262 | + retval = retval && tuple_type_conversion<typename std::tuple_element<I, AssignTo>::type, ConvertToElement>( | |
| 1263 | + strings, std::get<I>(output)); | |
| 934 | 1264 | } |
| 935 | - retval = retval && tuple_conversion<T, XC, I + 1>(strings, output); | |
| 1265 | + retval = retval && tuple_conversion<AssignTo, ConvertTo, I + 1>(std::move(strings), output); | |
| 936 | 1266 | return retval; |
| 937 | 1267 | } |
| 938 | 1268 | |
| 939 | -/// Conversion for tuples | |
| 940 | -template <class T, class XC, enable_if_t<is_tuple_like<T>::value, detail::enabler> = detail::dummy> | |
| 941 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 1269 | +/// Lexical conversion of a container types with tuple elements of size 2 | |
| 1270 | +template <class AssignTo, | |
| 1271 | + class ConvertTo, | |
| 1272 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && | |
| 1273 | + type_count_base<ConvertTo>::value == 2, | |
| 1274 | + detail::enabler>> | |
| 1275 | +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output) { | |
| 1276 | + output.clear(); | |
| 1277 | + while(!strings.empty()) { | |
| 1278 | + | |
| 1279 | + typename std::remove_const<typename std::tuple_element<0, typename ConvertTo::value_type>::type>::type v1; | |
| 1280 | + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; | |
| 1281 | + bool retval = tuple_type_conversion<decltype(v1), decltype(v1)>(strings, v1); | |
| 1282 | + if(!strings.empty()) { | |
| 1283 | + retval = retval && tuple_type_conversion<decltype(v2), decltype(v2)>(strings, v2); | |
| 1284 | + } | |
| 1285 | + if(retval) { | |
| 1286 | + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); | |
| 1287 | + } else { | |
| 1288 | + return false; | |
| 1289 | + } | |
| 1290 | + } | |
| 1291 | + return (!output.empty()); | |
| 1292 | +} | |
| 1293 | + | |
| 1294 | +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 | |
| 1295 | +template <class AssignTo, | |
| 1296 | + class ConvertTo, | |
| 1297 | + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value && | |
| 1298 | + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value || | |
| 1299 | + type_count<ConvertTo>::value > 2), | |
| 1300 | + detail::enabler>> | |
| 1301 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 942 | 1302 | static_assert( |
| 943 | - !is_tuple_like<XC>::value || type_count<T>::value == type_count<XC>::value, | |
| 1303 | + !is_tuple_like<ConvertTo>::value || type_count_base<AssignTo>::value == type_count_base<ConvertTo>::value, | |
| 944 | 1304 | "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); |
| 945 | - return tuple_conversion<T, XC, 0>(strings, output); | |
| 1305 | + return tuple_conversion<AssignTo, ConvertTo, 0>(strings, output); | |
| 946 | 1306 | } |
| 947 | 1307 | |
| 948 | -/// Lexical conversion of a vector types with type_size >2 | |
| 949 | -template <class T, | |
| 950 | - class XC, | |
| 951 | - enable_if_t<expected_count<T>::value == expected_max_vector_size && | |
| 952 | - expected_count<XC>::value == expected_max_vector_size && (type_count<XC>::value > 2), | |
| 953 | - detail::enabler> = detail::dummy> | |
| 954 | -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { | |
| 1308 | +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 | |
| 1309 | +template <class AssignTo, | |
| 1310 | + class ConvertTo, | |
| 1311 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && | |
| 1312 | + type_count_base<ConvertTo>::value != 2 && | |
| 1313 | + ((type_count<ConvertTo>::value > 2) || | |
| 1314 | + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)), | |
| 1315 | + detail::enabler>> | |
| 1316 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { | |
| 955 | 1317 | bool retval = true; |
| 956 | 1318 | output.clear(); |
| 957 | 1319 | std::vector<std::string> temp; |
| 958 | - std::size_t ii = 0; | |
| 959 | - std::size_t icount = 0; | |
| 960 | - std::size_t xcm = type_count<XC>::value; | |
| 961 | - while(ii < strings.size()) { | |
| 1320 | + std::size_t ii{0}; | |
| 1321 | + std::size_t icount{0}; | |
| 1322 | + std::size_t xcm{type_count<ConvertTo>::value}; | |
| 1323 | + auto ii_max = strings.size(); | |
| 1324 | + while(ii < ii_max) { | |
| 962 | 1325 | temp.push_back(strings[ii]); |
| 963 | 1326 | ++ii; |
| 964 | 1327 | ++icount; |
| 965 | - if(icount == xcm || temp.back().empty()) { | |
| 966 | - if(static_cast<int>(xcm) == expected_max_vector_size) { | |
| 1328 | + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { | |
| 1329 | + if(static_cast<int>(xcm) > type_count_min<ConvertTo>::value && is_separator(temp.back())) { | |
| 967 | 1330 | temp.pop_back(); |
| 968 | 1331 | } |
| 969 | - output.emplace_back(); | |
| 970 | - retval = retval && lexical_conversion<typename T::value_type, typename XC::value_type>(temp, output.back()); | |
| 1332 | + typename AssignTo::value_type temp_out; | |
| 1333 | + retval = retval && | |
| 1334 | + lexical_conversion<typename AssignTo::value_type, typename ConvertTo::value_type>(temp, temp_out); | |
| 971 | 1335 | temp.clear(); |
| 972 | 1336 | if(!retval) { |
| 973 | 1337 | return false; |
| 974 | 1338 | } |
| 1339 | + output.insert(output.end(), std::move(temp_out)); | |
| 975 | 1340 | icount = 0; |
| 976 | 1341 | } |
| 977 | 1342 | } |
| 978 | 1343 | return retval; |
| 979 | 1344 | } |
| 1345 | + | |
| 1346 | +/// conversion for wrapper types | |
| 1347 | +template < | |
| 1348 | + typename AssignTo, | |
| 1349 | + class ConvertTo, | |
| 1350 | + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value, detail::enabler> = detail::dummy> | |
| 1351 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { | |
| 1352 | + if(strings.empty() || strings.front().empty()) { | |
| 1353 | + output = ConvertTo{}; | |
| 1354 | + return true; | |
| 1355 | + } | |
| 1356 | + typename ConvertTo::value_type val; | |
| 1357 | + if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) { | |
| 1358 | + output = ConvertTo{val}; | |
| 1359 | + return true; | |
| 1360 | + } | |
| 1361 | + return false; | |
| 1362 | +} | |
| 1363 | + | |
| 980 | 1364 | /// Sum a vector of flag representations |
| 981 | 1365 | /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is |
| 982 | 1366 | /// by |
| 983 | 1367 | /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most |
| 984 | 1368 | /// common true and false strings then uses stoll to convert the rest for summing |
| 985 | -template <typename T, | |
| 986 | - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | |
| 1369 | +template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | |
| 987 | 1370 | void sum_flag_vector(const std::vector<std::string> &flags, T &output) { |
| 988 | 1371 | std::int64_t count{0}; |
| 989 | 1372 | for(auto &flag : flags) { |
| ... | ... | @@ -997,8 +1380,7 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) { |
| 997 | 1380 | /// by |
| 998 | 1381 | /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most |
| 999 | 1382 | /// common true and false strings then uses stoll to convert the rest for summing |
| 1000 | -template <typename T, | |
| 1001 | - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> | |
| 1383 | +template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy> | |
| 1002 | 1384 | void sum_flag_vector(const std::vector<std::string> &flags, T &output) { |
| 1003 | 1385 | std::int64_t count{0}; |
| 1004 | 1386 | for(auto &flag : flags) { | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -454,98 +454,6 @@ TEST_F(TApp, SepInt) { |
| 454 | 454 | EXPECT_EQ(i, 4); |
| 455 | 455 | } |
| 456 | 456 | |
| 457 | -TEST_F(TApp, OneStringAgain) { | |
| 458 | - std::string str; | |
| 459 | - app.add_option("-s,--string", str); | |
| 460 | - args = {"--string", "mystring"}; | |
| 461 | - run(); | |
| 462 | - EXPECT_EQ(1u, app.count("-s")); | |
| 463 | - EXPECT_EQ(1u, app.count("--string")); | |
| 464 | - EXPECT_EQ(str, "mystring"); | |
| 465 | -} | |
| 466 | - | |
| 467 | -TEST_F(TApp, OneStringFunction) { | |
| 468 | - std::string str; | |
| 469 | - app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; }); | |
| 470 | - args = {"--string", "mystring"}; | |
| 471 | - run(); | |
| 472 | - EXPECT_EQ(1u, app.count("-s")); | |
| 473 | - EXPECT_EQ(1u, app.count("--string")); | |
| 474 | - EXPECT_EQ(str, "mystring"); | |
| 475 | -} | |
| 476 | - | |
| 477 | -TEST_F(TApp, doubleFunction) { | |
| 478 | - double res{0.0}; | |
| 479 | - app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); | |
| 480 | - args = {"--val", "-354.356"}; | |
| 481 | - run(); | |
| 482 | - EXPECT_EQ(res, 300.356); | |
| 483 | - // get the original value as entered as an integer | |
| 484 | - EXPECT_EQ(app["--val"]->as<float>(), -354.356f); | |
| 485 | -} | |
| 486 | - | |
| 487 | -TEST_F(TApp, doubleFunctionFail) { | |
| 488 | - double res; | |
| 489 | - app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); | |
| 490 | - args = {"--val", "not_double"}; | |
| 491 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 492 | -} | |
| 493 | - | |
| 494 | -TEST_F(TApp, doubleVectorFunction) { | |
| 495 | - std::vector<double> res; | |
| 496 | - app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { | |
| 497 | - res = val; | |
| 498 | - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | |
| 499 | - }); | |
| 500 | - args = {"--val", "5", "--val", "6", "--val", "7"}; | |
| 501 | - run(); | |
| 502 | - EXPECT_EQ(res.size(), 3u); | |
| 503 | - EXPECT_EQ(res[0], 10.0); | |
| 504 | - EXPECT_EQ(res[2], 12.0); | |
| 505 | -} | |
| 506 | - | |
| 507 | -TEST_F(TApp, doubleVectorFunctionFail) { | |
| 508 | - std::vector<double> res; | |
| 509 | - std::string vstring = "--val"; | |
| 510 | - app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) { | |
| 511 | - res = val; | |
| 512 | - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | |
| 513 | - }); | |
| 514 | - args = {"--val", "five", "--val", "nine", "--val", "7"}; | |
| 515 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 516 | - // check that getting the results through the results function generates the same error | |
| 517 | - EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError); | |
| 518 | - auto strvec = app[vstring]->as<std::vector<std::string>>(); | |
| 519 | - EXPECT_EQ(strvec.size(), 3u); | |
| 520 | -} | |
| 521 | - | |
| 522 | -TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) { | |
| 523 | - std::vector<double> res; | |
| 524 | - auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { | |
| 525 | - res = val; | |
| 526 | - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | |
| 527 | - }); | |
| 528 | - args = {"--val", "5", "--val", "6", "--val", "7"}; | |
| 529 | - run(); | |
| 530 | - EXPECT_EQ(res.size(), 3u); | |
| 531 | - EXPECT_EQ(res[0], 10.0); | |
| 532 | - EXPECT_EQ(res[2], 12.0); | |
| 533 | - EXPECT_FALSE(opt->get_run_callback_for_default()); | |
| 534 | - opt->run_callback_for_default(); | |
| 535 | - opt->default_val(std::vector<int>{2, 1, -2}); | |
| 536 | - EXPECT_EQ(res[0], 7.0); | |
| 537 | - EXPECT_EQ(res[2], 3.0); | |
| 538 | - | |
| 539 | - EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError); | |
| 540 | - auto vec = opt->as<std::vector<double>>(); | |
| 541 | - ASSERT_EQ(vec.size(), 3U); | |
| 542 | - EXPECT_EQ(vec[0], 5.0); | |
| 543 | - EXPECT_EQ(vec[2], 7.0); | |
| 544 | - opt->check(CLI::Number); | |
| 545 | - opt->run_callback_for_default(false); | |
| 546 | - EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError); | |
| 547 | -} | |
| 548 | - | |
| 549 | 457 | TEST_F(TApp, DefaultStringAgain) { |
| 550 | 458 | std::string str = "previous"; |
| 551 | 459 | app.add_option("-s,--string", str); |
| ... | ... | @@ -647,35 +555,6 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) { |
| 647 | 555 | EXPECT_EQ(1u, app.count("-A")); |
| 648 | 556 | } |
| 649 | 557 | |
| 650 | -TEST_F(TApp, BoolAndIntFlags) { | |
| 651 | - | |
| 652 | - bool bflag{false}; | |
| 653 | - int iflag{0}; | |
| 654 | - unsigned int uflag{0}; | |
| 655 | - | |
| 656 | - app.add_flag("-b", bflag); | |
| 657 | - app.add_flag("-i", iflag); | |
| 658 | - app.add_flag("-u", uflag); | |
| 659 | - | |
| 660 | - args = {"-b", "-i", "-u"}; | |
| 661 | - run(); | |
| 662 | - EXPECT_TRUE(bflag); | |
| 663 | - EXPECT_EQ(1, iflag); | |
| 664 | - EXPECT_EQ((unsigned int)1, uflag); | |
| 665 | - | |
| 666 | - args = {"-b", "-b"}; | |
| 667 | - ASSERT_NO_THROW(run()); | |
| 668 | - EXPECT_TRUE(bflag); | |
| 669 | - | |
| 670 | - bflag = false; | |
| 671 | - | |
| 672 | - args = {"-iiiuu"}; | |
| 673 | - run(); | |
| 674 | - EXPECT_FALSE(bflag); | |
| 675 | - EXPECT_EQ(3, iflag); | |
| 676 | - EXPECT_EQ((unsigned int)2, uflag); | |
| 677 | -} | |
| 678 | - | |
| 679 | 558 | TEST_F(TApp, FlagLikeOption) { |
| 680 | 559 | bool val{false}; |
| 681 | 560 | auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true"); |
| ... | ... | @@ -724,32 +603,6 @@ TEST_F(TApp, BoolOnlyFlag) { |
| 724 | 603 | EXPECT_THROW(run(), CLI::ArgumentMismatch); |
| 725 | 604 | } |
| 726 | 605 | |
| 727 | -TEST_F(TApp, BoolOption) { | |
| 728 | - bool bflag{false}; | |
| 729 | - app.add_option("-b", bflag); | |
| 730 | - | |
| 731 | - args = {"-b", "false"}; | |
| 732 | - run(); | |
| 733 | - EXPECT_FALSE(bflag); | |
| 734 | - | |
| 735 | - args = {"-b", "1"}; | |
| 736 | - run(); | |
| 737 | - EXPECT_TRUE(bflag); | |
| 738 | - | |
| 739 | - args = {"-b", "-7"}; | |
| 740 | - run(); | |
| 741 | - EXPECT_FALSE(bflag); | |
| 742 | - | |
| 743 | - // cause an out of bounds error internally | |
| 744 | - args = {"-b", "751615654161688126132138844896646748852"}; | |
| 745 | - run(); | |
| 746 | - EXPECT_TRUE(bflag); | |
| 747 | - | |
| 748 | - args = {"-b", "-751615654161688126132138844896646748852"}; | |
| 749 | - run(); | |
| 750 | - EXPECT_FALSE(bflag); | |
| 751 | -} | |
| 752 | - | |
| 753 | 606 | TEST_F(TApp, ShortOpts) { |
| 754 | 607 | |
| 755 | 608 | unsigned long long funnyint{0}; |
| ... | ... | @@ -892,37 +745,6 @@ TEST_F(TApp, TakeLastOptMulti) { |
| 892 | 745 | EXPECT_EQ(vals, std::vector<int>({2, 3})); |
| 893 | 746 | } |
| 894 | 747 | |
| 895 | -TEST_F(TApp, vectorDefaults) { | |
| 896 | - std::vector<int> vals{4, 5}; | |
| 897 | - auto opt = app.add_option("--long", vals, "", true); | |
| 898 | - | |
| 899 | - args = {"--long", "[1,2,3]"}; | |
| 900 | - | |
| 901 | - run(); | |
| 902 | - | |
| 903 | - EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); | |
| 904 | - | |
| 905 | - args.clear(); | |
| 906 | - run(); | |
| 907 | - auto res = app["--long"]->as<std::vector<int>>(); | |
| 908 | - EXPECT_EQ(res, std::vector<int>({4, 5})); | |
| 909 | - | |
| 910 | - app.clear(); | |
| 911 | - opt->expected(1)->take_last(); | |
| 912 | - res = app["--long"]->as<std::vector<int>>(); | |
| 913 | - EXPECT_EQ(res, std::vector<int>({5})); | |
| 914 | - opt->take_first(); | |
| 915 | - res = app["--long"]->as<std::vector<int>>(); | |
| 916 | - EXPECT_EQ(res, std::vector<int>({4})); | |
| 917 | - | |
| 918 | - opt->expected(0, 1)->take_last(); | |
| 919 | - run(); | |
| 920 | - | |
| 921 | - EXPECT_EQ(res, std::vector<int>({4})); | |
| 922 | - res = app["--long"]->as<std::vector<int>>(); | |
| 923 | - EXPECT_EQ(res, std::vector<int>({5})); | |
| 924 | -} | |
| 925 | - | |
| 926 | 748 | TEST_F(TApp, TakeLastOptMulti_alternative_path) { |
| 927 | 749 | std::vector<int> vals; |
| 928 | 750 | app.add_option("--long", vals)->expected(2, -1)->take_last(); |
| ... | ... | @@ -1452,27 +1274,6 @@ TEST_F(TApp, CallbackFlags) { |
| 1452 | 1274 | EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); |
| 1453 | 1275 | } |
| 1454 | 1276 | |
| 1455 | -TEST_F(TApp, CallbackBoolFlags) { | |
| 1456 | - | |
| 1457 | - bool value{false}; | |
| 1458 | - | |
| 1459 | - auto func = [&value]() { value = true; }; | |
| 1460 | - | |
| 1461 | - auto cback = app.add_flag_callback("--val", func); | |
| 1462 | - args = {"--val"}; | |
| 1463 | - run(); | |
| 1464 | - EXPECT_TRUE(value); | |
| 1465 | - value = false; | |
| 1466 | - args = {"--val=false"}; | |
| 1467 | - run(); | |
| 1468 | - EXPECT_FALSE(value); | |
| 1469 | - | |
| 1470 | - EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction); | |
| 1471 | - cback->multi_option_policy(CLI::MultiOptionPolicy::Throw); | |
| 1472 | - args = {"--val", "--val=false"}; | |
| 1473 | - EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 1474 | -} | |
| 1475 | - | |
| 1476 | 1277 | TEST_F(TApp, CallbackFlagsFalse) { |
| 1477 | 1278 | std::int64_t value = 0; |
| 1478 | 1279 | |
| ... | ... | @@ -1747,107 +1548,6 @@ TEST_F(TApp, NotFileExists) { |
| 1747 | 1548 | EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); |
| 1748 | 1549 | } |
| 1749 | 1550 | |
| 1750 | -TEST_F(TApp, pair_check) { | |
| 1751 | - std::string myfile{"pair_check_file.txt"}; | |
| 1752 | - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file | |
| 1753 | - EXPECT_TRUE(ok); | |
| 1754 | - | |
| 1755 | - EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); | |
| 1756 | - std::pair<std::string, int> findex; | |
| 1757 | - | |
| 1758 | - auto v0 = CLI::ExistingFile; | |
| 1759 | - v0.application_index(0); | |
| 1760 | - auto v1 = CLI::PositiveNumber; | |
| 1761 | - v1.application_index(1); | |
| 1762 | - app.add_option("--file", findex)->check(v0)->check(v1); | |
| 1763 | - | |
| 1764 | - args = {"--file", myfile, "2"}; | |
| 1765 | - | |
| 1766 | - EXPECT_NO_THROW(run()); | |
| 1767 | - | |
| 1768 | - EXPECT_EQ(findex.first, myfile); | |
| 1769 | - EXPECT_EQ(findex.second, 2); | |
| 1770 | - | |
| 1771 | - args = {"--file", myfile, "-3"}; | |
| 1772 | - | |
| 1773 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 1774 | - | |
| 1775 | - args = {"--file", myfile, "2"}; | |
| 1776 | - std::remove(myfile.c_str()); | |
| 1777 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 1778 | -} | |
| 1779 | - | |
| 1780 | -// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present | |
| 1781 | - | |
| 1782 | -TEST_F(TApp, pair_check_take_first) { | |
| 1783 | - std::string myfile{"pair_check_file2.txt"}; | |
| 1784 | - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file | |
| 1785 | - EXPECT_TRUE(ok); | |
| 1786 | - | |
| 1787 | - EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); | |
| 1788 | - std::pair<std::string, int> findex; | |
| 1789 | - | |
| 1790 | - auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber); | |
| 1791 | - EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound); | |
| 1792 | - opt->get_validator(0)->application_index(0); | |
| 1793 | - opt->get_validator(1)->application_index(1); | |
| 1794 | - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); | |
| 1795 | - args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"}; | |
| 1796 | - // should only check the last one | |
| 1797 | - EXPECT_NO_THROW(run()); | |
| 1798 | - | |
| 1799 | - EXPECT_EQ(findex.first, myfile); | |
| 1800 | - EXPECT_EQ(findex.second, 2); | |
| 1801 | - | |
| 1802 | - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst); | |
| 1803 | - | |
| 1804 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 1805 | -} | |
| 1806 | - | |
| 1807 | -TEST_F(TApp, VectorFixedString) { | |
| 1808 | - std::vector<std::string> strvec; | |
| 1809 | - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 1810 | - | |
| 1811 | - CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3); | |
| 1812 | - EXPECT_EQ(3, opt->get_expected()); | |
| 1813 | - | |
| 1814 | - args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 1815 | - run(); | |
| 1816 | - EXPECT_EQ(3u, app.count("--string")); | |
| 1817 | - EXPECT_EQ(answer, strvec); | |
| 1818 | -} | |
| 1819 | - | |
| 1820 | -TEST_F(TApp, VectorDefaultedFixedString) { | |
| 1821 | - std::vector<std::string> strvec{"one"}; | |
| 1822 | - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 1823 | - | |
| 1824 | - CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str(); | |
| 1825 | - EXPECT_EQ(3, opt->get_expected()); | |
| 1826 | - | |
| 1827 | - args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 1828 | - run(); | |
| 1829 | - EXPECT_EQ(3u, app.count("--string")); | |
| 1830 | - EXPECT_EQ(answer, strvec); | |
| 1831 | -} | |
| 1832 | - | |
| 1833 | -TEST_F(TApp, VectorIndexedValidator) { | |
| 1834 | - std::vector<int> vvec; | |
| 1835 | - | |
| 1836 | - CLI::Option *opt = app.add_option("-v", vvec); | |
| 1837 | - | |
| 1838 | - args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; | |
| 1839 | - run(); | |
| 1840 | - EXPECT_EQ(4u, app.count("-v")); | |
| 1841 | - EXPECT_EQ(4u, vvec.size()); | |
| 1842 | - opt->check(CLI::PositiveNumber.application_index(0)); | |
| 1843 | - opt->check((!CLI::PositiveNumber).application_index(1)); | |
| 1844 | - EXPECT_NO_THROW(run()); | |
| 1845 | - EXPECT_EQ(4u, vvec.size()); | |
| 1846 | - // v[3] would be negative | |
| 1847 | - opt->check(CLI::PositiveNumber.application_index(3)); | |
| 1848 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 1849 | -} | |
| 1850 | - | |
| 1851 | 1551 | TEST_F(TApp, DefaultedResult) { |
| 1852 | 1552 | std::string sval = "NA"; |
| 1853 | 1553 | int ival{0}; |
| ... | ... | @@ -1866,93 +1566,6 @@ TEST_F(TApp, DefaultedResult) { |
| 1866 | 1566 | EXPECT_EQ(newIval, 442); |
| 1867 | 1567 | } |
| 1868 | 1568 | |
| 1869 | -TEST_F(TApp, VectorUnlimString) { | |
| 1870 | - std::vector<std::string> strvec; | |
| 1871 | - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 1872 | - | |
| 1873 | - CLI::Option *opt = app.add_option("-s,--string", strvec); | |
| 1874 | - EXPECT_EQ(1, opt->get_expected()); | |
| 1875 | - EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max()); | |
| 1876 | - | |
| 1877 | - args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 1878 | - run(); | |
| 1879 | - EXPECT_EQ(3u, app.count("--string")); | |
| 1880 | - EXPECT_EQ(answer, strvec); | |
| 1881 | - | |
| 1882 | - args = {"-s", "mystring", "mystring2", "mystring3"}; | |
| 1883 | - run(); | |
| 1884 | - EXPECT_EQ(3u, app.count("--string")); | |
| 1885 | - EXPECT_EQ(answer, strvec); | |
| 1886 | -} | |
| 1887 | - | |
| 1888 | -// From https://github.com/CLIUtils/CLI11/issues/420 | |
| 1889 | -TEST_F(TApp, stringLikeTests) { | |
| 1890 | - struct nType { | |
| 1891 | - explicit nType(const std::string &a_value) : m_value{a_value} {} | |
| 1892 | - | |
| 1893 | - explicit operator std::string() const { return std::string{"op str"}; } | |
| 1894 | - | |
| 1895 | - std::string m_value; | |
| 1896 | - }; | |
| 1897 | - | |
| 1898 | - nType m_type{"abc"}; | |
| 1899 | - app.add_option("--type", m_type, "type")->capture_default_str(); | |
| 1900 | - run(); | |
| 1901 | - | |
| 1902 | - EXPECT_EQ(app["--type"]->as<std::string>(), "op str"); | |
| 1903 | - args = {"--type", "bca"}; | |
| 1904 | - run(); | |
| 1905 | - EXPECT_EQ(std::string(m_type), "op str"); | |
| 1906 | - EXPECT_EQ(m_type.m_value, "bca"); | |
| 1907 | -} | |
| 1908 | - | |
| 1909 | -TEST_F(TApp, VectorExpectedRange) { | |
| 1910 | - std::vector<std::string> strvec; | |
| 1911 | - | |
| 1912 | - CLI::Option *opt = app.add_option("--string", strvec); | |
| 1913 | - opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw); | |
| 1914 | - | |
| 1915 | - args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 1916 | - run(); | |
| 1917 | - EXPECT_EQ(3u, app.count("--string")); | |
| 1918 | - | |
| 1919 | - args = {"--string", "mystring"}; | |
| 1920 | - EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 1921 | - | |
| 1922 | - args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"}; | |
| 1923 | - EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 1924 | - | |
| 1925 | - EXPECT_EQ(opt->get_expected_max(), 4); | |
| 1926 | - EXPECT_EQ(opt->get_expected_min(), 2); | |
| 1927 | - opt->expected(4, 2); // just test the handling of reversed arguments | |
| 1928 | - EXPECT_EQ(opt->get_expected_max(), 4); | |
| 1929 | - EXPECT_EQ(opt->get_expected_min(), 2); | |
| 1930 | - opt->expected(-5); | |
| 1931 | - EXPECT_EQ(opt->get_expected_max(), 5); | |
| 1932 | - EXPECT_EQ(opt->get_expected_min(), 5); | |
| 1933 | - opt->expected(-5, 7); | |
| 1934 | - EXPECT_EQ(opt->get_expected_max(), 7); | |
| 1935 | - EXPECT_EQ(opt->get_expected_min(), 5); | |
| 1936 | -} | |
| 1937 | - | |
| 1938 | -TEST_F(TApp, VectorFancyOpts) { | |
| 1939 | - std::vector<std::string> strvec; | |
| 1940 | - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 1941 | - | |
| 1942 | - CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3); | |
| 1943 | - EXPECT_EQ(3, opt->get_expected()); | |
| 1944 | - | |
| 1945 | - args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 1946 | - run(); | |
| 1947 | - EXPECT_EQ(3u, app.count("--string")); | |
| 1948 | - EXPECT_EQ(answer, strvec); | |
| 1949 | - | |
| 1950 | - args = {"one", "two"}; | |
| 1951 | - EXPECT_THROW(run(), CLI::RequiredError); | |
| 1952 | - | |
| 1953 | - EXPECT_THROW(run(), CLI::ParseError); | |
| 1954 | -} | |
| 1955 | - | |
| 1956 | 1569 | TEST_F(TApp, OriginalOrder) { |
| 1957 | 1570 | std::vector<int> st1; |
| 1958 | 1571 | CLI::Option *op1 = app.add_option("-a", st1); |
| ... | ... | @@ -2406,170 +2019,6 @@ TEST_F(TApp, EachItem) { |
| 2406 | 2019 | EXPECT_EQ(results, dummy); |
| 2407 | 2020 | } |
| 2408 | 2021 | |
| 2409 | -// #87 | |
| 2410 | -TEST_F(TApp, CustomDoubleOption) { | |
| 2411 | - | |
| 2412 | - std::pair<int, double> custom_opt; | |
| 2413 | - | |
| 2414 | - auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) { | |
| 2415 | - custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; | |
| 2416 | - return true; | |
| 2417 | - }); | |
| 2418 | - opt->type_name("INT FLOAT")->type_size(2); | |
| 2419 | - | |
| 2420 | - args = {"12", "1.5"}; | |
| 2421 | - | |
| 2422 | - run(); | |
| 2423 | - EXPECT_EQ(custom_opt.first, 12); | |
| 2424 | - EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); | |
| 2425 | -} | |
| 2426 | - | |
| 2427 | -// now with tuple support this is possible | |
| 2428 | -TEST_F(TApp, CustomDoubleOptionAlt) { | |
| 2429 | - | |
| 2430 | - std::pair<int, double> custom_opt; | |
| 2431 | - | |
| 2432 | - app.add_option("posit", custom_opt); | |
| 2433 | - | |
| 2434 | - args = {"12", "1.5"}; | |
| 2435 | - | |
| 2436 | - run(); | |
| 2437 | - EXPECT_EQ(custom_opt.first, 12); | |
| 2438 | - EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); | |
| 2439 | -} | |
| 2440 | - | |
| 2441 | -// now with independent type sizes and expected this is possible | |
| 2442 | -TEST_F(TApp, vectorPair) { | |
| 2443 | - | |
| 2444 | - std::vector<std::pair<int, std::string>> custom_opt; | |
| 2445 | - | |
| 2446 | - auto opt = app.add_option("--dict", custom_opt); | |
| 2447 | - | |
| 2448 | - args = {"--dict", "1", "str1", "--dict", "3", "str3"}; | |
| 2449 | - | |
| 2450 | - run(); | |
| 2451 | - EXPECT_EQ(custom_opt.size(), 2u); | |
| 2452 | - EXPECT_EQ(custom_opt[0].first, 1); | |
| 2453 | - EXPECT_EQ(custom_opt[1].second, "str3"); | |
| 2454 | - | |
| 2455 | - args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"}; | |
| 2456 | - run(); | |
| 2457 | - EXPECT_EQ(custom_opt.size(), 3u); | |
| 2458 | - EXPECT_EQ(custom_opt[2].first, -1); | |
| 2459 | - EXPECT_EQ(custom_opt[2].second, "str4"); | |
| 2460 | - opt->check(CLI::PositiveNumber.application_index(0)); | |
| 2461 | - | |
| 2462 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 2463 | -} | |
| 2464 | - | |
| 2465 | -TEST_F(TApp, vectorPairFail) { | |
| 2466 | - | |
| 2467 | - std::vector<std::pair<int, std::string>> custom_opt; | |
| 2468 | - | |
| 2469 | - app.add_option("--dict", custom_opt); | |
| 2470 | - | |
| 2471 | - args = {"--dict", "1", "str1", "--dict", "str3", "1"}; | |
| 2472 | - | |
| 2473 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 2474 | -} | |
| 2475 | - | |
| 2476 | -TEST_F(TApp, vectorPairTypeRange) { | |
| 2477 | - | |
| 2478 | - std::vector<std::pair<int, std::string>> custom_opt; | |
| 2479 | - | |
| 2480 | - auto opt = app.add_option("--dict", custom_opt); | |
| 2481 | - | |
| 2482 | - opt->type_size(2, 1); // just test switched arguments | |
| 2483 | - EXPECT_EQ(opt->get_type_size_min(), 1); | |
| 2484 | - EXPECT_EQ(opt->get_type_size_max(), 2); | |
| 2485 | - | |
| 2486 | - args = {"--dict", "1", "str1", "--dict", "3", "str3"}; | |
| 2487 | - | |
| 2488 | - run(); | |
| 2489 | - EXPECT_EQ(custom_opt.size(), 2u); | |
| 2490 | - EXPECT_EQ(custom_opt[0].first, 1); | |
| 2491 | - EXPECT_EQ(custom_opt[1].second, "str3"); | |
| 2492 | - | |
| 2493 | - args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; | |
| 2494 | - run(); | |
| 2495 | - EXPECT_EQ(custom_opt.size(), 3u); | |
| 2496 | - EXPECT_TRUE(custom_opt[1].second.empty()); | |
| 2497 | - EXPECT_EQ(custom_opt[2].first, -1); | |
| 2498 | - EXPECT_EQ(custom_opt[2].second, "str4"); | |
| 2499 | - | |
| 2500 | - opt->type_size(-2, -1); // test negative arguments | |
| 2501 | - EXPECT_EQ(opt->get_type_size_min(), 1); | |
| 2502 | - EXPECT_EQ(opt->get_type_size_max(), 2); | |
| 2503 | - // this type size spec should run exactly as before | |
| 2504 | - run(); | |
| 2505 | - EXPECT_EQ(custom_opt.size(), 3u); | |
| 2506 | - EXPECT_TRUE(custom_opt[1].second.empty()); | |
| 2507 | - EXPECT_EQ(custom_opt[2].first, -1); | |
| 2508 | - EXPECT_EQ(custom_opt[2].second, "str4"); | |
| 2509 | -} | |
| 2510 | - | |
| 2511 | -// now with independent type sizes and expected this is possible | |
| 2512 | -TEST_F(TApp, vectorTuple) { | |
| 2513 | - | |
| 2514 | - std::vector<std::tuple<int, std::string, double>> custom_opt; | |
| 2515 | - | |
| 2516 | - auto opt = app.add_option("--dict", custom_opt); | |
| 2517 | - | |
| 2518 | - args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; | |
| 2519 | - | |
| 2520 | - run(); | |
| 2521 | - EXPECT_EQ(custom_opt.size(), 2u); | |
| 2522 | - EXPECT_EQ(std::get<0>(custom_opt[0]), 1); | |
| 2523 | - EXPECT_EQ(std::get<1>(custom_opt[1]), "str3"); | |
| 2524 | - EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7); | |
| 2525 | - | |
| 2526 | - args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; | |
| 2527 | - run(); | |
| 2528 | - EXPECT_EQ(custom_opt.size(), 3u); | |
| 2529 | - EXPECT_EQ(std::get<0>(custom_opt[2]), -1); | |
| 2530 | - EXPECT_EQ(std::get<1>(custom_opt[2]), "str4"); | |
| 2531 | - EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87); | |
| 2532 | - opt->check(CLI::PositiveNumber.application_index(0)); | |
| 2533 | - | |
| 2534 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 2535 | - | |
| 2536 | - args.back() = "haha"; | |
| 2537 | - args[9] = "45"; | |
| 2538 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 2539 | -} | |
| 2540 | - | |
| 2541 | -// now with independent type sizes and expected this is possible | |
| 2542 | -TEST_F(TApp, vectorVector) { | |
| 2543 | - | |
| 2544 | - std::vector<std::vector<int>> custom_opt; | |
| 2545 | - | |
| 2546 | - auto opt = app.add_option("--dict", custom_opt); | |
| 2547 | - | |
| 2548 | - args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; | |
| 2549 | - | |
| 2550 | - run(); | |
| 2551 | - EXPECT_EQ(custom_opt.size(), 2u); | |
| 2552 | - EXPECT_EQ(custom_opt[0].size(), 3u); | |
| 2553 | - EXPECT_EQ(custom_opt[1].size(), 2u); | |
| 2554 | - | |
| 2555 | - args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", | |
| 2556 | - "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; | |
| 2557 | - run(); | |
| 2558 | - EXPECT_EQ(custom_opt.size(), 4u); | |
| 2559 | - EXPECT_EQ(custom_opt[0].size(), 3u); | |
| 2560 | - EXPECT_EQ(custom_opt[1].size(), 2u); | |
| 2561 | - EXPECT_EQ(custom_opt[2].size(), 1u); | |
| 2562 | - EXPECT_EQ(custom_opt[3].size(), 10u); | |
| 2563 | - opt->check(CLI::PositiveNumber.application_index(9)); | |
| 2564 | - | |
| 2565 | - EXPECT_THROW(run(), CLI::ValidationError); | |
| 2566 | - args.pop_back(); | |
| 2567 | - EXPECT_NO_THROW(run()); | |
| 2568 | - | |
| 2569 | - args.back() = "haha"; | |
| 2570 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 2571 | -} | |
| 2572 | - | |
| 2573 | 2022 | // #128 |
| 2574 | 2023 | TEST_F(TApp, RepeatingMultiArgumentOptions) { |
| 2575 | 2024 | std::vector<std::string> entries; | ... | ... |
tests/BoostOptionTypeTest.cpp
0 β 100644
| 1 | +#include "app_helper.hpp" | |
| 2 | +#include <boost/container/flat_map.hpp> | |
| 3 | +#include <boost/container/flat_set.hpp> | |
| 4 | +#include <boost/container/slist.hpp> | |
| 5 | +#include <boost/container/small_vector.hpp> | |
| 6 | +#include <boost/container/stable_vector.hpp> | |
| 7 | +#include <boost/container/static_vector.hpp> | |
| 8 | +#include <boost/container/vector.hpp> | |
| 9 | +#include <string> | |
| 10 | +#include <vector> | |
| 11 | + | |
| 12 | +#include "gmock/gmock.h" | |
| 13 | + | |
| 14 | +using namespace boost::container; | |
| 15 | + | |
| 16 | +template <class T> class TApp_container_single_boost : public TApp { | |
| 17 | + public: | |
| 18 | + using container_type = T; | |
| 19 | + container_type cval{}; | |
| 20 | + TApp_container_single_boost() : TApp(){}; | |
| 21 | +}; | |
| 22 | + | |
| 23 | +using containerTypes_single_boost = | |
| 24 | + ::testing::Types<small_vector<int, 2>, small_vector<int, 3>, flat_set<int>, stable_vector<int>, slist<int>>; | |
| 25 | + | |
| 26 | +TYPED_TEST_SUITE(TApp_container_single_boost, containerTypes_single_boost, ); | |
| 27 | + | |
| 28 | +TYPED_TEST(TApp_container_single_boost, containerInt_boost) { | |
| 29 | + | |
| 30 | + auto &cv = TApp_container_single_boost<TypeParam>::cval; | |
| 31 | + CLI::Option *opt = (TApp::app).add_option("-v", cv); | |
| 32 | + | |
| 33 | + TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; | |
| 34 | + TApp::run(); | |
| 35 | + EXPECT_EQ(4u, (TApp::app).count("-v")); | |
| 36 | + EXPECT_EQ(4u, cv.size()); | |
| 37 | + opt->check(CLI::PositiveNumber.application_index(0)); | |
| 38 | + opt->check((!CLI::PositiveNumber).application_index(1)); | |
| 39 | + EXPECT_NO_THROW(TApp::run()); | |
| 40 | + EXPECT_EQ(4u, cv.size()); | |
| 41 | + // v[3] would be negative | |
| 42 | + opt->check(CLI::PositiveNumber.application_index(3)); | |
| 43 | + EXPECT_THROW(TApp::run(), CLI::ValidationError); | |
| 44 | +} | |
| 45 | + | |
| 46 | +template <class T> class TApp_container_pair_boost : public TApp { | |
| 47 | + public: | |
| 48 | + using container_type = T; | |
| 49 | + container_type cval{}; | |
| 50 | + TApp_container_pair_boost() : TApp(){}; | |
| 51 | +}; | |
| 52 | + | |
| 53 | +using isp = std::pair<int, std::string>; | |
| 54 | +using containerTypes_pair_boost = ::testing:: | |
| 55 | + Types<stable_vector<isp>, small_vector<isp, 2>, flat_set<isp>, slist<isp>, vector<isp>, flat_map<int, std::string>>; | |
| 56 | + | |
| 57 | +TYPED_TEST_SUITE(TApp_container_pair_boost, containerTypes_pair_boost, ); | |
| 58 | + | |
| 59 | +TYPED_TEST(TApp_container_pair_boost, containerPair_boost) { | |
| 60 | + | |
| 61 | + auto &cv = TApp_container_pair_boost<TypeParam>::cval; | |
| 62 | + (TApp::app).add_option("--dict", cv); | |
| 63 | + | |
| 64 | + TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"}; | |
| 65 | + | |
| 66 | + TApp::run(); | |
| 67 | + EXPECT_EQ(cv.size(), 2u); | |
| 68 | + | |
| 69 | + TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; | |
| 70 | + TApp::run(); | |
| 71 | + EXPECT_EQ(cv.size(), 3u); | |
| 72 | +} | |
| 73 | + | |
| 74 | +template <class T> class TApp_container_tuple_boost : public TApp { | |
| 75 | + public: | |
| 76 | + using container_type = T; | |
| 77 | + container_type cval{}; | |
| 78 | + TApp_container_tuple_boost() : TApp(){}; | |
| 79 | +}; | |
| 80 | + | |
| 81 | +using tup_obj = std::tuple<int, std::string, double>; | |
| 82 | +using containerTypes_tuple_boost = | |
| 83 | + ::testing::Types<small_vector<tup_obj, 3>, stable_vector<tup_obj>, flat_set<tup_obj>, slist<tup_obj>>; | |
| 84 | + | |
| 85 | +TYPED_TEST_SUITE(TApp_container_tuple_boost, containerTypes_tuple_boost, ); | |
| 86 | + | |
| 87 | +TYPED_TEST(TApp_container_tuple_boost, containerTuple_boost) { | |
| 88 | + | |
| 89 | + auto &cv = TApp_container_tuple_boost<TypeParam>::cval; | |
| 90 | + (TApp::app).add_option("--dict", cv); | |
| 91 | + | |
| 92 | + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; | |
| 93 | + | |
| 94 | + TApp::run(); | |
| 95 | + EXPECT_EQ(cv.size(), 2u); | |
| 96 | + | |
| 97 | + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; | |
| 98 | + TApp::run(); | |
| 99 | + EXPECT_EQ(cv.size(), 3u); | |
| 100 | +} | |
| 101 | + | |
| 102 | +using icontainer1 = vector<int>; | |
| 103 | +using icontainer2 = flat_set<int>; | |
| 104 | +using icontainer3 = slist<int>; | |
| 105 | +using containerTypes_container_boost = ::testing::Types<std::vector<icontainer1>, | |
| 106 | + slist<icontainer1>, | |
| 107 | + flat_set<icontainer1>, | |
| 108 | + small_vector<icontainer1, 2>, | |
| 109 | + std::vector<icontainer2>, | |
| 110 | + slist<icontainer2>, | |
| 111 | + flat_set<icontainer2>, | |
| 112 | + stable_vector<icontainer2>, | |
| 113 | + static_vector<icontainer3, 10>, | |
| 114 | + slist<icontainer3>, | |
| 115 | + flat_set<icontainer3>, | |
| 116 | + static_vector<icontainer3, 10>>; | |
| 117 | + | |
| 118 | +template <class T> class TApp_container_container_boost : public TApp { | |
| 119 | + public: | |
| 120 | + using container_type = T; | |
| 121 | + container_type cval{}; | |
| 122 | + TApp_container_container_boost() : TApp(){}; | |
| 123 | +}; | |
| 124 | + | |
| 125 | +TYPED_TEST_SUITE(TApp_container_container_boost, containerTypes_container_boost, ); | |
| 126 | + | |
| 127 | +TYPED_TEST(TApp_container_container_boost, containerContainer_boost) { | |
| 128 | + | |
| 129 | + auto &cv = TApp_container_container_boost<TypeParam>::cval; | |
| 130 | + (TApp::app).add_option("--dict", cv); | |
| 131 | + | |
| 132 | + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; | |
| 133 | + | |
| 134 | + TApp::run(); | |
| 135 | + EXPECT_EQ(cv.size(), 2u); | |
| 136 | + | |
| 137 | + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", | |
| 138 | + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; | |
| 139 | + TApp::run(); | |
| 140 | + EXPECT_EQ(cv.size(), 4u); | |
| 141 | +} | ... | ... |
tests/CMakeLists.txt
| ... | ... | @@ -32,13 +32,19 @@ endif() |
| 32 | 32 | set(GOOGLE_TEST_INDIVIDUAL OFF) |
| 33 | 33 | include(AddGoogletest) |
| 34 | 34 | |
| 35 | +# Add boost to test boost::optional if available | |
| 36 | +find_package(Boost 1.61) | |
| 37 | + | |
| 38 | +set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>) | |
| 39 | + | |
| 35 | 40 | set(CLI11_TESTS |
| 36 | 41 | HelpersTest |
| 37 | 42 | ConfigFileTest |
| 43 | + OptionTypeTest | |
| 38 | 44 | SimpleTest |
| 39 | 45 | AppTest |
| 40 | 46 | SetTest |
| 41 | - TransformTest | |
| 47 | + TransformTest | |
| 42 | 48 | CreationTest |
| 43 | 49 | SubcommandTest |
| 44 | 50 | HelpTest |
| ... | ... | @@ -47,6 +53,7 @@ set(CLI11_TESTS |
| 47 | 53 | OptionalTest |
| 48 | 54 | DeprecatedTest |
| 49 | 55 | StringParseTest |
| 56 | + ComplexTypeTest | |
| 50 | 57 | TrueFalseTest |
| 51 | 58 | OptionGroupTest |
| 52 | 59 | ) |
| ... | ... | @@ -55,6 +62,10 @@ if(WIN32) |
| 55 | 62 | list(APPEND CLI11_TESTS WindowsTest) |
| 56 | 63 | endif() |
| 57 | 64 | |
| 65 | +if (Boost_FOUND) | |
| 66 | + list(APPEND CLI11_TESTS BoostOptionTypeTest) | |
| 67 | +endif() | |
| 68 | + | |
| 58 | 69 | set(CLI11_MULTIONLY_TESTS TimerTest) |
| 59 | 70 | |
| 60 | 71 | # Only affects current directory, so safe |
| ... | ... | @@ -140,20 +151,29 @@ file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake" |
| 140 | 151 | "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")" |
| 141 | 152 | ) |
| 142 | 153 | |
| 143 | -# Add boost to test boost::optional if available | |
| 144 | -find_package(Boost 1.61) | |
| 145 | - | |
| 146 | -set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>) | |
| 147 | - | |
| 148 | 154 | target_compile_definitions(informational PRIVATE ${boost-optional-def}) |
| 149 | 155 | target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def}) |
| 150 | 156 | |
| 157 | +message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}") | |
| 158 | + | |
| 151 | 159 | if(TARGET Boost::boost) |
| 160 | + message(STATUS "including boost target") | |
| 152 | 161 | target_link_libraries(informational PRIVATE Boost::boost) |
| 153 | 162 | target_link_libraries(OptionalTest PRIVATE Boost::boost) |
| 163 | + target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost) | |
| 164 | + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) | |
| 165 | + target_link_libraries(OptionalTest_Single PRIVATE Boost::boost) | |
| 166 | + target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost) | |
| 167 | + endif() | |
| 154 | 168 | elseif(BOOST_FOUND) |
| 169 | +message(STATUS "no boost target") | |
| 155 | 170 | target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS}) |
| 156 | 171 | target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS}) |
| 172 | + target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS}) | |
| 173 | + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) | |
| 174 | + target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) | |
| 175 | + target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) | |
| 176 | + endif() | |
| 157 | 177 | endif() |
| 158 | 178 | |
| 159 | 179 | if(CMAKE_BUILD_TYPE STREQUAL Coverage) | ... | ... |
tests/ComplexTypeTest.cpp
0 β 100644
| 1 | +#include "app_helper.hpp" | |
| 2 | +#include "gmock/gmock.h" | |
| 3 | +#include <complex> | |
| 4 | +#include <cstdint> | |
| 5 | + | |
| 6 | +using ::testing::HasSubstr; | |
| 7 | + | |
| 8 | +using cx = std::complex<double>; | |
| 9 | + | |
| 10 | +CLI::Option * | |
| 11 | +add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { | |
| 12 | + CLI::callback_t fun = [&variable](CLI::results_t res) { | |
| 13 | + double x, y; | |
| 14 | + bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); | |
| 15 | + if(worked) | |
| 16 | + variable = cx(x, y); | |
| 17 | + return worked; | |
| 18 | + }; | |
| 19 | + | |
| 20 | + CLI::Option *opt = app.add_option(name, fun, description, defaulted); | |
| 21 | + opt->type_name("COMPLEX")->type_size(2); | |
| 22 | + if(defaulted) { | |
| 23 | + std::stringstream out; | |
| 24 | + out << variable; | |
| 25 | + opt->default_str(out.str()); | |
| 26 | + } | |
| 27 | + return opt; | |
| 28 | +} | |
| 29 | + | |
| 30 | +TEST_F(TApp, AddingComplexParser) { | |
| 31 | + | |
| 32 | + cx comp{0, 0}; | |
| 33 | + add_option(app, "-c,--complex", comp); | |
| 34 | + args = {"-c", "1.5", "2.5"}; | |
| 35 | + | |
| 36 | + run(); | |
| 37 | + | |
| 38 | + EXPECT_DOUBLE_EQ(1.5, comp.real()); | |
| 39 | + EXPECT_DOUBLE_EQ(2.5, comp.imag()); | |
| 40 | +} | |
| 41 | + | |
| 42 | +TEST_F(TApp, DefaultedComplex) { | |
| 43 | + | |
| 44 | + cx comp{1, 2}; | |
| 45 | + add_option(app, "-c,--complex", comp, "", true); | |
| 46 | + args = {"-c", "4", "3"}; | |
| 47 | + | |
| 48 | + std::string help = app.help(); | |
| 49 | + EXPECT_THAT(help, HasSubstr("1")); | |
| 50 | + EXPECT_THAT(help, HasSubstr("2")); | |
| 51 | + | |
| 52 | + EXPECT_DOUBLE_EQ(1, comp.real()); | |
| 53 | + EXPECT_DOUBLE_EQ(2, comp.imag()); | |
| 54 | + | |
| 55 | + run(); | |
| 56 | + | |
| 57 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 58 | + EXPECT_DOUBLE_EQ(3, comp.imag()); | |
| 59 | +} | |
| 60 | + | |
| 61 | +// an example of custom complex number converter that can be used to add new parsing options | |
| 62 | +#if defined(__has_include) | |
| 63 | +#if __has_include(<regex>) | |
| 64 | +// an example of custom converter that can be used to add new parsing options | |
| 65 | +#define HAS_REGEX_INCLUDE | |
| 66 | +#endif | |
| 67 | +#endif | |
| 68 | + | |
| 69 | +#ifdef HAS_REGEX_INCLUDE | |
| 70 | +// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would | |
| 71 | +// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to | |
| 72 | +// simplify compilation and prevent a much more complicated #if expression | |
| 73 | +#include <regex> | |
| 74 | +namespace CLI { | |
| 75 | +namespace detail { | |
| 76 | + | |
| 77 | +// On MSVC and possibly some other new compilers this can be a free standing function without the template | |
| 78 | +// specialization but this is compiler dependent | |
| 79 | +template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) { | |
| 80 | + // regular expression to handle complex numbers of various formats | |
| 81 | + static const std::regex creg( | |
| 82 | + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); | |
| 83 | + | |
| 84 | + std::smatch m; | |
| 85 | + double x{0.0}, y{0.0}; | |
| 86 | + bool worked; | |
| 87 | + std::regex_search(input, m, creg); | |
| 88 | + if(m.size() == 9) { | |
| 89 | + worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); | |
| 90 | + if(worked) { | |
| 91 | + if(*m[5].first == '-') { | |
| 92 | + y = -y; | |
| 93 | + } | |
| 94 | + } | |
| 95 | + } else { | |
| 96 | + if((input.back() == 'j') || (input.back() == 'i')) { | |
| 97 | + auto strval = input.substr(0, input.size() - 1); | |
| 98 | + CLI::detail::trim(strval); | |
| 99 | + worked = CLI::detail::lexical_cast(strval, y); | |
| 100 | + } else { | |
| 101 | + std::string ival = input; | |
| 102 | + CLI::detail::trim(ival); | |
| 103 | + worked = CLI::detail::lexical_cast(ival, x); | |
| 104 | + } | |
| 105 | + } | |
| 106 | + if(worked) { | |
| 107 | + output = cx{x, y}; | |
| 108 | + } | |
| 109 | + return worked; | |
| 110 | +} | |
| 111 | +} // namespace detail | |
| 112 | +} // namespace CLI | |
| 113 | + | |
| 114 | +TEST_F(TApp, AddingComplexParserDetail) { | |
| 115 | + | |
| 116 | + bool skip_tests = false; | |
| 117 | + try { // check if the library actually supports regex, it is possible to link against a non working regex in the | |
| 118 | + // standard library | |
| 119 | + std::smatch m; | |
| 120 | + std::string input = "1.5+2.5j"; | |
| 121 | + static const std::regex creg( | |
| 122 | + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); | |
| 123 | + | |
| 124 | + auto rsearch = std::regex_search(input, m, creg); | |
| 125 | + if(!rsearch) { | |
| 126 | + skip_tests = true; | |
| 127 | + } else { | |
| 128 | + EXPECT_EQ(m.size(), 9u); | |
| 129 | + } | |
| 130 | + | |
| 131 | + } catch(...) { | |
| 132 | + skip_tests = true; | |
| 133 | + } | |
| 134 | + static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation"); | |
| 135 | + if(!skip_tests) { | |
| 136 | + cx comp{0, 0}; | |
| 137 | + | |
| 138 | + app.add_option("-c,--complex", comp, "add a complex number option"); | |
| 139 | + args = {"-c", "1.5+2.5j"}; | |
| 140 | + | |
| 141 | + run(); | |
| 142 | + | |
| 143 | + EXPECT_DOUBLE_EQ(1.5, comp.real()); | |
| 144 | + EXPECT_DOUBLE_EQ(2.5, comp.imag()); | |
| 145 | + args = {"-c", "1.5-2.5j"}; | |
| 146 | + | |
| 147 | + run(); | |
| 148 | + | |
| 149 | + EXPECT_DOUBLE_EQ(1.5, comp.real()); | |
| 150 | + EXPECT_DOUBLE_EQ(-2.5, comp.imag()); | |
| 151 | + } | |
| 152 | +} | |
| 153 | +#endif | |
| 154 | +// defining a new complex class | |
| 155 | +class complex_new { | |
| 156 | + public: | |
| 157 | + complex_new() = default; | |
| 158 | + complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {}; | |
| 159 | + double real() { return val1_; } | |
| 160 | + double imag() { return val2_; } | |
| 161 | + | |
| 162 | + private: | |
| 163 | + double val1_{0.0}; | |
| 164 | + double val2_{0.0}; | |
| 165 | +}; | |
| 166 | + | |
| 167 | +TEST_F(TApp, newComplex) { | |
| 168 | + complex_new cval; | |
| 169 | + static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type"); | |
| 170 | + static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number, | |
| 171 | + "complex new does not result in complex number categorization"); | |
| 172 | + app.add_option("-c,--complex", cval, "add a complex number option"); | |
| 173 | + args = {"-c", "1.5+2.5j"}; | |
| 174 | + | |
| 175 | + run(); | |
| 176 | + | |
| 177 | + EXPECT_DOUBLE_EQ(1.5, cval.real()); | |
| 178 | + EXPECT_DOUBLE_EQ(2.5, cval.imag()); | |
| 179 | + args = {"-c", "1.5-2.5j"}; | |
| 180 | + | |
| 181 | + run(); | |
| 182 | + | |
| 183 | + EXPECT_DOUBLE_EQ(1.5, cval.real()); | |
| 184 | + EXPECT_DOUBLE_EQ(-2.5, cval.imag()); | |
| 185 | +} | ... | ... |
tests/HelpersTest.cpp
| ... | ... | @@ -6,8 +6,10 @@ |
| 6 | 6 | #include <cstdint> |
| 7 | 7 | #include <cstdio> |
| 8 | 8 | #include <fstream> |
| 9 | +#include <map> | |
| 9 | 10 | #include <string> |
| 10 | 11 | #include <tuple> |
| 12 | +#include <unordered_map> | |
| 11 | 13 | #include <utility> |
| 12 | 14 | |
| 13 | 15 | class NotStreamable {}; |
| ... | ... | @@ -52,6 +54,57 @@ TEST(TypeTools, type_size) { |
| 52 | 54 | EXPECT_EQ(V, 5); |
| 53 | 55 | V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value; |
| 54 | 56 | EXPECT_EQ(V, 2); |
| 57 | + V = CLI::detail::type_count<std::tuple<std::pair<std::string, double>>>::value; | |
| 58 | + EXPECT_EQ(V, 2); | |
| 59 | + V = CLI::detail::type_count<std::tuple<int, std::pair<std::string, double>>>::value; | |
| 60 | + EXPECT_EQ(V, 3); | |
| 61 | + V = CLI::detail::type_count<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value; | |
| 62 | + EXPECT_EQ(V, 4); | |
| 63 | + // maps | |
| 64 | + V = CLI::detail::type_count<std::map<int, std::pair<int, double>>>::value; | |
| 65 | + EXPECT_EQ(V, 3); | |
| 66 | + // three level tuples | |
| 67 | + V = CLI::detail::type_count<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value; | |
| 68 | + EXPECT_EQ(V, 5); | |
| 69 | + V = CLI::detail::type_count<std::pair<int, std::vector<int>>>::value; | |
| 70 | + EXPECT_GE(V, CLI::detail::expected_max_vector_size); | |
| 71 | + V = CLI::detail::type_count<std::vector<std::vector<int>>>::value; | |
| 72 | + EXPECT_EQ(V, CLI::detail::expected_max_vector_size); | |
| 73 | +} | |
| 74 | + | |
| 75 | +TEST(TypeTools, type_size_min) { | |
| 76 | + auto V = CLI::detail::type_count_min<int>::value; | |
| 77 | + EXPECT_EQ(V, 1); | |
| 78 | + V = CLI::detail::type_count_min<void>::value; | |
| 79 | + EXPECT_EQ(V, 0); | |
| 80 | + V = CLI::detail::type_count_min<std::vector<double>>::value; | |
| 81 | + EXPECT_EQ(V, 1); | |
| 82 | + V = CLI::detail::type_count_min<std::tuple<double, int>>::value; | |
| 83 | + EXPECT_EQ(V, 2); | |
| 84 | + V = CLI::detail::type_count_min<std::tuple<std::string, double, int>>::value; | |
| 85 | + EXPECT_EQ(V, 3); | |
| 86 | + V = CLI::detail::type_count_min<std::array<std::string, 5>>::value; | |
| 87 | + EXPECT_EQ(V, 5); | |
| 88 | + V = CLI::detail::type_count_min<std::vector<std::pair<std::string, double>>>::value; | |
| 89 | + EXPECT_EQ(V, 2); | |
| 90 | + V = CLI::detail::type_count_min<std::tuple<std::pair<std::string, double>>>::value; | |
| 91 | + EXPECT_EQ(V, 2); | |
| 92 | + V = CLI::detail::type_count_min<std::tuple<int, std::pair<std::string, double>>>::value; | |
| 93 | + EXPECT_EQ(V, 3); | |
| 94 | + V = CLI::detail::type_count_min<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value; | |
| 95 | + EXPECT_EQ(V, 4); | |
| 96 | + // maps | |
| 97 | + V = CLI::detail::type_count_min<std::map<int, std::pair<int, double>>>::value; | |
| 98 | + EXPECT_EQ(V, 3); | |
| 99 | + // three level tuples | |
| 100 | + V = CLI::detail::type_count_min<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value; | |
| 101 | + EXPECT_EQ(V, 5); | |
| 102 | + V = CLI::detail::type_count_min<std::pair<int, std::vector<int>>>::value; | |
| 103 | + EXPECT_EQ(V, 2); | |
| 104 | + V = CLI::detail::type_count_min<std::vector<std::vector<int>>>::value; | |
| 105 | + EXPECT_EQ(V, 1); | |
| 106 | + V = CLI::detail::type_count_min<std::vector<std::vector<std::pair<int, int>>>>::value; | |
| 107 | + EXPECT_EQ(V, 2); | |
| 55 | 108 | } |
| 56 | 109 | |
| 57 | 110 | TEST(TypeTools, expected_count) { |
| ... | ... | @@ -862,6 +915,10 @@ TEST(Types, TypeName) { |
| 862 | 915 | CLI::detail::object_category::tuple_value, |
| 863 | 916 | "pair<int,string> does not read like a tuple"); |
| 864 | 917 | |
| 918 | + static_assert(CLI::detail::classify_object<std::tuple<std::string, double>>::value == | |
| 919 | + CLI::detail::object_category::tuple_value, | |
| 920 | + "tuple<string,double> does not read like a tuple"); | |
| 921 | + | |
| 865 | 922 | std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>(); |
| 866 | 923 | EXPECT_EQ("[INT,TEXT]", pair_name); |
| 867 | 924 | |
| ... | ... | @@ -869,7 +926,7 @@ TEST(Types, TypeName) { |
| 869 | 926 | EXPECT_EQ("UINT", vector_name); |
| 870 | 927 | |
| 871 | 928 | auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value; |
| 872 | - EXPECT_EQ(vclass, CLI::detail::object_category::vector_value); | |
| 929 | + EXPECT_EQ(vclass, CLI::detail::object_category::container_value); | |
| 873 | 930 | |
| 874 | 931 | auto tclass = CLI::detail::classify_object<std::tuple<double>>::value; |
| 875 | 932 | EXPECT_EQ(tclass, CLI::detail::object_category::number_constructible); |
| ... | ... | @@ -883,6 +940,18 @@ TEST(Types, TypeName) { |
| 883 | 940 | tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>(); |
| 884 | 941 | EXPECT_EQ("[INT,TEXT]", tuple_name); |
| 885 | 942 | |
| 943 | + tuple_name = CLI::detail::type_name<std::tuple<const int, std::string>>(); | |
| 944 | + EXPECT_EQ("[INT,TEXT]", tuple_name); | |
| 945 | + | |
| 946 | + tuple_name = CLI::detail::type_name<const std::tuple<int, std::string>>(); | |
| 947 | + EXPECT_EQ("[INT,TEXT]", tuple_name); | |
| 948 | + | |
| 949 | + tuple_name = CLI::detail::type_name<std::tuple<std::string, double>>(); | |
| 950 | + EXPECT_EQ("[TEXT,FLOAT]", tuple_name); | |
| 951 | + | |
| 952 | + tuple_name = CLI::detail::type_name<const std::tuple<std::string, double>>(); | |
| 953 | + EXPECT_EQ("[TEXT,FLOAT]", tuple_name); | |
| 954 | + | |
| 886 | 955 | tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>(); |
| 887 | 956 | EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name); |
| 888 | 957 | |
| ... | ... | @@ -911,6 +980,8 @@ TEST(Types, TypeName) { |
| 911 | 980 | "tuple<test> does not classify as a tuple"); |
| 912 | 981 | std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>(); |
| 913 | 982 | EXPECT_EQ("ENUM", enum_name2); |
| 983 | + std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>(); | |
| 984 | + EXPECT_EQ("[INT,[TEXT,FLOAT]]", umapName); | |
| 914 | 985 | } |
| 915 | 986 | |
| 916 | 987 | TEST(Types, OverflowSmall) { |
| ... | ... | @@ -995,11 +1066,11 @@ TEST(Types, LexicalCastParsable) { |
| 995 | 1066 | std::complex<double> output; |
| 996 | 1067 | EXPECT_TRUE(CLI::detail::lexical_cast(input, output)); |
| 997 | 1068 | EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble |
| 998 | - EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const | |
| 1069 | + EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + gcc 4.8 due to missing const | |
| 999 | 1070 | |
| 1000 | 1071 | EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output)); |
| 1001 | 1072 | EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble |
| 1002 | - EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const | |
| 1073 | + EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + gcc 4.8 due to missing const | |
| 1003 | 1074 | |
| 1004 | 1075 | EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); |
| 1005 | 1076 | EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); |
| ... | ... | @@ -1173,6 +1244,26 @@ TEST(Types, LexicalConversionComplex) { |
| 1173 | 1244 | EXPECT_EQ(x.imag(), 3.5); |
| 1174 | 1245 | } |
| 1175 | 1246 | |
| 1247 | +static_assert(CLI::detail::is_wrapper<std::vector<double>>::value, "vector double should be a wrapper"); | |
| 1248 | +static_assert(CLI::detail::is_wrapper<std::vector<std::string>>::value, "vector string should be a wrapper"); | |
| 1249 | +static_assert(CLI::detail::is_wrapper<std::string>::value, "string should be a wrapper"); | |
| 1250 | +static_assert(!CLI::detail::is_wrapper<double>::value, "double should not be a wrapper"); | |
| 1251 | + | |
| 1252 | +static_assert(CLI::detail::is_mutable_container<std::vector<double>>::value, "vector class should be a container"); | |
| 1253 | +static_assert(CLI::detail::is_mutable_container<std::vector<std::string>>::value, "vector class should be a container"); | |
| 1254 | +static_assert(!CLI::detail::is_mutable_container<std::string>::value, "string should be a container"); | |
| 1255 | +static_assert(!CLI::detail::is_mutable_container<double>::value, "double should not be a container"); | |
| 1256 | +static_assert(!CLI::detail::is_mutable_container<std::array<double, 5>>::value, "array should not be a container"); | |
| 1257 | + | |
| 1258 | +static_assert(CLI::detail::is_mutable_container<std::vector<int>>::value, "vector int should be a container"); | |
| 1259 | + | |
| 1260 | +static_assert(CLI::detail::is_readable_container<std::vector<int> &>::value, | |
| 1261 | + "vector int & should be a readable container"); | |
| 1262 | +static_assert(CLI::detail::is_readable_container<const std::vector<int>>::value, | |
| 1263 | + "const vector int should be a readable container"); | |
| 1264 | +static_assert(CLI::detail::is_readable_container<const std::vector<int> &>::value, | |
| 1265 | + "const vector int & should be a readable container"); | |
| 1266 | + | |
| 1176 | 1267 | TEST(FixNewLines, BasicCheck) { |
| 1177 | 1268 | std::string input = "one\ntwo"; |
| 1178 | 1269 | std::string output = "one\n; two"; | ... | ... |
tests/NewParseTest.cpp
| ... | ... | @@ -7,47 +7,36 @@ using ::testing::HasSubstr; |
| 7 | 7 | |
| 8 | 8 | using cx = std::complex<double>; |
| 9 | 9 | |
| 10 | -CLI::Option * | |
| 11 | -add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { | |
| 12 | - CLI::callback_t fun = [&variable](CLI::results_t res) { | |
| 13 | - double x, y; | |
| 14 | - bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); | |
| 15 | - if(worked) | |
| 16 | - variable = cx(x, y); | |
| 17 | - return worked; | |
| 18 | - }; | |
| 19 | - | |
| 20 | - CLI::Option *opt = app.add_option(name, fun, description, defaulted); | |
| 21 | - opt->type_name("COMPLEX")->type_size(2); | |
| 22 | - if(defaulted) { | |
| 23 | - std::stringstream out; | |
| 24 | - out << variable; | |
| 25 | - opt->default_str(out.str()); | |
| 26 | - } | |
| 27 | - return opt; | |
| 28 | -} | |
| 10 | +TEST_F(TApp, Complex) { | |
| 11 | + cx comp{1, 2}; | |
| 12 | + app.add_complex("-c,--complex", comp, "", true); | |
| 13 | + | |
| 14 | + args = {"-c", "4", "3"}; | |
| 29 | 15 | |
| 30 | -TEST_F(TApp, AddingComplexParser) { | |
| 16 | + std::string help = app.help(); | |
| 17 | + EXPECT_THAT(help, HasSubstr("1")); | |
| 18 | + EXPECT_THAT(help, HasSubstr("2")); | |
| 19 | + EXPECT_THAT(help, HasSubstr("COMPLEX")); | |
| 31 | 20 | |
| 32 | - cx comp{0, 0}; | |
| 33 | - add_option(app, "-c,--complex", comp); | |
| 34 | - args = {"-c", "1.5", "2.5"}; | |
| 21 | + EXPECT_DOUBLE_EQ(1, comp.real()); | |
| 22 | + EXPECT_DOUBLE_EQ(2, comp.imag()); | |
| 35 | 23 | |
| 36 | 24 | run(); |
| 37 | 25 | |
| 38 | - EXPECT_DOUBLE_EQ(1.5, comp.real()); | |
| 39 | - EXPECT_DOUBLE_EQ(2.5, comp.imag()); | |
| 26 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 27 | + EXPECT_DOUBLE_EQ(3, comp.imag()); | |
| 40 | 28 | } |
| 41 | 29 | |
| 42 | -TEST_F(TApp, DefaultComplex) { | |
| 43 | - | |
| 30 | +TEST_F(TApp, ComplexOption) { | |
| 44 | 31 | cx comp{1, 2}; |
| 45 | - add_option(app, "-c,--complex", comp, "", true); | |
| 32 | + app.add_option("-c,--complex", comp, "", true); | |
| 33 | + | |
| 46 | 34 | args = {"-c", "4", "3"}; |
| 47 | 35 | |
| 48 | 36 | std::string help = app.help(); |
| 49 | 37 | EXPECT_THAT(help, HasSubstr("1")); |
| 50 | 38 | EXPECT_THAT(help, HasSubstr("2")); |
| 39 | + EXPECT_THAT(help, HasSubstr("COMPLEX")); | |
| 51 | 40 | |
| 52 | 41 | EXPECT_DOUBLE_EQ(1, comp.real()); |
| 53 | 42 | EXPECT_DOUBLE_EQ(2, comp.imag()); |
| ... | ... | @@ -58,9 +47,9 @@ TEST_F(TApp, DefaultComplex) { |
| 58 | 47 | EXPECT_DOUBLE_EQ(3, comp.imag()); |
| 59 | 48 | } |
| 60 | 49 | |
| 61 | -TEST_F(TApp, BuiltinComplex) { | |
| 62 | - cx comp{1, 2}; | |
| 63 | - app.add_complex("-c,--complex", comp, "", true); | |
| 50 | +TEST_F(TApp, ComplexFloat) { | |
| 51 | + std::complex<float> comp{1, 2}; | |
| 52 | + app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true); | |
| 64 | 53 | |
| 65 | 54 | args = {"-c", "4", "3"}; |
| 66 | 55 | |
| ... | ... | @@ -69,18 +58,18 @@ TEST_F(TApp, BuiltinComplex) { |
| 69 | 58 | EXPECT_THAT(help, HasSubstr("2")); |
| 70 | 59 | EXPECT_THAT(help, HasSubstr("COMPLEX")); |
| 71 | 60 | |
| 72 | - EXPECT_DOUBLE_EQ(1, comp.real()); | |
| 73 | - EXPECT_DOUBLE_EQ(2, comp.imag()); | |
| 61 | + EXPECT_FLOAT_EQ(1, comp.real()); | |
| 62 | + EXPECT_FLOAT_EQ(2, comp.imag()); | |
| 74 | 63 | |
| 75 | 64 | run(); |
| 76 | 65 | |
| 77 | - EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 78 | - EXPECT_DOUBLE_EQ(3, comp.imag()); | |
| 66 | + EXPECT_FLOAT_EQ(4, comp.real()); | |
| 67 | + EXPECT_FLOAT_EQ(3, comp.imag()); | |
| 79 | 68 | } |
| 80 | 69 | |
| 81 | -TEST_F(TApp, BuiltinComplexFloat) { | |
| 70 | +TEST_F(TApp, ComplexFloatOption) { | |
| 82 | 71 | std::complex<float> comp{1, 2}; |
| 83 | - app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true); | |
| 72 | + app.add_option("-c,--complex", comp, "", true); | |
| 84 | 73 | |
| 85 | 74 | args = {"-c", "4", "3"}; |
| 86 | 75 | |
| ... | ... | @@ -98,7 +87,7 @@ TEST_F(TApp, BuiltinComplexFloat) { |
| 98 | 87 | EXPECT_FLOAT_EQ(3, comp.imag()); |
| 99 | 88 | } |
| 100 | 89 | |
| 101 | -TEST_F(TApp, BuiltinComplexWithDelimiter) { | |
| 90 | +TEST_F(TApp, ComplexWithDelimiter) { | |
| 102 | 91 | cx comp{1, 2}; |
| 103 | 92 | app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); |
| 104 | 93 | |
| ... | ... | @@ -130,7 +119,39 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) { |
| 130 | 119 | EXPECT_DOUBLE_EQ(-4, comp.imag()); |
| 131 | 120 | } |
| 132 | 121 | |
| 133 | -TEST_F(TApp, BuiltinComplexIgnoreI) { | |
| 122 | +TEST_F(TApp, ComplexWithDelimiterOption) { | |
| 123 | + cx comp{1, 2}; | |
| 124 | + app.add_option("-c,--complex", comp, "", true)->delimiter('+'); | |
| 125 | + | |
| 126 | + args = {"-c", "4+3i"}; | |
| 127 | + | |
| 128 | + std::string help = app.help(); | |
| 129 | + EXPECT_THAT(help, HasSubstr("1")); | |
| 130 | + EXPECT_THAT(help, HasSubstr("2")); | |
| 131 | + EXPECT_THAT(help, HasSubstr("COMPLEX")); | |
| 132 | + | |
| 133 | + EXPECT_DOUBLE_EQ(1, comp.real()); | |
| 134 | + EXPECT_DOUBLE_EQ(2, comp.imag()); | |
| 135 | + | |
| 136 | + run(); | |
| 137 | + | |
| 138 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 139 | + EXPECT_DOUBLE_EQ(3, comp.imag()); | |
| 140 | + | |
| 141 | + args = {"-c", "5+-3i"}; | |
| 142 | + run(); | |
| 143 | + | |
| 144 | + EXPECT_DOUBLE_EQ(5, comp.real()); | |
| 145 | + EXPECT_DOUBLE_EQ(-3, comp.imag()); | |
| 146 | + | |
| 147 | + args = {"-c", "6", "-4i"}; | |
| 148 | + run(); | |
| 149 | + | |
| 150 | + EXPECT_DOUBLE_EQ(6, comp.real()); | |
| 151 | + EXPECT_DOUBLE_EQ(-4, comp.imag()); | |
| 152 | +} | |
| 153 | + | |
| 154 | +TEST_F(TApp, ComplexIgnoreI) { | |
| 134 | 155 | cx comp{1, 2}; |
| 135 | 156 | app.add_complex("-c,--complex", comp); |
| 136 | 157 | |
| ... | ... | @@ -142,7 +163,19 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { |
| 142 | 163 | EXPECT_DOUBLE_EQ(3, comp.imag()); |
| 143 | 164 | } |
| 144 | 165 | |
| 145 | -TEST_F(TApp, BuiltinComplexSingleArg) { | |
| 166 | +TEST_F(TApp, ComplexIgnoreIOption) { | |
| 167 | + cx comp{1, 2}; | |
| 168 | + app.add_option("-c,--complex", comp); | |
| 169 | + | |
| 170 | + args = {"-c", "4", "3i"}; | |
| 171 | + | |
| 172 | + run(); | |
| 173 | + | |
| 174 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 175 | + EXPECT_DOUBLE_EQ(3, comp.imag()); | |
| 176 | +} | |
| 177 | + | |
| 178 | +TEST_F(TApp, ComplexSingleArg) { | |
| 146 | 179 | cx comp{1, 2}; |
| 147 | 180 | app.add_complex("-c,--complex", comp); |
| 148 | 181 | |
| ... | ... | @@ -176,7 +209,41 @@ TEST_F(TApp, BuiltinComplexSingleArg) { |
| 176 | 209 | EXPECT_DOUBLE_EQ(-2.7, comp.imag()); |
| 177 | 210 | } |
| 178 | 211 | |
| 179 | -TEST_F(TApp, BuiltinComplexSingleImag) { | |
| 212 | +TEST_F(TApp, ComplexSingleArgOption) { | |
| 213 | + cx comp{1, 2}; | |
| 214 | + app.add_option("-c,--complex", comp); | |
| 215 | + | |
| 216 | + args = {"-c", "4"}; | |
| 217 | + run(); | |
| 218 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 219 | + EXPECT_DOUBLE_EQ(0, comp.imag()); | |
| 220 | + | |
| 221 | + args = {"-c", "4-2i"}; | |
| 222 | + run(); | |
| 223 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 224 | + EXPECT_DOUBLE_EQ(-2, comp.imag()); | |
| 225 | + args = {"-c", "4+2i"}; | |
| 226 | + run(); | |
| 227 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 228 | + EXPECT_DOUBLE_EQ(2, comp.imag()); | |
| 229 | + | |
| 230 | + args = {"-c", "-4+2j"}; | |
| 231 | + run(); | |
| 232 | + EXPECT_DOUBLE_EQ(-4, comp.real()); | |
| 233 | + EXPECT_DOUBLE_EQ(2, comp.imag()); | |
| 234 | + | |
| 235 | + args = {"-c", "-4.2-2j"}; | |
| 236 | + run(); | |
| 237 | + EXPECT_DOUBLE_EQ(-4.2, comp.real()); | |
| 238 | + EXPECT_DOUBLE_EQ(-2, comp.imag()); | |
| 239 | + | |
| 240 | + args = {"-c", "-4.2-2.7i"}; | |
| 241 | + run(); | |
| 242 | + EXPECT_DOUBLE_EQ(-4.2, comp.real()); | |
| 243 | + EXPECT_DOUBLE_EQ(-2.7, comp.imag()); | |
| 244 | +} | |
| 245 | + | |
| 246 | +TEST_F(TApp, ComplexSingleImag) { | |
| 180 | 247 | cx comp{1, 2}; |
| 181 | 248 | app.add_complex("-c,--complex", comp); |
| 182 | 249 | |
| ... | ... | @@ -199,6 +266,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) { |
| 199 | 266 | EXPECT_DOUBLE_EQ(0, comp.imag()); |
| 200 | 267 | } |
| 201 | 268 | |
| 269 | +TEST_F(TApp, ComplexSingleImagOption) { | |
| 270 | + cx comp{1, 2}; | |
| 271 | + app.add_option("-c,--complex", comp); | |
| 272 | + | |
| 273 | + args = {"-c", "4j"}; | |
| 274 | + run(); | |
| 275 | + EXPECT_DOUBLE_EQ(0, comp.real()); | |
| 276 | + EXPECT_DOUBLE_EQ(4, comp.imag()); | |
| 277 | + | |
| 278 | + args = {"-c", "-4j"}; | |
| 279 | + run(); | |
| 280 | + EXPECT_DOUBLE_EQ(0, comp.real()); | |
| 281 | + EXPECT_DOUBLE_EQ(-4, comp.imag()); | |
| 282 | + args = {"-c", "-4"}; | |
| 283 | + run(); | |
| 284 | + EXPECT_DOUBLE_EQ(-4, comp.real()); | |
| 285 | + EXPECT_DOUBLE_EQ(0, comp.imag()); | |
| 286 | + args = {"-c", "+4"}; | |
| 287 | + run(); | |
| 288 | + EXPECT_DOUBLE_EQ(4, comp.real()); | |
| 289 | + EXPECT_DOUBLE_EQ(0, comp.imag()); | |
| 290 | +} | |
| 291 | + | |
| 202 | 292 | /// Simple class containing two strings useful for testing lexical cast and conversions |
| 203 | 293 | class spair { |
| 204 | 294 | public: |
| ... | ... | @@ -245,98 +335,6 @@ TEST_F(TApp, custom_string_converterFail) { |
| 245 | 335 | EXPECT_THROW(run(), CLI::ConversionError); |
| 246 | 336 | } |
| 247 | 337 | |
| 248 | -// an example of custom complex number converter that can be used to add new parsing options | |
| 249 | -#if defined(__has_include) | |
| 250 | -#if __has_include(<regex>) | |
| 251 | -// an example of custom converter that can be used to add new parsing options | |
| 252 | -#define HAS_REGEX_INCLUDE | |
| 253 | -#endif | |
| 254 | -#endif | |
| 255 | - | |
| 256 | -#ifdef HAS_REGEX_INCLUDE | |
| 257 | -// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would | |
| 258 | -// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to | |
| 259 | -// simplify compilation and prevent a much more complicated #if expression | |
| 260 | -#include <regex> | |
| 261 | -namespace CLI { | |
| 262 | -namespace detail { | |
| 263 | - | |
| 264 | -// On MSVC and possibly some other new compilers this can be a free standing function without the template | |
| 265 | -// specialization but this is compiler dependent | |
| 266 | -template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) { | |
| 267 | - // regular expression to handle complex numbers of various formats | |
| 268 | - static const std::regex creg( | |
| 269 | - R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); | |
| 270 | - | |
| 271 | - std::smatch m; | |
| 272 | - double x{0.0}, y{0.0}; | |
| 273 | - bool worked; | |
| 274 | - std::regex_search(input, m, creg); | |
| 275 | - if(m.size() == 9) { | |
| 276 | - worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); | |
| 277 | - if(worked) { | |
| 278 | - if(*m[5].first == '-') { | |
| 279 | - y = -y; | |
| 280 | - } | |
| 281 | - } | |
| 282 | - } else { | |
| 283 | - if((input.back() == 'j') || (input.back() == 'i')) { | |
| 284 | - auto strval = input.substr(0, input.size() - 1); | |
| 285 | - CLI::detail::trim(strval); | |
| 286 | - worked = CLI::detail::lexical_cast(strval, y); | |
| 287 | - } else { | |
| 288 | - std::string ival = input; | |
| 289 | - CLI::detail::trim(ival); | |
| 290 | - worked = CLI::detail::lexical_cast(ival, x); | |
| 291 | - } | |
| 292 | - } | |
| 293 | - if(worked) { | |
| 294 | - output = cx{x, y}; | |
| 295 | - } | |
| 296 | - return worked; | |
| 297 | -} | |
| 298 | -} // namespace detail | |
| 299 | -} // namespace CLI | |
| 300 | - | |
| 301 | -TEST_F(TApp, AddingComplexParserDetail) { | |
| 302 | - | |
| 303 | - bool skip_tests = false; | |
| 304 | - try { // check if the library actually supports regex, it is possible to link against a non working regex in the | |
| 305 | - // standard library | |
| 306 | - std::smatch m; | |
| 307 | - std::string input = "1.5+2.5j"; | |
| 308 | - static const std::regex creg( | |
| 309 | - R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); | |
| 310 | - | |
| 311 | - auto rsearch = std::regex_search(input, m, creg); | |
| 312 | - if(!rsearch) { | |
| 313 | - skip_tests = true; | |
| 314 | - } else { | |
| 315 | - EXPECT_EQ(m.size(), 9u); | |
| 316 | - } | |
| 317 | - | |
| 318 | - } catch(...) { | |
| 319 | - skip_tests = true; | |
| 320 | - } | |
| 321 | - if(!skip_tests) { | |
| 322 | - cx comp{0, 0}; | |
| 323 | - app.add_option("-c,--complex", comp, "add a complex number option"); | |
| 324 | - args = {"-c", "1.5+2.5j"}; | |
| 325 | - | |
| 326 | - run(); | |
| 327 | - | |
| 328 | - EXPECT_DOUBLE_EQ(1.5, comp.real()); | |
| 329 | - EXPECT_DOUBLE_EQ(2.5, comp.imag()); | |
| 330 | - args = {"-c", "1.5-2.5j"}; | |
| 331 | - | |
| 332 | - run(); | |
| 333 | - | |
| 334 | - EXPECT_DOUBLE_EQ(1.5, comp.real()); | |
| 335 | - EXPECT_DOUBLE_EQ(-2.5, comp.imag()); | |
| 336 | - } | |
| 337 | -} | |
| 338 | -#endif | |
| 339 | - | |
| 340 | 338 | /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the |
| 341 | 339 | /// option assignments |
| 342 | 340 | template <class X> class objWrapper { |
| ... | ... | @@ -523,3 +521,107 @@ TEST_F(TApp, uint16Wrapper) { |
| 523 | 521 | |
| 524 | 522 | EXPECT_THROW(run(), CLI::ConversionError); |
| 525 | 523 | } |
| 524 | + | |
| 525 | +template <class T> class SimpleWrapper { | |
| 526 | + public: | |
| 527 | + SimpleWrapper() : val_{} {}; | |
| 528 | + explicit SimpleWrapper(const T &initial) : val_{initial} {}; | |
| 529 | + T &getRef() { return val_; }; | |
| 530 | + using value_type = T; | |
| 531 | + | |
| 532 | + private: | |
| 533 | + T val_; | |
| 534 | +}; | |
| 535 | + | |
| 536 | +TEST_F(TApp, wrapperInt) { | |
| 537 | + SimpleWrapper<int> wrap; | |
| 538 | + app.add_option("--val", wrap); | |
| 539 | + args = {"--val", "2"}; | |
| 540 | + | |
| 541 | + run(); | |
| 542 | + EXPECT_EQ(wrap.getRef(), 2); | |
| 543 | +} | |
| 544 | + | |
| 545 | +TEST_F(TApp, wrapperString) { | |
| 546 | + SimpleWrapper<std::string> wrap; | |
| 547 | + app.add_option("--val", wrap); | |
| 548 | + args = {"--val", "str"}; | |
| 549 | + | |
| 550 | + run(); | |
| 551 | + EXPECT_EQ(wrap.getRef(), "str"); | |
| 552 | +} | |
| 553 | + | |
| 554 | +TEST_F(TApp, wrapperVector) { | |
| 555 | + SimpleWrapper<std::vector<int>> wrap; | |
| 556 | + app.add_option("--val", wrap); | |
| 557 | + args = {"--val", "1", "2", "3", "4"}; | |
| 558 | + | |
| 559 | + run(); | |
| 560 | + auto v1 = wrap.getRef(); | |
| 561 | + auto v2 = std::vector<int>{1, 2, 3, 4}; | |
| 562 | + EXPECT_EQ(v1, v2); | |
| 563 | +} | |
| 564 | + | |
| 565 | +TEST_F(TApp, wrapperwrapperString) { | |
| 566 | + SimpleWrapper<SimpleWrapper<std::string>> wrap; | |
| 567 | + app.add_option("--val", wrap); | |
| 568 | + args = {"--val", "arg"}; | |
| 569 | + | |
| 570 | + run(); | |
| 571 | + auto v1 = wrap.getRef().getRef(); | |
| 572 | + auto v2 = "arg"; | |
| 573 | + EXPECT_EQ(v1, v2); | |
| 574 | +} | |
| 575 | + | |
| 576 | +TEST_F(TApp, wrapperwrapperVector) { | |
| 577 | + SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap; | |
| 578 | + auto opt = app.add_option("--val", wrap); | |
| 579 | + args = {"--val", "1", "2", "3", "4"}; | |
| 580 | + | |
| 581 | + run(); | |
| 582 | + auto v1 = wrap.getRef().getRef(); | |
| 583 | + auto v2 = std::vector<int>{1, 2, 3, 4}; | |
| 584 | + EXPECT_EQ(v1, v2); | |
| 585 | + opt->type_size(0, 5); | |
| 586 | + | |
| 587 | + args = {"--val"}; | |
| 588 | + | |
| 589 | + run(); | |
| 590 | + EXPECT_TRUE(wrap.getRef().getRef().empty()); | |
| 591 | + | |
| 592 | + args = {"--val", "happy", "sad"}; | |
| 593 | + | |
| 594 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 595 | +} | |
| 596 | + | |
| 597 | +TEST_F(TApp, wrapperComplex) { | |
| 598 | + SimpleWrapper<std::complex<double>> wrap; | |
| 599 | + app.add_option("--val", wrap); | |
| 600 | + args = {"--val", "1", "2"}; | |
| 601 | + | |
| 602 | + run(); | |
| 603 | + auto &v1 = wrap.getRef(); | |
| 604 | + auto v2 = std::complex<double>{1, 2}; | |
| 605 | + EXPECT_EQ(v1.real(), v2.real()); | |
| 606 | + EXPECT_EQ(v1.imag(), v2.imag()); | |
| 607 | + args = {"--val", "1.4-4j"}; | |
| 608 | + | |
| 609 | + run(); | |
| 610 | + v2 = std::complex<double>{1.4, -4}; | |
| 611 | + EXPECT_EQ(v1.real(), v2.real()); | |
| 612 | + EXPECT_EQ(v1.imag(), v2.imag()); | |
| 613 | +} | |
| 614 | + | |
| 615 | +TEST_F(TApp, vectorComplex) { | |
| 616 | + std::vector<std::complex<double>> vcomplex; | |
| 617 | + app.add_option("--val", vcomplex); | |
| 618 | + args = {"--val", "1", "2", "--val", "1.4-4j"}; | |
| 619 | + | |
| 620 | + run(); | |
| 621 | + | |
| 622 | + ASSERT_EQ(vcomplex.size(), 2U); | |
| 623 | + EXPECT_EQ(vcomplex[0].real(), 1.0); | |
| 624 | + EXPECT_EQ(vcomplex[0].imag(), 2.0); | |
| 625 | + EXPECT_EQ(vcomplex[1].real(), 1.4); | |
| 626 | + EXPECT_EQ(vcomplex[1].imag(), -4.0); | |
| 627 | +} | ... | ... |
tests/OptionTypeTest.cpp
0 β 100644
| 1 | +#include "app_helper.hpp" | |
| 2 | +#include <complex> | |
| 3 | +#include <cstdint> | |
| 4 | +#include <cstdlib> | |
| 5 | +#include <deque> | |
| 6 | +#include <forward_list> | |
| 7 | +#include <list> | |
| 8 | +#include <map> | |
| 9 | +#include <queue> | |
| 10 | +#include <set> | |
| 11 | +#include <unordered_map> | |
| 12 | +#include <unordered_set> | |
| 13 | +#include <vector> | |
| 14 | + | |
| 15 | +#include "gmock/gmock.h" | |
| 16 | + | |
| 17 | +TEST_F(TApp, OneStringAgain) { | |
| 18 | + std::string str; | |
| 19 | + app.add_option("-s,--string", str); | |
| 20 | + args = {"--string", "mystring"}; | |
| 21 | + run(); | |
| 22 | + EXPECT_EQ(1u, app.count("-s")); | |
| 23 | + EXPECT_EQ(1u, app.count("--string")); | |
| 24 | + EXPECT_EQ(str, "mystring"); | |
| 25 | +} | |
| 26 | + | |
| 27 | +TEST_F(TApp, OneStringFunction) { | |
| 28 | + std::string str; | |
| 29 | + app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; }); | |
| 30 | + args = {"--string", "mystring"}; | |
| 31 | + run(); | |
| 32 | + EXPECT_EQ(1u, app.count("-s")); | |
| 33 | + EXPECT_EQ(1u, app.count("--string")); | |
| 34 | + EXPECT_EQ(str, "mystring"); | |
| 35 | +} | |
| 36 | + | |
| 37 | +TEST_F(TApp, doubleFunction) { | |
| 38 | + double res{0.0}; | |
| 39 | + app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); | |
| 40 | + args = {"--val", "-354.356"}; | |
| 41 | + run(); | |
| 42 | + EXPECT_EQ(res, 300.356); | |
| 43 | + // get the original value as entered as an integer | |
| 44 | + EXPECT_EQ(app["--val"]->as<float>(), -354.356f); | |
| 45 | +} | |
| 46 | + | |
| 47 | +TEST_F(TApp, doubleFunctionFail) { | |
| 48 | + double res; | |
| 49 | + app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); | |
| 50 | + args = {"--val", "not_double"}; | |
| 51 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 52 | +} | |
| 53 | + | |
| 54 | +TEST_F(TApp, doubleVectorFunction) { | |
| 55 | + std::vector<double> res; | |
| 56 | + app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { | |
| 57 | + res = val; | |
| 58 | + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | |
| 59 | + }); | |
| 60 | + args = {"--val", "5", "--val", "6", "--val", "7"}; | |
| 61 | + run(); | |
| 62 | + EXPECT_EQ(res.size(), 3u); | |
| 63 | + EXPECT_EQ(res[0], 10.0); | |
| 64 | + EXPECT_EQ(res[2], 12.0); | |
| 65 | +} | |
| 66 | + | |
| 67 | +TEST_F(TApp, doubleVectorFunctionFail) { | |
| 68 | + std::vector<double> res; | |
| 69 | + std::string vstring = "--val"; | |
| 70 | + app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) { | |
| 71 | + res = val; | |
| 72 | + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | |
| 73 | + }); | |
| 74 | + args = {"--val", "five", "--val", "nine", "--val", "7"}; | |
| 75 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 76 | + // check that getting the results through the results function generates the same error | |
| 77 | + EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError); | |
| 78 | + auto strvec = app[vstring]->as<std::vector<std::string>>(); | |
| 79 | + EXPECT_EQ(strvec.size(), 3u); | |
| 80 | +} | |
| 81 | + | |
| 82 | +TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) { | |
| 83 | + std::vector<double> res; | |
| 84 | + auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { | |
| 85 | + res = val; | |
| 86 | + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | |
| 87 | + }); | |
| 88 | + args = {"--val", "5", "--val", "6", "--val", "7"}; | |
| 89 | + run(); | |
| 90 | + EXPECT_EQ(res.size(), 3u); | |
| 91 | + EXPECT_EQ(res[0], 10.0); | |
| 92 | + EXPECT_EQ(res[2], 12.0); | |
| 93 | + EXPECT_FALSE(opt->get_run_callback_for_default()); | |
| 94 | + opt->run_callback_for_default(); | |
| 95 | + opt->default_val(std::vector<int>{2, 1, -2}); | |
| 96 | + EXPECT_EQ(res[0], 7.0); | |
| 97 | + EXPECT_EQ(res[2], 3.0); | |
| 98 | + | |
| 99 | + EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError); | |
| 100 | + auto vec = opt->as<std::vector<double>>(); | |
| 101 | + ASSERT_EQ(vec.size(), 3U); | |
| 102 | + EXPECT_EQ(vec[0], 5.0); | |
| 103 | + EXPECT_EQ(vec[2], 7.0); | |
| 104 | + opt->check(CLI::Number); | |
| 105 | + opt->run_callback_for_default(false); | |
| 106 | + EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError); | |
| 107 | +} | |
| 108 | + | |
| 109 | +TEST_F(TApp, BoolAndIntFlags) { | |
| 110 | + | |
| 111 | + bool bflag{false}; | |
| 112 | + int iflag{0}; | |
| 113 | + unsigned int uflag{0}; | |
| 114 | + | |
| 115 | + app.add_flag("-b", bflag); | |
| 116 | + app.add_flag("-i", iflag); | |
| 117 | + app.add_flag("-u", uflag); | |
| 118 | + | |
| 119 | + args = {"-b", "-i", "-u"}; | |
| 120 | + run(); | |
| 121 | + EXPECT_TRUE(bflag); | |
| 122 | + EXPECT_EQ(1, iflag); | |
| 123 | + EXPECT_EQ((unsigned int)1, uflag); | |
| 124 | + | |
| 125 | + args = {"-b", "-b"}; | |
| 126 | + ASSERT_NO_THROW(run()); | |
| 127 | + EXPECT_TRUE(bflag); | |
| 128 | + | |
| 129 | + bflag = false; | |
| 130 | + | |
| 131 | + args = {"-iiiuu"}; | |
| 132 | + run(); | |
| 133 | + EXPECT_FALSE(bflag); | |
| 134 | + EXPECT_EQ(3, iflag); | |
| 135 | + EXPECT_EQ((unsigned int)2, uflag); | |
| 136 | +} | |
| 137 | + | |
| 138 | +TEST_F(TApp, BoolOption) { | |
| 139 | + bool bflag{false}; | |
| 140 | + app.add_option("-b", bflag); | |
| 141 | + | |
| 142 | + args = {"-b", "false"}; | |
| 143 | + run(); | |
| 144 | + EXPECT_FALSE(bflag); | |
| 145 | + | |
| 146 | + args = {"-b", "1"}; | |
| 147 | + run(); | |
| 148 | + EXPECT_TRUE(bflag); | |
| 149 | + | |
| 150 | + args = {"-b", "-7"}; | |
| 151 | + run(); | |
| 152 | + EXPECT_FALSE(bflag); | |
| 153 | + | |
| 154 | + // cause an out of bounds error internally | |
| 155 | + args = {"-b", "751615654161688126132138844896646748852"}; | |
| 156 | + run(); | |
| 157 | + EXPECT_TRUE(bflag); | |
| 158 | + | |
| 159 | + args = {"-b", "-751615654161688126132138844896646748852"}; | |
| 160 | + run(); | |
| 161 | + EXPECT_FALSE(bflag); | |
| 162 | +} | |
| 163 | + | |
| 164 | +TEST_F(TApp, vectorDefaults) { | |
| 165 | + std::vector<int> vals{4, 5}; | |
| 166 | + auto opt = app.add_option("--long", vals, "", true); | |
| 167 | + | |
| 168 | + args = {"--long", "[1,2,3]"}; | |
| 169 | + | |
| 170 | + run(); | |
| 171 | + | |
| 172 | + EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); | |
| 173 | + | |
| 174 | + args.clear(); | |
| 175 | + run(); | |
| 176 | + auto res = app["--long"]->as<std::vector<int>>(); | |
| 177 | + EXPECT_EQ(res, std::vector<int>({4, 5})); | |
| 178 | + | |
| 179 | + app.clear(); | |
| 180 | + opt->expected(1)->take_last(); | |
| 181 | + res = app["--long"]->as<std::vector<int>>(); | |
| 182 | + EXPECT_EQ(res, std::vector<int>({5})); | |
| 183 | + opt->take_first(); | |
| 184 | + res = app["--long"]->as<std::vector<int>>(); | |
| 185 | + EXPECT_EQ(res, std::vector<int>({4})); | |
| 186 | + | |
| 187 | + opt->expected(0, 1)->take_last(); | |
| 188 | + run(); | |
| 189 | + | |
| 190 | + EXPECT_EQ(res, std::vector<int>({4})); | |
| 191 | + res = app["--long"]->as<std::vector<int>>(); | |
| 192 | + EXPECT_EQ(res, std::vector<int>({5})); | |
| 193 | +} | |
| 194 | + | |
| 195 | +TEST_F(TApp, CallbackBoolFlags) { | |
| 196 | + | |
| 197 | + bool value{false}; | |
| 198 | + | |
| 199 | + auto func = [&value]() { value = true; }; | |
| 200 | + | |
| 201 | + auto cback = app.add_flag_callback("--val", func); | |
| 202 | + args = {"--val"}; | |
| 203 | + run(); | |
| 204 | + EXPECT_TRUE(value); | |
| 205 | + value = false; | |
| 206 | + args = {"--val=false"}; | |
| 207 | + run(); | |
| 208 | + EXPECT_FALSE(value); | |
| 209 | + | |
| 210 | + EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction); | |
| 211 | + cback->multi_option_policy(CLI::MultiOptionPolicy::Throw); | |
| 212 | + args = {"--val", "--val=false"}; | |
| 213 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 214 | +} | |
| 215 | + | |
| 216 | +TEST_F(TApp, pair_check) { | |
| 217 | + std::string myfile{"pair_check_file.txt"}; | |
| 218 | + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file | |
| 219 | + EXPECT_TRUE(ok); | |
| 220 | + | |
| 221 | + EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); | |
| 222 | + std::pair<std::string, int> findex; | |
| 223 | + | |
| 224 | + auto v0 = CLI::ExistingFile; | |
| 225 | + v0.application_index(0); | |
| 226 | + auto v1 = CLI::PositiveNumber; | |
| 227 | + v1.application_index(1); | |
| 228 | + app.add_option("--file", findex)->check(v0)->check(v1); | |
| 229 | + | |
| 230 | + args = {"--file", myfile, "2"}; | |
| 231 | + | |
| 232 | + EXPECT_NO_THROW(run()); | |
| 233 | + | |
| 234 | + EXPECT_EQ(findex.first, myfile); | |
| 235 | + EXPECT_EQ(findex.second, 2); | |
| 236 | + | |
| 237 | + args = {"--file", myfile, "-3"}; | |
| 238 | + | |
| 239 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 240 | + | |
| 241 | + args = {"--file", myfile, "2"}; | |
| 242 | + std::remove(myfile.c_str()); | |
| 243 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 244 | +} | |
| 245 | + | |
| 246 | +// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present | |
| 247 | + | |
| 248 | +TEST_F(TApp, pair_check_take_first) { | |
| 249 | + std::string myfile{"pair_check_file2.txt"}; | |
| 250 | + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file | |
| 251 | + EXPECT_TRUE(ok); | |
| 252 | + | |
| 253 | + EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); | |
| 254 | + std::pair<std::string, int> findex; | |
| 255 | + | |
| 256 | + auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber); | |
| 257 | + EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound); | |
| 258 | + opt->get_validator(0)->application_index(0); | |
| 259 | + opt->get_validator(1)->application_index(1); | |
| 260 | + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); | |
| 261 | + args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"}; | |
| 262 | + // should only check the last one | |
| 263 | + EXPECT_NO_THROW(run()); | |
| 264 | + | |
| 265 | + EXPECT_EQ(findex.first, myfile); | |
| 266 | + EXPECT_EQ(findex.second, 2); | |
| 267 | + | |
| 268 | + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst); | |
| 269 | + | |
| 270 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 271 | +} | |
| 272 | + | |
| 273 | +TEST_F(TApp, VectorFixedString) { | |
| 274 | + std::vector<std::string> strvec; | |
| 275 | + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 276 | + | |
| 277 | + CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3); | |
| 278 | + EXPECT_EQ(3, opt->get_expected()); | |
| 279 | + | |
| 280 | + args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 281 | + run(); | |
| 282 | + EXPECT_EQ(3u, app.count("--string")); | |
| 283 | + EXPECT_EQ(answer, strvec); | |
| 284 | +} | |
| 285 | + | |
| 286 | +TEST_F(TApp, VectorDefaultedFixedString) { | |
| 287 | + std::vector<std::string> strvec{"one"}; | |
| 288 | + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 289 | + | |
| 290 | + CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str(); | |
| 291 | + EXPECT_EQ(3, opt->get_expected()); | |
| 292 | + | |
| 293 | + args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 294 | + run(); | |
| 295 | + EXPECT_EQ(3u, app.count("--string")); | |
| 296 | + EXPECT_EQ(answer, strvec); | |
| 297 | +} | |
| 298 | + | |
| 299 | +TEST_F(TApp, VectorIndexedValidator) { | |
| 300 | + std::vector<int> vvec; | |
| 301 | + | |
| 302 | + CLI::Option *opt = app.add_option("-v", vvec); | |
| 303 | + | |
| 304 | + args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; | |
| 305 | + run(); | |
| 306 | + EXPECT_EQ(4u, app.count("-v")); | |
| 307 | + EXPECT_EQ(4u, vvec.size()); | |
| 308 | + opt->check(CLI::PositiveNumber.application_index(0)); | |
| 309 | + opt->check((!CLI::PositiveNumber).application_index(1)); | |
| 310 | + EXPECT_NO_THROW(run()); | |
| 311 | + EXPECT_EQ(4u, vvec.size()); | |
| 312 | + // v[3] would be negative | |
| 313 | + opt->check(CLI::PositiveNumber.application_index(3)); | |
| 314 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 315 | +} | |
| 316 | + | |
| 317 | +TEST_F(TApp, VectorUnlimString) { | |
| 318 | + std::vector<std::string> strvec; | |
| 319 | + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 320 | + | |
| 321 | + CLI::Option *opt = app.add_option("-s,--string", strvec); | |
| 322 | + EXPECT_EQ(1, opt->get_expected()); | |
| 323 | + EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max()); | |
| 324 | + | |
| 325 | + args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 326 | + run(); | |
| 327 | + EXPECT_EQ(3u, app.count("--string")); | |
| 328 | + EXPECT_EQ(answer, strvec); | |
| 329 | + | |
| 330 | + args = {"-s", "mystring", "mystring2", "mystring3"}; | |
| 331 | + run(); | |
| 332 | + EXPECT_EQ(3u, app.count("--string")); | |
| 333 | + EXPECT_EQ(answer, strvec); | |
| 334 | +} | |
| 335 | + | |
| 336 | +// From https://github.com/CLIUtils/CLI11/issues/420 | |
| 337 | +TEST_F(TApp, stringLikeTests) { | |
| 338 | + struct nType { | |
| 339 | + explicit nType(const std::string &a_value) : m_value{a_value} {} | |
| 340 | + | |
| 341 | + explicit operator std::string() const { return std::string{"op str"}; } | |
| 342 | + | |
| 343 | + std::string m_value; | |
| 344 | + }; | |
| 345 | + | |
| 346 | + nType m_type{"abc"}; | |
| 347 | + app.add_option("--type", m_type, "type")->capture_default_str(); | |
| 348 | + run(); | |
| 349 | + | |
| 350 | + EXPECT_EQ(app["--type"]->as<std::string>(), "op str"); | |
| 351 | + args = {"--type", "bca"}; | |
| 352 | + run(); | |
| 353 | + EXPECT_EQ(std::string(m_type), "op str"); | |
| 354 | + EXPECT_EQ(m_type.m_value, "bca"); | |
| 355 | +} | |
| 356 | + | |
| 357 | +TEST_F(TApp, VectorExpectedRange) { | |
| 358 | + std::vector<std::string> strvec; | |
| 359 | + | |
| 360 | + CLI::Option *opt = app.add_option("--string", strvec); | |
| 361 | + opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw); | |
| 362 | + | |
| 363 | + args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 364 | + run(); | |
| 365 | + EXPECT_EQ(3u, app.count("--string")); | |
| 366 | + | |
| 367 | + args = {"--string", "mystring"}; | |
| 368 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 369 | + | |
| 370 | + args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"}; | |
| 371 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 372 | + | |
| 373 | + EXPECT_EQ(opt->get_expected_max(), 4); | |
| 374 | + EXPECT_EQ(opt->get_expected_min(), 2); | |
| 375 | + opt->expected(4, 2); // just test the handling of reversed arguments | |
| 376 | + EXPECT_EQ(opt->get_expected_max(), 4); | |
| 377 | + EXPECT_EQ(opt->get_expected_min(), 2); | |
| 378 | + opt->expected(-5); | |
| 379 | + EXPECT_EQ(opt->get_expected_max(), 5); | |
| 380 | + EXPECT_EQ(opt->get_expected_min(), 5); | |
| 381 | + opt->expected(-5, 7); | |
| 382 | + EXPECT_EQ(opt->get_expected_max(), 7); | |
| 383 | + EXPECT_EQ(opt->get_expected_min(), 5); | |
| 384 | +} | |
| 385 | + | |
| 386 | +TEST_F(TApp, VectorFancyOpts) { | |
| 387 | + std::vector<std::string> strvec; | |
| 388 | + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 389 | + | |
| 390 | + CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3); | |
| 391 | + EXPECT_EQ(3, opt->get_expected()); | |
| 392 | + | |
| 393 | + args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 394 | + run(); | |
| 395 | + EXPECT_EQ(3u, app.count("--string")); | |
| 396 | + EXPECT_EQ(answer, strvec); | |
| 397 | + | |
| 398 | + args = {"one", "two"}; | |
| 399 | + EXPECT_THROW(run(), CLI::RequiredError); | |
| 400 | + | |
| 401 | + EXPECT_THROW(run(), CLI::ParseError); | |
| 402 | +} | |
| 403 | + | |
| 404 | +// #87 | |
| 405 | +TEST_F(TApp, CustomDoubleOption) { | |
| 406 | + | |
| 407 | + std::pair<int, double> custom_opt; | |
| 408 | + | |
| 409 | + auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) { | |
| 410 | + custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; | |
| 411 | + return true; | |
| 412 | + }); | |
| 413 | + opt->type_name("INT FLOAT")->type_size(2); | |
| 414 | + | |
| 415 | + args = {"12", "1.5"}; | |
| 416 | + | |
| 417 | + run(); | |
| 418 | + EXPECT_EQ(custom_opt.first, 12); | |
| 419 | + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); | |
| 420 | +} | |
| 421 | + | |
| 422 | +// now with tuple support this is possible | |
| 423 | +TEST_F(TApp, CustomDoubleOptionAlt) { | |
| 424 | + | |
| 425 | + std::pair<int, double> custom_opt; | |
| 426 | + | |
| 427 | + app.add_option("posit", custom_opt); | |
| 428 | + | |
| 429 | + args = {"12", "1.5"}; | |
| 430 | + | |
| 431 | + run(); | |
| 432 | + EXPECT_EQ(custom_opt.first, 12); | |
| 433 | + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); | |
| 434 | +} | |
| 435 | + | |
| 436 | +// now with independent type sizes and expected this is possible | |
| 437 | +TEST_F(TApp, vectorPair) { | |
| 438 | + | |
| 439 | + std::vector<std::pair<int, std::string>> custom_opt; | |
| 440 | + | |
| 441 | + auto opt = app.add_option("--dict", custom_opt); | |
| 442 | + | |
| 443 | + args = {"--dict", "1", "str1", "--dict", "3", "str3"}; | |
| 444 | + | |
| 445 | + run(); | |
| 446 | + ASSERT_EQ(custom_opt.size(), 2u); | |
| 447 | + EXPECT_EQ(custom_opt[0].first, 1); | |
| 448 | + EXPECT_EQ(custom_opt[1].second, "str3"); | |
| 449 | + | |
| 450 | + args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"}; | |
| 451 | + run(); | |
| 452 | + ASSERT_EQ(custom_opt.size(), 3u); | |
| 453 | + EXPECT_EQ(custom_opt[2].first, -1); | |
| 454 | + EXPECT_EQ(custom_opt[2].second, "str4"); | |
| 455 | + opt->check(CLI::PositiveNumber.application_index(0)); | |
| 456 | + | |
| 457 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 458 | +} | |
| 459 | + | |
| 460 | +TEST_F(TApp, vectorPairFail) { | |
| 461 | + | |
| 462 | + std::vector<std::pair<int, std::string>> custom_opt; | |
| 463 | + | |
| 464 | + app.add_option("--dict", custom_opt); | |
| 465 | + | |
| 466 | + args = {"--dict", "1", "str1", "--dict", "str3", "1"}; | |
| 467 | + | |
| 468 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 469 | +} | |
| 470 | + | |
| 471 | +TEST_F(TApp, vectorPairTypeRange) { | |
| 472 | + | |
| 473 | + std::vector<std::pair<int, std::string>> custom_opt; | |
| 474 | + | |
| 475 | + auto opt = app.add_option("--dict", custom_opt); | |
| 476 | + | |
| 477 | + opt->type_size(2, 1); // just test switched arguments | |
| 478 | + EXPECT_EQ(opt->get_type_size_min(), 1); | |
| 479 | + EXPECT_EQ(opt->get_type_size_max(), 2); | |
| 480 | + | |
| 481 | + args = {"--dict", "1", "str1", "--dict", "3", "str3"}; | |
| 482 | + | |
| 483 | + run(); | |
| 484 | + ASSERT_EQ(custom_opt.size(), 2u); | |
| 485 | + EXPECT_EQ(custom_opt[0].first, 1); | |
| 486 | + EXPECT_EQ(custom_opt[1].second, "str3"); | |
| 487 | + | |
| 488 | + args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; | |
| 489 | + run(); | |
| 490 | + ASSERT_EQ(custom_opt.size(), 3u); | |
| 491 | + EXPECT_TRUE(custom_opt[1].second.empty()); | |
| 492 | + EXPECT_EQ(custom_opt[2].first, -1); | |
| 493 | + EXPECT_EQ(custom_opt[2].second, "str4"); | |
| 494 | + | |
| 495 | + opt->type_size(-2, -1); // test negative arguments | |
| 496 | + EXPECT_EQ(opt->get_type_size_min(), 1); | |
| 497 | + EXPECT_EQ(opt->get_type_size_max(), 2); | |
| 498 | + // this type size spec should run exactly as before | |
| 499 | + run(); | |
| 500 | + ASSERT_EQ(custom_opt.size(), 3u); | |
| 501 | + EXPECT_TRUE(custom_opt[1].second.empty()); | |
| 502 | + EXPECT_EQ(custom_opt[2].first, -1); | |
| 503 | + EXPECT_EQ(custom_opt[2].second, "str4"); | |
| 504 | +} | |
| 505 | + | |
| 506 | +// now with independent type sizes and expected this is possible | |
| 507 | +TEST_F(TApp, vectorTuple) { | |
| 508 | + | |
| 509 | + std::vector<std::tuple<int, std::string, double>> custom_opt; | |
| 510 | + | |
| 511 | + auto opt = app.add_option("--dict", custom_opt); | |
| 512 | + | |
| 513 | + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; | |
| 514 | + | |
| 515 | + run(); | |
| 516 | + ASSERT_EQ(custom_opt.size(), 2u); | |
| 517 | + EXPECT_EQ(std::get<0>(custom_opt[0]), 1); | |
| 518 | + EXPECT_EQ(std::get<1>(custom_opt[1]), "str3"); | |
| 519 | + EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7); | |
| 520 | + | |
| 521 | + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; | |
| 522 | + run(); | |
| 523 | + ASSERT_EQ(custom_opt.size(), 3u); | |
| 524 | + EXPECT_EQ(std::get<0>(custom_opt[2]), -1); | |
| 525 | + EXPECT_EQ(std::get<1>(custom_opt[2]), "str4"); | |
| 526 | + EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87); | |
| 527 | + opt->check(CLI::PositiveNumber.application_index(0)); | |
| 528 | + | |
| 529 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 530 | + | |
| 531 | + args.back() = "haha"; | |
| 532 | + args[9] = "45"; | |
| 533 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 534 | +} | |
| 535 | + | |
| 536 | +// now with independent type sizes and expected this is possible | |
| 537 | +TEST_F(TApp, vectorVector) { | |
| 538 | + | |
| 539 | + std::vector<std::vector<int>> custom_opt; | |
| 540 | + | |
| 541 | + auto opt = app.add_option("--dict", custom_opt); | |
| 542 | + | |
| 543 | + args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; | |
| 544 | + | |
| 545 | + run(); | |
| 546 | + ASSERT_EQ(custom_opt.size(), 2u); | |
| 547 | + EXPECT_EQ(custom_opt[0].size(), 3u); | |
| 548 | + EXPECT_EQ(custom_opt[1].size(), 2u); | |
| 549 | + | |
| 550 | + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", | |
| 551 | + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; | |
| 552 | + run(); | |
| 553 | + ASSERT_EQ(custom_opt.size(), 4u); | |
| 554 | + EXPECT_EQ(custom_opt[0].size(), 3u); | |
| 555 | + EXPECT_EQ(custom_opt[1].size(), 2u); | |
| 556 | + EXPECT_EQ(custom_opt[2].size(), 1u); | |
| 557 | + EXPECT_EQ(custom_opt[3].size(), 10u); | |
| 558 | + opt->check(CLI::PositiveNumber.application_index(9)); | |
| 559 | + | |
| 560 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 561 | + args.pop_back(); | |
| 562 | + EXPECT_NO_THROW(run()); | |
| 563 | + | |
| 564 | + args.back() = "haha"; | |
| 565 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 566 | + | |
| 567 | + args = {"--dict", "1", "2", "4", "%%", "3", "1", "%%", "3", "%%", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3"}; | |
| 568 | + run(); | |
| 569 | + ASSERT_EQ(custom_opt.size(), 4u); | |
| 570 | +} | |
| 571 | + | |
| 572 | +// now with independent type sizes and expected this is possible | |
| 573 | +TEST_F(TApp, vectorVectorFixedSize) { | |
| 574 | + | |
| 575 | + std::vector<std::vector<int>> custom_opt; | |
| 576 | + | |
| 577 | + auto opt = app.add_option("--dict", custom_opt)->type_size(4); | |
| 578 | + | |
| 579 | + args = {"--dict", "1", "2", "4", "3", "--dict", "3", "1", "2", "8"}; | |
| 580 | + | |
| 581 | + run(); | |
| 582 | + ASSERT_EQ(custom_opt.size(), 2u); | |
| 583 | + EXPECT_EQ(custom_opt[0].size(), 4u); | |
| 584 | + EXPECT_EQ(custom_opt[1].size(), 4u); | |
| 585 | + | |
| 586 | + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "7", "6"}; | |
| 587 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 588 | + // this should reset it | |
| 589 | + opt->type_size(CLI::detail::expected_max_vector_size); | |
| 590 | + opt->type_size(1, CLI::detail::expected_max_vector_size); | |
| 591 | + EXPECT_NO_THROW(run()); | |
| 592 | + ASSERT_EQ(custom_opt.size(), 2U); | |
| 593 | +} | |
| 594 | + | |
| 595 | +// now with independent type sizes and expected this is possible | |
| 596 | +TEST_F(TApp, tuplePair) { | |
| 597 | + std::tuple<std::pair<int, double>> custom_opt; | |
| 598 | + | |
| 599 | + app.add_option("--pr", custom_opt); | |
| 600 | + | |
| 601 | + args = {"--pr", "1", "2"}; | |
| 602 | + | |
| 603 | + run(); | |
| 604 | + EXPECT_EQ(std::get<0>(custom_opt).first, 1); | |
| 605 | + EXPECT_EQ(std::get<0>(custom_opt).second, 2.0); | |
| 606 | +} | |
| 607 | +// now with independent type sizes and expected this is possible | |
| 608 | +TEST_F(TApp, tupleintPair) { | |
| 609 | + std::tuple<int, std::pair<int, double>> custom_opt; | |
| 610 | + | |
| 611 | + app.add_option("--pr", custom_opt); | |
| 612 | + | |
| 613 | + args = {"--pr", "3", "1", "2"}; | |
| 614 | + | |
| 615 | + run(); | |
| 616 | + EXPECT_EQ(std::get<0>(custom_opt), 3); | |
| 617 | + EXPECT_EQ(std::get<1>(custom_opt).first, 1); | |
| 618 | + EXPECT_EQ(std::get<1>(custom_opt).second, 2.0); | |
| 619 | +} | |
| 620 | + | |
| 621 | +static_assert(CLI::detail::is_mutable_container<std::set<std::string>>::value, "set should be a container"); | |
| 622 | +static_assert(CLI::detail::is_mutable_container<std::map<std::string, std::string>>::value, | |
| 623 | + "map should be a container"); | |
| 624 | +static_assert(CLI::detail::is_mutable_container<std::unordered_map<std::string, double>>::value, | |
| 625 | + "unordered_map should be a container"); | |
| 626 | + | |
| 627 | +static_assert(CLI::detail::is_mutable_container<std::list<std::pair<int, std::string>>>::value, | |
| 628 | + "list should be a container"); | |
| 629 | + | |
| 630 | +static_assert(CLI::detail::type_count<std::set<std::string>>::value == 1, "set should have a type size of 1"); | |
| 631 | +static_assert(CLI::detail::type_count<std::set<std::tuple<std::string, int, int>>>::value == 3, | |
| 632 | + "tuple set should have size of 3"); | |
| 633 | +static_assert(CLI::detail::type_count<std::map<std::string, std::string>>::value == 2, | |
| 634 | + "map should have a type size of 2"); | |
| 635 | +static_assert(CLI::detail::type_count<std::unordered_map<std::string, double>>::value == 2, | |
| 636 | + "unordered_map should have a type size of 2"); | |
| 637 | + | |
| 638 | +static_assert(CLI::detail::type_count<std::list<std::pair<int, std::string>>>::value == 2, | |
| 639 | + "list<int,string> should have a type size of 2"); | |
| 640 | +static_assert(CLI::detail::type_count<std::map<std::string, std::pair<int, std::string>>>::value == 3, | |
| 641 | + "map<string,pair<int,string>> should have a type size of 3"); | |
| 642 | + | |
| 643 | +template <class T> class TApp_container_single : public TApp { | |
| 644 | + public: | |
| 645 | + using container_type = T; | |
| 646 | + container_type cval{}; | |
| 647 | + TApp_container_single() : TApp(){}; | |
| 648 | +}; | |
| 649 | + | |
| 650 | +using containerTypes_single = | |
| 651 | + ::testing::Types<std::vector<int>, std::deque<int>, std::set<int>, std::list<int>, std::unordered_set<int>>; | |
| 652 | + | |
| 653 | +TYPED_TEST_SUITE(TApp_container_single, containerTypes_single, ); | |
| 654 | + | |
| 655 | +TYPED_TEST(TApp_container_single, containerInt) { | |
| 656 | + | |
| 657 | + auto &cv = TApp_container_single<TypeParam>::cval; | |
| 658 | + CLI::Option *opt = (TApp::app).add_option("-v", cv); | |
| 659 | + | |
| 660 | + TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; | |
| 661 | + TApp::run(); | |
| 662 | + EXPECT_EQ(4u, (TApp::app).count("-v")); | |
| 663 | + EXPECT_EQ(4u, cv.size()); | |
| 664 | + opt->check(CLI::PositiveNumber.application_index(0)); | |
| 665 | + opt->check((!CLI::PositiveNumber).application_index(1)); | |
| 666 | + EXPECT_NO_THROW(TApp::run()); | |
| 667 | + EXPECT_EQ(4u, cv.size()); | |
| 668 | + // v[3] would be negative | |
| 669 | + opt->check(CLI::PositiveNumber.application_index(3)); | |
| 670 | + EXPECT_THROW(TApp::run(), CLI::ValidationError); | |
| 671 | +} | |
| 672 | + | |
| 673 | +template <class T> class TApp_container_pair : public TApp { | |
| 674 | + public: | |
| 675 | + using container_type = T; | |
| 676 | + container_type cval{}; | |
| 677 | + TApp_container_pair() : TApp(){}; | |
| 678 | +}; | |
| 679 | + | |
| 680 | +using isp = std::pair<int, std::string>; | |
| 681 | +using containerTypes_pair = ::testing::Types<std::vector<isp>, | |
| 682 | + std::deque<isp>, | |
| 683 | + std::set<isp>, | |
| 684 | + std::list<isp>, | |
| 685 | + std::map<int, std::string>, | |
| 686 | + std::unordered_map<int, std::string>>; | |
| 687 | + | |
| 688 | +TYPED_TEST_SUITE(TApp_container_pair, containerTypes_pair, ); | |
| 689 | + | |
| 690 | +TYPED_TEST(TApp_container_pair, containerPair) { | |
| 691 | + | |
| 692 | + auto &cv = TApp_container_pair<TypeParam>::cval; | |
| 693 | + (TApp::app).add_option("--dict", cv); | |
| 694 | + | |
| 695 | + TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"}; | |
| 696 | + | |
| 697 | + TApp::run(); | |
| 698 | + EXPECT_EQ(cv.size(), 2u); | |
| 699 | + | |
| 700 | + TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; | |
| 701 | + TApp::run(); | |
| 702 | + EXPECT_EQ(cv.size(), 3u); | |
| 703 | +} | |
| 704 | + | |
| 705 | +template <class T> class TApp_container_tuple : public TApp { | |
| 706 | + public: | |
| 707 | + using container_type = T; | |
| 708 | + container_type cval{}; | |
| 709 | + TApp_container_tuple() : TApp(){}; | |
| 710 | +}; | |
| 711 | + | |
| 712 | +using tup_obj = std::tuple<int, std::string, double>; | |
| 713 | +using containerTypes_tuple = ::testing::Types<std::vector<tup_obj>, | |
| 714 | + std::deque<tup_obj>, | |
| 715 | + std::set<tup_obj>, | |
| 716 | + std::list<tup_obj>, | |
| 717 | + std::map<int, std::pair<std::string, double>>, | |
| 718 | + std::unordered_map<int, std::tuple<std::string, double>>>; | |
| 719 | + | |
| 720 | +TYPED_TEST_SUITE(TApp_container_tuple, containerTypes_tuple, ); | |
| 721 | + | |
| 722 | +TYPED_TEST(TApp_container_tuple, containerTuple) { | |
| 723 | + | |
| 724 | + auto &cv = TApp_container_tuple<TypeParam>::cval; | |
| 725 | + (TApp::app).add_option("--dict", cv); | |
| 726 | + | |
| 727 | + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; | |
| 728 | + | |
| 729 | + TApp::run(); | |
| 730 | + EXPECT_EQ(cv.size(), 2u); | |
| 731 | + | |
| 732 | + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; | |
| 733 | + TApp::run(); | |
| 734 | + EXPECT_EQ(cv.size(), 3u); | |
| 735 | +} | |
| 736 | + | |
| 737 | +using icontainer1 = std::vector<int>; | |
| 738 | +using icontainer2 = std::list<int>; | |
| 739 | +using icontainer3 = std::set<int>; | |
| 740 | +using icontainer4 = std::pair<int, std::vector<int>>; | |
| 741 | + | |
| 742 | +using containerTypes_container = ::testing::Types<std::vector<icontainer1>, | |
| 743 | + std::list<icontainer1>, | |
| 744 | + std::set<icontainer1>, | |
| 745 | + std::deque<icontainer1>, | |
| 746 | + std::vector<icontainer2>, | |
| 747 | + std::list<icontainer2>, | |
| 748 | + std::set<icontainer2>, | |
| 749 | + std::deque<icontainer2>, | |
| 750 | + std::vector<icontainer3>, | |
| 751 | + std::list<icontainer3>, | |
| 752 | + std::set<icontainer3>, | |
| 753 | + std::deque<icontainer3>>; | |
| 754 | + | |
| 755 | +template <class T> class TApp_container_container : public TApp { | |
| 756 | + public: | |
| 757 | + using container_type = T; | |
| 758 | + container_type cval{}; | |
| 759 | + TApp_container_container() : TApp(){}; | |
| 760 | +}; | |
| 761 | + | |
| 762 | +TYPED_TEST_SUITE(TApp_container_container, containerTypes_container, ); | |
| 763 | + | |
| 764 | +TYPED_TEST(TApp_container_container, containerContainer) { | |
| 765 | + | |
| 766 | + auto &cv = TApp_container_container<TypeParam>::cval; | |
| 767 | + (TApp::app).add_option("--dict", cv); | |
| 768 | + | |
| 769 | + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; | |
| 770 | + | |
| 771 | + TApp::run(); | |
| 772 | + EXPECT_EQ(cv.size(), 2u); | |
| 773 | + | |
| 774 | + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", | |
| 775 | + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; | |
| 776 | + TApp::run(); | |
| 777 | + EXPECT_EQ(cv.size(), 4u); | |
| 778 | +} | |
| 779 | + | |
| 780 | +TEST_F(TApp, containerContainer) { | |
| 781 | + | |
| 782 | + std::vector<icontainer4> cv; | |
| 783 | + app.add_option("--dict", cv); | |
| 784 | + | |
| 785 | + args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; | |
| 786 | + | |
| 787 | + run(); | |
| 788 | + EXPECT_EQ(cv.size(), 2u); | |
| 789 | + | |
| 790 | + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "", "--dict", | |
| 791 | + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; | |
| 792 | + run(); | |
| 793 | + EXPECT_EQ(cv.size(), 4u); | |
| 794 | +} | |
| 795 | + | |
| 796 | +TEST_F(TApp, unknownContainerWrapper) { | |
| 797 | + | |
| 798 | + class vopt { | |
| 799 | + public: | |
| 800 | + vopt() = default; | |
| 801 | + vopt(const std::vector<double> &vdub) : val_{vdub} {}; | |
| 802 | + std::vector<double> val_{}; | |
| 803 | + }; | |
| 804 | + | |
| 805 | + vopt cv; | |
| 806 | + app.add_option<vopt, std::vector<double>>("--vv", cv); | |
| 807 | + | |
| 808 | + args = {"--vv", "1", "2", "4"}; | |
| 809 | + | |
| 810 | + run(); | |
| 811 | + EXPECT_EQ(cv.val_.size(), 3u); | |
| 812 | + args = {"--vv", ""}; | |
| 813 | + | |
| 814 | + run(); | |
| 815 | + EXPECT_TRUE(cv.val_.empty()); | |
| 816 | +} | |
| 817 | + | |
| 818 | +TEST_F(TApp, tupleTwoVectors) { | |
| 819 | + | |
| 820 | + std::tuple<std::vector<int>, std::vector<int>> cv; | |
| 821 | + app.add_option("--vv", cv); | |
| 822 | + | |
| 823 | + args = {"--vv", "1", "2", "4"}; | |
| 824 | + | |
| 825 | + run(); | |
| 826 | + EXPECT_EQ(std::get<0>(cv).size(), 3U); | |
| 827 | + EXPECT_TRUE(std::get<1>(cv).empty()); | |
| 828 | + | |
| 829 | + args = {"--vv", "1", "2", "%%", "4", "4", "5"}; | |
| 830 | + | |
| 831 | + run(); | |
| 832 | + EXPECT_EQ(std::get<0>(cv).size(), 2U); | |
| 833 | + EXPECT_EQ(std::get<1>(cv).size(), 3U); | |
| 834 | +} | ... | ... |
tests/OptionalTest.cpp
| 1 | +#include <complex> | |
| 1 | 2 | #include <cstdint> |
| 2 | 3 | #include <cstdlib> |
| 3 | 4 | #include <iostream> |
| ... | ... | @@ -72,6 +73,44 @@ TEST_F(TApp, StdOptionalTest) { |
| 72 | 73 | EXPECT_EQ(*opt, 3); |
| 73 | 74 | } |
| 74 | 75 | |
| 76 | +TEST_F(TApp, StdOptionalVectorEmptyDirect) { | |
| 77 | + std::optional<std::vector<int>> opt; | |
| 78 | + app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); | |
| 79 | + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); | |
| 80 | + run(); | |
| 81 | + EXPECT_FALSE(opt); | |
| 82 | + args = {"-v"}; | |
| 83 | + opt = std::vector<int>{4, 3}; | |
| 84 | + run(); | |
| 85 | + EXPECT_FALSE(opt); | |
| 86 | + args = {"-v", "1", "4", "5"}; | |
| 87 | + run(); | |
| 88 | + EXPECT_TRUE(opt); | |
| 89 | + std::vector<int> expV{1, 4, 5}; | |
| 90 | + EXPECT_EQ(*opt, expV); | |
| 91 | +} | |
| 92 | + | |
| 93 | +TEST_F(TApp, StdOptionalComplexDirect) { | |
| 94 | + std::optional<std::complex<double>> opt; | |
| 95 | + app.add_option("-c,--complex", opt)->type_size(0, 2); | |
| 96 | + run(); | |
| 97 | + EXPECT_FALSE(opt); | |
| 98 | + args = {"-c"}; | |
| 99 | + opt = std::complex<double>{4.0, 3.0}; | |
| 100 | + run(); | |
| 101 | + EXPECT_FALSE(opt); | |
| 102 | + args = {"-c", "1+2j"}; | |
| 103 | + run(); | |
| 104 | + EXPECT_TRUE(opt); | |
| 105 | + std::complex<double> val{1, 2}; | |
| 106 | + EXPECT_EQ(*opt, val); | |
| 107 | + args = {"-c", "3", "-4"}; | |
| 108 | + run(); | |
| 109 | + EXPECT_TRUE(opt); | |
| 110 | + std::complex<double> val2{3, -4}; | |
| 111 | + EXPECT_EQ(*opt, val2); | |
| 112 | +} | |
| 113 | + | |
| 75 | 114 | #ifdef _MSC_VER |
| 76 | 115 | #pragma warning(default : 4244) |
| 77 | 116 | #endif |
| ... | ... | @@ -165,11 +204,16 @@ TEST_F(TApp, BoostOptionalStringTest) { |
| 165 | 204 | EXPECT_TRUE(opt); |
| 166 | 205 | EXPECT_EQ(*opt, "strv"); |
| 167 | 206 | } |
| 207 | +namespace boost { | |
| 208 | +using CLI::enums::operator<<; | |
| 209 | +} | |
| 168 | 210 | |
| 169 | 211 | TEST_F(TApp, BoostOptionalEnumTest) { |
| 212 | + | |
| 170 | 213 | enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 }; |
| 171 | - boost::optional<eval> opt; | |
| 214 | + boost::optional<eval> opt, opt2; | |
| 172 | 215 | auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt); |
| 216 | + app.add_option_no_stream("-e,--eval", opt2); | |
| 173 | 217 | optptr->capture_default_str(); |
| 174 | 218 | |
| 175 | 219 | auto dstring = optptr->get_default_str(); |
| ... | ... | @@ -206,6 +250,7 @@ TEST_F(TApp, BoostOptionalVector) { |
| 206 | 250 | TEST_F(TApp, BoostOptionalVectorEmpty) { |
| 207 | 251 | boost::optional<std::vector<int>> opt; |
| 208 | 252 | app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); |
| 253 | + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); | |
| 209 | 254 | run(); |
| 210 | 255 | EXPECT_FALSE(opt); |
| 211 | 256 | args = {"-v"}; |
| ... | ... | @@ -219,6 +264,44 @@ TEST_F(TApp, BoostOptionalVectorEmpty) { |
| 219 | 264 | EXPECT_EQ(*opt, expV); |
| 220 | 265 | } |
| 221 | 266 | |
| 267 | +TEST_F(TApp, BoostOptionalVectorEmptyDirect) { | |
| 268 | + boost::optional<std::vector<int>> opt; | |
| 269 | + app.add_option_no_stream("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); | |
| 270 | + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); | |
| 271 | + run(); | |
| 272 | + EXPECT_FALSE(opt); | |
| 273 | + args = {"-v"}; | |
| 274 | + opt = std::vector<int>{4, 3}; | |
| 275 | + run(); | |
| 276 | + EXPECT_FALSE(opt); | |
| 277 | + args = {"-v", "1", "4", "5"}; | |
| 278 | + run(); | |
| 279 | + EXPECT_TRUE(opt); | |
| 280 | + std::vector<int> expV{1, 4, 5}; | |
| 281 | + EXPECT_EQ(*opt, expV); | |
| 282 | +} | |
| 283 | + | |
| 284 | +TEST_F(TApp, BoostOptionalComplexDirect) { | |
| 285 | + boost::optional<std::complex<double>> opt; | |
| 286 | + app.add_option("-c,--complex", opt)->type_size(0, 2); | |
| 287 | + run(); | |
| 288 | + EXPECT_FALSE(opt); | |
| 289 | + args = {"-c"}; | |
| 290 | + opt = std::complex<double>{4.0, 3.0}; | |
| 291 | + run(); | |
| 292 | + EXPECT_FALSE(opt); | |
| 293 | + args = {"-c", "1+2j"}; | |
| 294 | + run(); | |
| 295 | + EXPECT_TRUE(opt); | |
| 296 | + std::complex<double> val{1, 2}; | |
| 297 | + EXPECT_EQ(*opt, val); | |
| 298 | + args = {"-c", "3", "-4"}; | |
| 299 | + run(); | |
| 300 | + EXPECT_TRUE(opt); | |
| 301 | + std::complex<double> val2{3, -4}; | |
| 302 | + EXPECT_EQ(*opt, val2); | |
| 303 | +} | |
| 304 | + | |
| 222 | 305 | #endif |
| 223 | 306 | |
| 224 | 307 | #if !CLI11_OPTIONAL | ... | ... |
tests/TrueFalseTest.cpp
| 1 | 1 | #include "app_helper.hpp" |
| 2 | 2 | |
| 3 | 3 | /// This allows a set of strings to be run over by a test |
| 4 | -struct TApp_TBO : public TApp, public ::testing::WithParamInterface<const char *> {}; | |
| 4 | +struct TApp_TBO : public TApp_base, testing::TestWithParam<const char *> {}; | |
| 5 | 5 | |
| 6 | 6 | TEST_P(TApp_TBO, TrueBoolOption) { |
| 7 | 7 | bool value{false}; // Not used, but set just in case |
| ... | ... | @@ -13,10 +13,10 @@ TEST_P(TApp_TBO, TrueBoolOption) { |
| 13 | 13 | } |
| 14 | 14 | |
| 15 | 15 | // Change to INSTANTIATE_TEST_SUITE_P in GTest master |
| 16 | -INSTANTIATE_TEST_CASE_P(TrueBoolOptions, TApp_TBO, ::testing::Values("true", "on", "True", "ON"), ); | |
| 16 | +INSTANTIATE_TEST_SUITE_P(TrueBoolOptions_test, TApp_TBO, testing::Values("true", "on", "True", "ON")); | |
| 17 | 17 | |
| 18 | 18 | /// This allows a set of strings to be run over by a test |
| 19 | -struct TApp_FBO : public TApp, public ::testing::WithParamInterface<const char *> {}; | |
| 19 | +struct TApp_FBO : public TApp_base, public ::testing::TestWithParam<const char *> {}; | |
| 20 | 20 | |
| 21 | 21 | TEST_P(TApp_FBO, FalseBoolOptions) { |
| 22 | 22 | bool value{true}; // Not used, but set just in case |
| ... | ... | @@ -27,4 +27,4 @@ TEST_P(TApp_FBO, FalseBoolOptions) { |
| 27 | 27 | EXPECT_FALSE(value); |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | -INSTANTIATE_TEST_CASE_P(FalseBoolOptions, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"), ); | |
| 30 | +INSTANTIATE_TEST_SUITE_P(FalseBoolOptions_test, TApp_FBO, ::testing::Values("false", "off", "False", "OFF")); | ... | ... |
tests/app_helper.hpp
| ... | ... | @@ -11,10 +11,11 @@ |
| 11 | 11 | |
| 12 | 12 | using input_t = std::vector<std::string>; |
| 13 | 13 | |
| 14 | -struct TApp : public ::testing::Test { | |
| 14 | +class TApp_base { | |
| 15 | + public: | |
| 15 | 16 | CLI::App app{"My Test Program"}; |
| 16 | 17 | input_t args{}; |
| 17 | - | |
| 18 | + virtual ~TApp_base() = default; | |
| 18 | 19 | void run() { |
| 19 | 20 | // It is okay to re-parse - clear is called automatically before a parse. |
| 20 | 21 | input_t newargs = args; |
| ... | ... | @@ -23,6 +24,8 @@ struct TApp : public ::testing::Test { |
| 23 | 24 | } |
| 24 | 25 | }; |
| 25 | 26 | |
| 27 | +class TApp : public TApp_base, public ::testing::Test {}; | |
| 28 | + | |
| 26 | 29 | class TempFile { |
| 27 | 30 | std::string _name{}; |
| 28 | 31 | ... | ... |