Commit ed5cd896362363f0fc1c3b4e4e1bb8ee308a688d

Authored by Philip Top
Committed by Henry Schreiner
1 parent 1a1cde98

remove template for operator[] and adjust some tests

add some comments in readME about performance

move operator[] to return const Option *

Apply suggestions from code review

Co-Authored-By: phlptp <top1@llnl.gov>

update readme and add some IniTests and fix a bug from the tests

add_flag_callback

add a few tests to capture the different paths

fix incorrectly updated CMAKE file, and add some subcommand test for option finding

add disable_flag_override and work out some kinks in the find option functions

add some more tests and fix a few bugs in as<> function for options

Allow general flag types and default values, add shortcut notation for retrieving values
README.md
@@ -177,13 +177,15 @@ The initialization is just one line, adding options is just two each. The parse @@ -177,13 +177,15 @@ The initialization is just one line, adding options is just two each. The parse
177 While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are: 177 While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are:
178 178
179 ```cpp 179 ```cpp
  180 +app.add_option(option_name, help_str="")
  181 +
180 app.add_option(option_name, 182 app.add_option(option_name,
181 - variable_to_bind_to, // bool, int, float, vector, enum, or string-like 183 + variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string
182 help_string="", 184 help_string="",
183 default=false) 185 default=false)
184 186
185 app.add_option_function<type>(option_name, 187 app.add_option_function<type>(option_name,
186 - function <void(const type &value)>, // int, float, enum, vector, or string-like 188 + function <void(const type &value)>, // int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string
187 help_string="") 189 help_string="")
188 190
189 app.add_complex(... // Special case: support for complex numbers 191 app.add_complex(... // Special case: support for complex numbers
@@ -192,17 +194,19 @@ app.add_flag(option_name, @@ -192,17 +194,19 @@ app.add_flag(option_name,
192 help_string="") 194 help_string="")
193 195
194 app.add_flag(option_name, 196 app.add_flag(option_name,
195 - int_or_bool, 197 + variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string
196 help_string="") 198 help_string="")
197 199
198 app.add_flag_function(option_name, 200 app.add_flag_function(option_name,
199 - function <void(int count)>, 201 + function <void(int64_t count)>,
200 help_string="") 202 help_string="")
201 203
  204 +app.add_flag_callback(option_name,function<void(void)>,help_string="")
  205 +
202 App* subcom = app.add_subcommand(name, description); 206 App* subcom = app.add_subcommand(name, description);
203 ``` 207 ```
204 208
205 -An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`. 209 +An option name must start with a alphabetic character, underscore, or a number. For long options, anything but an equals sign or a comma is valid after that, though for the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
206 210
207 The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. 211 The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function.
208 212
@@ -210,27 +214,37 @@ Flag options specified through the functions @@ -210,27 +214,37 @@ Flag options specified through the functions
210 214
211 ```cpp 215 ```cpp
212 app.add_flag(option_name, 216 app.add_flag(option_name,
213 - int_or_bool, 217 + help_string="")
  218 +
  219 +app.add_flag(option_name,
  220 + variable_to_bind_to,
214 help_string="") 221 help_string="")
215 222
216 app.add_flag_function(option_name, 223 app.add_flag_function(option_name,
217 - function <void(int count)>, 224 + function <void(int64_t count)>,
218 help_string="") 225 help_string="")
  226 +
  227 +app.add_flag_callback(option_name,function<void(void)>,help_string="")
219 ``` 228 ```
220 229
221 -which allow a syntax for the option names to default particular options to a false value if some flags are passed. For example: 230 +which allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example:
222 231
223 ```cpp 232 ```cpp
224 app.add_flag("--flag,!--no-flag,result,"help for flag");` 233 app.add_flag("--flag,!--no-flag,result,"help for flag");`
225 `````` 234 ``````
226 235
227 specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is 236 specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is
228 -passed result will contain false or -1 if result is a signed integer type, or 0 if it is an unsigned type. An 237 +passed `result` will contain false or -1 if `result` is a signed integer type, or 0 if it is an unsigned type. An
229 alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous 238 alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous
230 -example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `int_or_bool` is a boolean value the  
231 -default behavior is to take the last value given, while if `int_or_bool` is an integer type the behavior will be to sum  
232 -all the given arguments and return the result. This can be modified if needed by changing the `multi_option_policy` on  
233 -each flag (this is not inherited). 239 +example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `variable_to_bind_to` is anything but an integer value the
  240 +default behavior is to take the last value given, while if `variable_to_bind_to` is an integer type the behavior will be to sum
  241 +all the given arguments and return the result. This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited).
  242 +The default value can be any value For example if you wished to define a numerical flag
  243 +```cpp
  244 +app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag")
  245 +```
  246 +using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type.
  247 +
234 248
235 On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. 249 On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.
236 250
@@ -258,6 +272,7 @@ Before parsing, you can set the following options: @@ -258,6 +272,7 @@ Before parsing, you can set the following options:
258 - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). 272 - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
259 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). 273 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
260 - `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character 274 - `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
  275 +- `->disable_flag_override()`: from the command line long form flag option 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.
261 - `->description(str)`: Set/change the description. 276 - `->description(str)`: Set/change the description.
262 - `->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). 277 - `->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).
263 - `->check(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options. 278 - `->check(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options.
@@ -296,7 +311,7 @@ On the command line, options can be given as: @@ -296,7 +311,7 @@ On the command line, options can be given as:
296 - `-ffilename` (no space required) 311 - `-ffilename` (no space required)
297 - `-abcf filename` (flags and option can be combined) 312 - `-abcf filename` (flags and option can be combined)
298 - `--long` (long flag) 313 - `--long` (long flag)
299 -- `--long_flag=true` (long flag with equals) 314 +- `--long_flag=true` (long flag with equals to override default value)
300 - `--file filename` (space) 315 - `--file filename` (space)
301 - `--file=filename` (equals) 316 - `--file=filename` (equals)
302 317
@@ -306,9 +321,10 @@ If `allow_windows_style_options()` is specified in the application or subcommand @@ -306,9 +321,10 @@ If `allow_windows_style_options()` is specified in the application or subcommand
306 - `/long` (long flag) 321 - `/long` (long flag)
307 - `/file filename` (space) 322 - `/file filename` (space)
308 - `/file:filename` (colon) 323 - `/file:filename` (colon)
  324 +- `/long_flag:false (long flag with : to override the default value)
309 = Windows style options do not allow combining short options or values not separated from the short option like with `-` options 325 = Windows style options do not allow combining short options or values not separated from the short option like with `-` options
310 326
311 -Long flag options may be given with and `=<value>` to allow specifying a false value See [config files](#configuration-file) for details on the values supported. NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument. This syntax can override the default (true or false) values. 327 +Long flag options may be given with an `=<value>` to allow specifying a false value, or some other value to the flag. See [config files](#configuration-file) for details on the values supported. NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument. This syntax can override the default values, and can be disabled by using `disable_flag_override()`.
312 328
313 Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. 329 Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments.
314 If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). 330 If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included).
@@ -317,10 +333,18 @@ You can access a vector of pointers to the parsed options in the original order @@ -317,10 +333,18 @@ You can access a vector of pointers to the parsed options in the original order
317 If `--` is present in the command line that does not end an unlimited option, then 333 If `--` is present in the command line that does not end an unlimited option, then
318 everything after that is positional only. 334 everything after that is positional only.
319 335
  336 +#### Getting results
  337 +In most cases the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:
  338 +
  339 +- `results()`: retrieves a vector of strings with all the results in the order they were given.
  340 +- `results(variable_to_bind_to)`: gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
  341 +- `results(vector_type_variable,delimiter)`: gets the results to a vector type and uses a delimiter to further split the values
  342 +- `Value=as<type>()`: returns the result or default value directly as the specified type if possible.
  343 +- `Vector_value=as<type>(delimiter): same the results function with the delimiter but returns the value directly.
  344 +
320 ### Subcommands 345 ### Subcommands
321 346
322 -Subcommands are supported, and can be nested infinitely. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. `->ignore_underscore()` is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including ignore  
323 -case). 347 +Subcommands are supported, and can be nested infinitely. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. `->ignore_underscore()` is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including `ignore_case` and `ignore_underscore`).
324 348
325 If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed. 349 If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed.
326 0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous 350 0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous
@@ -335,7 +359,7 @@ You are allowed to throw `CLI::Success` in the callbacks. @@ -335,7 +359,7 @@ You are allowed to throw `CLI::Success` in the callbacks.
335 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). 359 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
336 360
337 Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments. 361 Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
338 -Nameless subcommands function a little like groups in the main `App`. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. 362 +Nameless subcommands function a similarly to groups in the main `App`. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed.
339 363
340 #### Subcommand options 364 #### Subcommand options
341 365
@@ -353,7 +377,8 @@ There are several options that are supported on the main app and subcommands. Th @@ -353,7 +377,8 @@ There are several options that are supported on the main app and subcommands. Th
353 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. 377 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
354 - `.get_subcommands(filter)`: The list of subcommands given on the command line. 378 - `.get_subcommands(filter)`: The list of subcommands given on the command line.
355 - `.get_parent()`: Get the parent App or nullptr if called on master App. 379 - `.get_parent()`: Get the parent App or nullptr if called on master App.
356 -- `.get_option(name)`: Get an option pointer by option name 380 +- `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched
  381 +- `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available.
357 - `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats). 382 - `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
358 - `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). 383 - `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates).
359 - `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details. 384 - `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details.
@@ -370,6 +395,7 @@ There are several options that are supported on the main app and subcommands. Th @@ -370,6 +395,7 @@ There are several options that are supported on the main app and subcommands. Th
370 - `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands. 395 - `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands.
371 - `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). 396 - `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
372 - `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand. 397 - `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
  398 +- `[option_name]`: retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>() will get the results of the command line for a flag
373 399
374 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function. 400 > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function.
375 401
@@ -408,7 +434,7 @@ arguments, use `.config_to_str(default_also=false, prefix=&quot;&quot;, write_description= @@ -408,7 +434,7 @@ arguments, use `.config_to_str(default_also=false, prefix=&quot;&quot;, write_description=
408 434
409 Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. 435 Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
410 436
411 -Options have defaults for `group`, `required`, `multi_option_policy`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example: 437 +Options have defaults for `group`, `required`, `disable_flag_override`,`multi_option_policy`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
412 438
413 ```cpp 439 ```cpp
414 app.option_defaults()->required(); 440 app.option_defaults()->required();
examples/CMakeLists.txt
@@ -132,6 +132,11 @@ add_test(NAME enum_fail COMMAND enum -l 4) @@ -132,6 +132,11 @@ add_test(NAME enum_fail COMMAND enum -l 4)
132 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION 132 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
133 "--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}") 133 "--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}")
134 134
  135 +add_cli_exe(digit_args digit_args.cpp)
  136 +add_test(NAME digit_args COMMAND digit_args -h)
  137 +set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION
  138 + "-3{3}")
  139 +
135 add_cli_exe(modhelp modhelp.cpp) 140 add_cli_exe(modhelp modhelp.cpp)
136 add_test(NAME modhelp COMMAND modhelp -a test -h) 141 add_test(NAME modhelp COMMAND modhelp -a test -h)
137 set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION 142 set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION
examples/digit_args.cpp 0 โ†’ 100644
  1 +#include <CLI/CLI.hpp>
  2 +#include <iostream>
  3 +
  4 +int main(int argc, char **argv) {
  5 + CLI::App app;
  6 +
  7 + int val;
  8 + // add a set of flags with default values associate with them
  9 + app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val, "compression level");
  10 +
  11 + CLI11_PARSE(app, argc, argv);
  12 +
  13 + std::cout << "value = " << val << std::endl;
  14 + return 0;
  15 +}
include/CLI/App.hpp
@@ -402,9 +402,22 @@ class App { @@ -402,9 +402,22 @@ class App {
402 opt->type_name(detail::type_name<T>()); 402 opt->type_name(detail::type_name<T>());
403 return opt; 403 return opt;
404 } 404 }
  405 + /// Add option with no description or variable assignment
  406 + Option *add_option(std::string option_name) {
  407 + return add_option(option_name, CLI::callback_t(), std::string{}, false);
  408 + }
  409 +
  410 + /// Add option with description but with no variable assignment or callback
  411 + template <typename T,
  412 + enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
  413 + detail::dummy>
  414 + Option *add_option(std::string option_name, T &option_description) {
  415 + return add_option(option_name, CLI::callback_t(), option_description, false);
  416 + }
405 417
406 /// Add option for non-vectors with a default print 418 /// Add option for non-vectors with a default print
407 - template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> 419 + template <typename T,
  420 + enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy>
408 Option *add_option(std::string option_name, 421 Option *add_option(std::string option_name,
409 T &variable, ///< The variable to set 422 T &variable, ///< The variable to set
410 std::string option_description, 423 std::string option_description,
@@ -523,7 +536,8 @@ class App { @@ -523,7 +536,8 @@ class App {
523 } 536 }
524 537
525 /// Set a help flag, replace the existing one if present 538 /// Set a help flag, replace the existing one if present
526 - Option *set_help_flag(std::string flag_name = "", std::string help_description = "") { 539 + Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
  540 + // take flag_description by const reference otherwise add_flag tries to assign to help_description
527 if(help_ptr_ != nullptr) { 541 if(help_ptr_ != nullptr) {
528 remove_option(help_ptr_); 542 remove_option(help_ptr_);
529 help_ptr_ = nullptr; 543 help_ptr_ = nullptr;
@@ -531,7 +545,7 @@ class App { @@ -531,7 +545,7 @@ class App {
531 545
532 // Empty name will simply remove the help flag 546 // Empty name will simply remove the help flag
533 if(!flag_name.empty()) { 547 if(!flag_name.empty()) {
534 - help_ptr_ = add_flag(flag_name, std::move(help_description)); 548 + help_ptr_ = add_flag(flag_name, help_description);
535 help_ptr_->configurable(false); 549 help_ptr_->configurable(false);
536 } 550 }
537 551
@@ -539,7 +553,8 @@ class App { @@ -539,7 +553,8 @@ class App {
539 } 553 }
540 554
541 /// Set a help all flag, replaced the existing one if present 555 /// Set a help all flag, replaced the existing one if present
542 - Option *set_help_all_flag(std::string help_name = "", std::string help_description = "") { 556 + Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") {
  557 + // take flag_description by const reference otherwise add_flag tries to assign to flag_description
543 if(help_all_ptr_ != nullptr) { 558 if(help_all_ptr_ != nullptr) {
544 remove_option(help_all_ptr_); 559 remove_option(help_all_ptr_);
545 help_all_ptr_ = nullptr; 560 help_all_ptr_ = nullptr;
@@ -547,109 +562,149 @@ class App { @@ -547,109 +562,149 @@ class App {
547 562
548 // Empty name will simply remove the help all flag 563 // Empty name will simply remove the help all flag
549 if(!help_name.empty()) { 564 if(!help_name.empty()) {
550 - help_all_ptr_ = add_flag(help_name, std::move(help_description)); 565 + help_all_ptr_ = add_flag(help_name, help_description);
551 help_all_ptr_->configurable(false); 566 help_all_ptr_->configurable(false);
552 } 567 }
553 568
554 return help_all_ptr_; 569 return help_all_ptr_;
555 } 570 }
556 571
557 - /// Add option for flag  
558 - Option *add_flag(std::string flag_name, std::string flag_description = "") {  
559 - CLI::callback_t fun = [](CLI::results_t) { return true; };  
560 - Option *opt = add_option(flag_name, fun, flag_description, false);  
561 - if(opt->get_positional())  
562 - throw IncorrectConstruction::PositionalFlag(flag_name); 572 + private:
  573 + /// Internal function for adding a flag
  574 + Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
  575 + Option *opt;
  576 + if(detail::has_default_flag_values(flag_name)) {
  577 + // check for default values and if it has them
  578 + auto flag_defaults = detail::get_default_flag_values(flag_name);
  579 + detail::remove_default_flag_values(flag_name);
  580 + opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
  581 + for(const auto &fname : flag_defaults)
  582 + opt->fnames_.push_back(fname.first);
  583 + opt->default_flag_values_ = std::move(flag_defaults);
  584 + } else {
  585 + opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
  586 + }
  587 + // flags cannot have positional values
  588 + if(opt->get_positional()) {
  589 + auto pos_name = opt->get_name(true);
  590 + remove_option(opt);
  591 + throw IncorrectConstruction::PositionalFlag(pos_name);
  592 + }
  593 +
563 opt->type_size(0); 594 opt->type_size(0);
564 return opt; 595 return opt;
565 } 596 }
566 597
567 - /// Add option for flag integer 598 + public:
  599 + /// Add a flag with no description or variable assignment
  600 + Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); }
  601 +
  602 + /// Add flag with description but with no variable assignment or callback
  603 + /// takes a constant string, if a variable string is passed that variable will be assigned the results from the
  604 + /// flag
  605 + template <typename T,
  606 + enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
  607 + detail::dummy>
  608 + Option *add_flag(std::string flag_name, T &flag_description) {
  609 + return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);
  610 + }
  611 +
  612 + /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one if
  613 + /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
568 template <typename T, 614 template <typename T,
569 enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy> 615 enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy>
570 Option *add_flag(std::string flag_name, 616 Option *add_flag(std::string flag_name,
571 T &flag_count, ///< A variable holding the count 617 T &flag_count, ///< A variable holding the count
572 std::string flag_description = "") { 618 std::string flag_description = "") {
573 flag_count = 0; 619 flag_count = 0;
574 - Option *opt;  
575 CLI::callback_t fun = [&flag_count](CLI::results_t res) { 620 CLI::callback_t fun = [&flag_count](CLI::results_t res) {
576 - detail::sum_flag_vector(res, flag_count); 621 + try {
  622 + detail::sum_flag_vector(res, flag_count);
  623 + } catch(const std::invalid_argument &) {
  624 + return false;
  625 + }
577 return true; 626 return true;
578 }; 627 };
579 - if(detail::has_false_flags(flag_name)) {  
580 - std::vector<std::string> neg = detail::get_false_flags(flag_name);  
581 - detail::remove_false_flag_notation(flag_name);  
582 - opt = add_option(flag_name, fun, flag_description, false);  
583 - opt->fnames_ = std::move(neg);  
584 - } else {  
585 - opt = add_option(flag_name, fun, flag_description, false);  
586 - }  
587 -  
588 - if(opt->get_positional())  
589 - throw IncorrectConstruction::PositionalFlag(flag_name);  
590 - opt->type_size(0);  
591 - return opt; 628 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
592 } 629 }
593 630
594 - /// Bool version - defaults to allowing multiple passings, but can be forced to one if  
595 - /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.  
596 - template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> 631 + /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes that
  632 + /// can be converted from a string
  633 + template <typename T,
  634 + enable_if_t<!is_vector<T>::value && !std::is_const<T>::value &&
  635 + (!std::is_integral<T>::value || is_bool<T>::value) &&
  636 + !std::is_constructible<std::function<void(int)>, T>::value,
  637 + detail::enabler> = detail::dummy>
597 Option *add_flag(std::string flag_name, 638 Option *add_flag(std::string flag_name,
598 T &flag_result, ///< A variable holding true if passed 639 T &flag_result, ///< A variable holding true if passed
599 std::string flag_description = "") { 640 std::string flag_description = "") {
600 - flag_result = false;  
601 - Option *opt; 641 +
602 CLI::callback_t fun = [&flag_result](CLI::results_t res) { 642 CLI::callback_t fun = [&flag_result](CLI::results_t res) {
603 - flag_result = (res[0][0] != '-');  
604 - return res.size() == 1; 643 + if(res.size() != 1) {
  644 + return false;
  645 + }
  646 + return CLI::detail::lexical_cast(res[0], flag_result);
605 }; 647 };
606 - if(detail::has_false_flags(flag_name)) {  
607 - std::vector<std::string> neg = detail::get_false_flags(flag_name);  
608 - detail::remove_false_flag_notation(flag_name);  
609 - opt = add_option(flag_name, fun, std::move(flag_description), false);  
610 - opt->fnames_ = std::move(neg);  
611 - } else {  
612 - opt = add_option(flag_name, fun, std::move(flag_description), false);  
613 - } 648 + Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
  649 + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
  650 + return opt;
  651 + }
614 652
615 - if(opt->get_positional())  
616 - throw IncorrectConstruction::PositionalFlag(flag_name);  
617 - opt->type_size(0); 653 + /// Vector version to capture multiple flags.
  654 + template <typename T,
  655 + enable_if_t<!std::is_assignable<std::function<void(int64_t)>, T>::value, detail::enabler> = detail::dummy>
  656 + Option *add_flag(std::string flag_name,
  657 + std::vector<T> &flag_results, ///< A vector of values with the flag results
  658 + std::string flag_description = "") {
  659 + CLI::callback_t fun = [&flag_results](CLI::results_t res) {
  660 + bool retval = true;
  661 + for(const auto &elem : res) {
  662 + flag_results.emplace_back();
  663 + retval &= detail::lexical_cast(elem, flag_results.back());
  664 + }
  665 + return retval;
  666 + };
  667 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
  668 + }
  669 +
  670 + /// Add option for callback that is triggered with a true flag and takes no arguments
  671 + Option *add_flag_callback(std::string flag_name,
  672 + std::function<void(void)> function, ///< A function to call, void(void)
  673 + std::string flag_description = "") {
  674 +
  675 + CLI::callback_t fun = [function](CLI::results_t res) {
  676 + if(res.size() != 1) {
  677 + return false;
  678 + }
  679 + bool trigger;
  680 + auto result = CLI::detail::lexical_cast(res[0], trigger);
  681 + if(trigger)
  682 + function();
  683 + return result;
  684 + };
  685 + Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
618 opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); 686 opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
619 return opt; 687 return opt;
620 } 688 }
621 689
622 - /// Add option for callback 690 + /// Add option for callback with an integer value
623 Option *add_flag_function(std::string flag_name, 691 Option *add_flag_function(std::string flag_name,
624 - std::function<void(int)> function, ///< A function to call, void(size_t) 692 + std::function<void(int64_t)> function, ///< A function to call, void(int)
625 std::string flag_description = "") { 693 std::string flag_description = "") {
626 694
627 CLI::callback_t fun = [function](CLI::results_t res) { 695 CLI::callback_t fun = [function](CLI::results_t res) {
628 - int flag_count = 0; 696 + int64_t flag_count = 0;
629 detail::sum_flag_vector(res, flag_count); 697 detail::sum_flag_vector(res, flag_count);
630 function(flag_count); 698 function(flag_count);
631 return true; 699 return true;
632 }; 700 };
633 - Option *opt;  
634 - if(detail::has_false_flags(flag_name)) {  
635 - std::vector<std::string> neg = detail::get_false_flags(flag_name);  
636 - detail::remove_false_flag_notation(flag_name);  
637 - opt = add_option(flag_name, fun, std::move(flag_description), false);  
638 - opt->fnames_ = std::move(neg);  
639 - } else {  
640 - opt = add_option(flag_name, fun, std::move(flag_description), false);  
641 - }  
642 -  
643 - if(opt->get_positional())  
644 - throw IncorrectConstruction::PositionalFlag(flag_name);  
645 - opt->type_size(0);  
646 - return opt; 701 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
647 } 702 }
648 703
649 #ifdef CLI11_CPP14 704 #ifdef CLI11_CPP14
650 /// Add option for callback (C++14 or better only) 705 /// Add option for callback (C++14 or better only)
651 Option *add_flag(std::string flag_name, 706 Option *add_flag(std::string flag_name,
652 - std::function<void(int)> function, ///< A function to call, void(int) 707 + std::function<void(int64_t)> function, ///< A function to call, void(int)
653 std::string flag_description = "") { 708 std::string flag_description = "") {
654 return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description)); 709 return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description));
655 } 710 }
@@ -1187,14 +1242,7 @@ class App { @@ -1187,14 +1242,7 @@ class App {
1187 ///@{ 1242 ///@{
1188 1243
1189 /// Counts the number of times the given option was passed. 1244 /// Counts the number of times the given option was passed.
1190 - size_t count(std::string option_name) const {  
1191 - for(const Option_p &opt : options_) {  
1192 - if(opt->check_name(option_name)) {  
1193 - return opt->count();  
1194 - }  
1195 - }  
1196 - throw OptionNotFound(option_name);  
1197 - } 1245 + size_t count(std::string option_name) const { return get_option(option_name)->count(); }
1198 1246
1199 /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command 1247 /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command
1200 /// line order; use parsed = false to get the original definition list.) 1248 /// line order; use parsed = false to get the original definition list.)
@@ -1312,26 +1360,68 @@ class App { @@ -1312,26 +1360,68 @@ class App {
1312 return options; 1360 return options;
1313 } 1361 }
1314 1362
1315 - /// Get an option by name  
1316 - const Option *get_option(std::string option_name) const {  
1317 - for(const Option_p &opt : options_) { 1363 + /// Get an option by name (noexcept non-const version)
  1364 + Option *get_option_no_throw(std::string option_name) noexcept {
  1365 + for(Option_p &opt : options_) {
1318 if(opt->check_name(option_name)) { 1366 if(opt->check_name(option_name)) {
1319 return opt.get(); 1367 return opt.get();
1320 } 1368 }
1321 } 1369 }
1322 - throw OptionNotFound(option_name); 1370 + for(auto &subc : subcommands_) {
  1371 + // also check down into nameless subcommands
  1372 + if(subc->get_name().empty()) {
  1373 + auto opt = subc->get_option_no_throw(option_name);
  1374 + if(opt != nullptr) {
  1375 + return opt;
  1376 + }
  1377 + }
  1378 + }
  1379 + return nullptr;
1323 } 1380 }
1324 1381
1325 - /// Get an option by name (non-const version)  
1326 - Option *get_option(std::string option_name) {  
1327 - for(Option_p &opt : options_) { 1382 + /// Get an option by name (noexcept const version)
  1383 + const Option *get_option_no_throw(std::string option_name) const noexcept {
  1384 + for(const Option_p &opt : options_) {
1328 if(opt->check_name(option_name)) { 1385 if(opt->check_name(option_name)) {
1329 return opt.get(); 1386 return opt.get();
1330 } 1387 }
1331 } 1388 }
1332 - throw OptionNotFound(option_name); 1389 + for(const auto &subc : subcommands_) {
  1390 + // also check down into nameless subcommands
  1391 + if(subc->get_name().empty()) {
  1392 + auto opt = subc->get_option_no_throw(option_name);
  1393 + if(opt != nullptr) {
  1394 + return opt;
  1395 + }
  1396 + }
  1397 + }
  1398 + return nullptr;
  1399 + }
  1400 +
  1401 + /// Get an option by name
  1402 + const Option *get_option(std::string option_name) const {
  1403 + auto opt = get_option_no_throw(option_name);
  1404 + if(opt == nullptr) {
  1405 + throw OptionNotFound(option_name);
  1406 + }
  1407 + return opt;
  1408 + }
  1409 +
  1410 + /// Get an option by name (non-const version)
  1411 + Option *get_option(std::string option_name) {
  1412 + auto opt = get_option_no_throw(option_name);
  1413 + if(opt == nullptr) {
  1414 + throw OptionNotFound(option_name);
  1415 + }
  1416 + return opt;
1333 } 1417 }
1334 1418
  1419 + /// Shortcut bracket operator for getting a pointer to an option
  1420 + const Option *operator[](const std::string &option_name) const { return get_option(option_name); }
  1421 +
  1422 + /// Shortcut bracket operator for getting a pointer to an option
  1423 + const Option *operator[](const char *option_name) const { return get_option(option_name); }
  1424 +
1335 /// Check the status of ignore_case 1425 /// Check the status of ignore_case
1336 bool get_ignore_case() const { return ignore_case_; } 1426 bool get_ignore_case() const { return ignore_case_; }
1337 1427
@@ -1351,7 +1441,7 @@ class App { @@ -1351,7 +1441,7 @@ class App {
1351 const std::string &get_group() const { return group_; } 1441 const std::string &get_group() const { return group_; }
1352 1442
1353 /// Get footer. 1443 /// Get footer.
1354 - std::string get_footer() const { return footer_; } 1444 + const std::string &get_footer() const { return footer_; }
1355 1445
1356 /// Get the required min subcommand value 1446 /// Get the required min subcommand value
1357 size_t get_require_subcommand_min() const { return require_subcommand_min_; } 1447 size_t get_require_subcommand_min() const { return require_subcommand_min_; }
@@ -1526,7 +1616,7 @@ class App { @@ -1526,7 +1616,7 @@ class App {
1526 return detail::Classifier::LONG; 1616 return detail::Classifier::LONG;
1527 if(detail::split_short(current, dummy1, dummy2)) 1617 if(detail::split_short(current, dummy1, dummy2))
1528 return detail::Classifier::SHORT; 1618 return detail::Classifier::SHORT;
1529 - if((allow_windows_style_options_) && (detail::split_windows(current, dummy1, dummy2))) 1619 + if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
1530 return detail::Classifier::WINDOWS; 1620 return detail::Classifier::WINDOWS;
1531 return detail::Classifier::NONE; 1621 return detail::Classifier::NONE;
1532 } 1622 }
@@ -1735,10 +1825,8 @@ class App { @@ -1735,10 +1825,8 @@ class App {
1735 } 1825 }
1736 } 1826 }
1737 1827
1738 - Option *op;  
1739 - try {  
1740 - op = get_option("--" + item.name);  
1741 - } catch(const OptionNotFound &) { 1828 + Option *op = get_option_no_throw("--" + item.name);
  1829 + if(op == nullptr) {
1742 // If the option was not present 1830 // If the option was not present
1743 if(get_allow_config_extras()) 1831 if(get_allow_config_extras())
1744 // Should we worry about classifying the extras properly? 1832 // Should we worry about classifying the extras properly?
@@ -1753,9 +1841,8 @@ class App { @@ -1753,9 +1841,8 @@ class App {
1753 // Flag parsing 1841 // Flag parsing
1754 if(op->get_type_size() == 0) { 1842 if(op->get_type_size() == 0) {
1755 auto res = config_formatter_->to_flag(item); 1843 auto res = config_formatter_->to_flag(item);
1756 - if(op->check_fname(item.name)) {  
1757 - res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res));  
1758 - } 1844 + res = op->get_flag_value(item.name, res);
  1845 +
1759 op->add_result(res); 1846 op->add_result(res);
1760 1847
1761 } else { 1848 } else {
@@ -1894,7 +1981,7 @@ class App { @@ -1894,7 +1981,7 @@ class App {
1894 throw HorribleError("Short parsed but missing! You should not see this"); 1981 throw HorribleError("Short parsed but missing! You should not see this");
1895 break; 1982 break;
1896 case detail::Classifier::WINDOWS: 1983 case detail::Classifier::WINDOWS:
1897 - if(!detail::split_windows(current, arg_name, value)) 1984 + if(!detail::split_windows_style(current, arg_name, value))
1898 throw HorribleError("windows option parsed but missing! You should not see this"); 1985 throw HorribleError("windows option parsed but missing! You should not see this");
1899 break; 1986 break;
1900 default: 1987 default:
@@ -1946,16 +2033,9 @@ class App { @@ -1946,16 +2033,9 @@ class App {
1946 int collected = 0; 2033 int collected = 0;
1947 // deal with flag like things 2034 // deal with flag like things
1948 if(num == 0) { 2035 if(num == 0) {
1949 - try {  
1950 - auto res = (value.empty()) ? std ::string("1") : detail::to_flag_value(value);  
1951 - if(op->check_fname(arg_name)) {  
1952 - res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res));  
1953 - }  
1954 - op->add_result(res);  
1955 - parse_order_.push_back(op.get());  
1956 - } catch(const std::invalid_argument &) {  
1957 - throw ConversionError::TrueFalse(arg_name);  
1958 - } 2036 + auto res = op->get_flag_value(arg_name, value);
  2037 + op->add_result(res);
  2038 + parse_order_.push_back(op.get());
1959 } 2039 }
1960 // --this=value 2040 // --this=value
1961 else if(!value.empty()) { 2041 else if(!value.empty()) {
include/CLI/ConfigFwd.hpp
@@ -69,14 +69,10 @@ class Config { @@ -69,14 +69,10 @@ class Config {
69 /// Convert a configuration into an app 69 /// Convert a configuration into an app
70 virtual std::vector<ConfigItem> from_config(std::istream &) const = 0; 70 virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
71 71
72 - /// Convert a flag to a bool representation 72 + /// Get a flag value
73 virtual std::string to_flag(const ConfigItem &item) const { 73 virtual std::string to_flag(const ConfigItem &item) const {
74 if(item.inputs.size() == 1) { 74 if(item.inputs.size() == 1) {
75 - try {  
76 - return detail::to_flag_value(item.inputs.at(0));  
77 - } catch(const std::invalid_argument &) {  
78 - throw ConversionError::TrueFalse(item.fullname());  
79 - } 75 + return item.inputs.at(0);
80 } 76 }
81 throw ConversionError::TooManyInputsFlag(item.fullname()); 77 throw ConversionError::TooManyInputsFlag(item.fullname());
82 } 78 }
@@ -90,7 +86,7 @@ class Config { @@ -90,7 +86,7 @@ class Config {
90 return from_config(input); 86 return from_config(input);
91 } 87 }
92 88
93 - /// virtual destructor 89 + /// Virtual destructor
94 virtual ~Config() = default; 90 virtual ~Config() = default;
95 }; 91 };
96 92
include/CLI/Error.hpp
@@ -231,6 +231,9 @@ class ArgumentMismatch : public ParseError { @@ -231,6 +231,9 @@ class ArgumentMismatch : public ParseError {
231 static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { 231 static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
232 return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); 232 return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
233 } 233 }
  234 + static ArgumentMismatch FlagOverride(std::string name) {
  235 + return ArgumentMismatch(name + " was given a disallowed flag override");
  236 + }
234 }; 237 };
235 238
236 /// Thrown when a requires option is missing 239 /// Thrown when a requires option is missing
include/CLI/Option.hpp
@@ -28,7 +28,7 @@ class App; @@ -28,7 +28,7 @@ class App;
28 28
29 using Option_p = std::unique_ptr<Option>; 29 using Option_p = std::unique_ptr<Option>;
30 30
31 -enum class MultiOptionPolicy { Throw, TakeLast, TakeFirst, Join }; 31 +enum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join };
32 32
33 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way 33 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
34 /// to share parts of the class; an OptionDefaults can copy to an Option. 34 /// to share parts of the class; an OptionDefaults can copy to an Option.
@@ -50,6 +50,8 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -50,6 +50,8 @@ template &lt;typename CRTP&gt; class OptionBase {
50 50
51 /// Allow this option to be given in a configuration file 51 /// Allow this option to be given in a configuration file
52 bool configurable_{true}; 52 bool configurable_{true};
  53 + /// Disable overriding flag values with '=value'
  54 + bool disable_flag_override_{false};
53 55
54 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) 56 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
55 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; 57 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
@@ -61,6 +63,7 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -61,6 +63,7 @@ template &lt;typename CRTP&gt; class OptionBase {
61 other->ignore_case(ignore_case_); 63 other->ignore_case(ignore_case_);
62 other->ignore_underscore(ignore_underscore_); 64 other->ignore_underscore(ignore_underscore_);
63 other->configurable(configurable_); 65 other->configurable(configurable_);
  66 + other->disable_flag_override(disable_flag_override_);
64 other->multi_option_policy(multi_option_policy_); 67 other->multi_option_policy(multi_option_policy_);
65 } 68 }
66 69
@@ -100,6 +103,9 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -100,6 +103,9 @@ template &lt;typename CRTP&gt; class OptionBase {
100 /// The status of configurable 103 /// The status of configurable
101 bool get_configurable() const { return configurable_; } 104 bool get_configurable() const { return configurable_; }
102 105
  106 + /// The status of configurable
  107 + bool get_disable_flag_override() const { return disable_flag_override_; }
  108 +
103 /// The status of the multi option policy 109 /// The status of the multi option policy
104 MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } 110 MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
105 111
@@ -158,6 +164,12 @@ class OptionDefaults : public OptionBase&lt;OptionDefaults&gt; { @@ -158,6 +164,12 @@ class OptionDefaults : public OptionBase&lt;OptionDefaults&gt; {
158 ignore_underscore_ = value; 164 ignore_underscore_ = value;
159 return this; 165 return this;
160 } 166 }
  167 +
  168 + /// Ignore underscores in the option name
  169 + OptionDefaults *disable_flag_override(bool value = true) {
  170 + disable_flag_override_ = value;
  171 + return this;
  172 + }
161 }; 173 };
162 174
163 class Option : public OptionBase<Option> { 175 class Option : public OptionBase<Option> {
@@ -173,8 +185,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -173,8 +185,11 @@ class Option : public OptionBase&lt;Option&gt; {
173 /// A list of the long names (`--a`) without the leading dashes 185 /// A list of the long names (`--a`) without the leading dashes
174 std::vector<std::string> lnames_; 186 std::vector<std::string> lnames_;
175 187
176 - /// A list of the negation names, should be duplicates of what is in snames or lnames but trigger a false response  
177 - /// on a flag 188 + /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
  189 + /// what is in snames or lnames but will trigger a particular response on a flag
  190 + std::vector<std::pair<std::string, std::string>> default_flag_values_;
  191 +
  192 + /// a list of flag names with specified default values;
178 std::vector<std::string> fnames_; 193 std::vector<std::string> fnames_;
179 194
180 /// A positional name 195 /// A positional name
@@ -251,7 +266,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -251,7 +266,7 @@ class Option : public OptionBase&lt;Option&gt; {
251 bool defaulted, 266 bool defaulted,
252 App *parent) 267 App *parent)
253 : description_(std::move(option_description)), default_(defaulted), parent_(parent), 268 : description_(std::move(option_description)), default_(defaulted), parent_(parent),
254 - callback_(callback ? std::move(callback) : [](results_t) { return true; }) { 269 + callback_(std::move(callback)) {
255 std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name)); 270 std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
256 } 271 }
257 272
@@ -471,6 +486,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -471,6 +486,11 @@ class Option : public OptionBase&lt;Option&gt; {
471 return this; 486 return this;
472 } 487 }
473 488
  489 + /// disable flag overrides
  490 + Option *disable_flag_override(bool value = true) {
  491 + disable_flag_override_ = value;
  492 + return this;
  493 + }
474 ///@} 494 ///@}
475 /// @name Accessors 495 /// @name Accessors
476 ///@{ 496 ///@{
@@ -499,7 +519,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -499,7 +519,7 @@ class Option : public OptionBase&lt;Option&gt; {
499 /// Get the short names 519 /// Get the short names
500 const std::vector<std::string> get_snames() const { return snames_; } 520 const std::vector<std::string> get_snames() const { return snames_; }
501 521
502 - /// get the negative flag names 522 + /// get the flag names with specified default values
503 const std::vector<std::string> get_fnames() const { return fnames_; } 523 const std::vector<std::string> get_fnames() const { return fnames_; }
504 524
505 /// The number of times the option expects to be included 525 /// The number of times the option expects to be included
@@ -570,14 +590,14 @@ class Option : public OptionBase&lt;Option&gt; { @@ -570,14 +590,14 @@ class Option : public OptionBase&lt;Option&gt; {
570 for(const std::string &sname : snames_) { 590 for(const std::string &sname : snames_) {
571 name_list.push_back("-" + sname); 591 name_list.push_back("-" + sname);
572 if(check_fname(sname)) { 592 if(check_fname(sname)) {
573 - name_list.back() += "{false}"; 593 + name_list.back() += "{" + get_flag_value(sname, "") + "}";
574 } 594 }
575 } 595 }
576 596
577 for(const std::string &lname : lnames_) { 597 for(const std::string &lname : lnames_) {
578 name_list.push_back("--" + lname); 598 name_list.push_back("--" + lname);
579 if(check_fname(lname)) { 599 if(check_fname(lname)) {
580 - name_list.back() += "{false}"; 600 + name_list.back() += "{" + get_flag_value(lname, "") + "}";
581 } 601 }
582 } 602 }
583 } else { 603 } else {
@@ -635,7 +655,9 @@ class Option : public OptionBase&lt;Option&gt; { @@ -635,7 +655,9 @@ class Option : public OptionBase&lt;Option&gt; {
635 throw ValidationError(get_name(), err_msg); 655 throw ValidationError(get_name(), err_msg);
636 } 656 }
637 } 657 }
638 - 658 + if(!(callback_)) {
  659 + return;
  660 + }
639 bool local_result; 661 bool local_result;
640 662
641 // Num items expected or length of vector, always at least 1 663 // Num items expected or length of vector, always at least 1
@@ -716,11 +738,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -716,11 +738,11 @@ class Option : public OptionBase&lt;Option&gt; {
716 } 738 }
717 739
718 /// Requires "-" to be removed from string 740 /// Requires "-" to be removed from string
719 - bool check_sname(std::string name) const { return detail::check_is_member(name, snames_, ignore_case_); } 741 + bool check_sname(std::string name) const { return (detail::find_member(name, snames_, ignore_case_) >= 0); }
720 742
721 /// Requires "--" to be removed from string 743 /// Requires "--" to be removed from string
722 bool check_lname(std::string name) const { 744 bool check_lname(std::string name) const {
723 - return detail::check_is_member(name, lnames_, ignore_case_, ignore_underscore_); 745 + return (detail::find_member(name, lnames_, ignore_case_, ignore_underscore_) >= 0);
724 } 746 }
725 747
726 /// Requires "--" to be removed from string 748 /// Requires "--" to be removed from string
@@ -728,7 +750,45 @@ class Option : public OptionBase&lt;Option&gt; { @@ -728,7 +750,45 @@ class Option : public OptionBase&lt;Option&gt; {
728 if(fnames_.empty()) { 750 if(fnames_.empty()) {
729 return false; 751 return false;
730 } 752 }
731 - return detail::check_is_member(name, fnames_, ignore_case_, ignore_underscore_); 753 + return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0);
  754 + }
  755 +
  756 + std::string get_flag_value(std::string name, std::string input_value) const {
  757 + static const std::string trueString{"true"};
  758 + static const std::string falseString{"false"};
  759 + static const std::string emptyString{"{}"};
  760 + // check for disable flag override_
  761 + if(disable_flag_override_) {
  762 + if(!((input_value.empty()) || (input_value == emptyString))) {
  763 + auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
  764 + if(default_ind >= 0) {
  765 + if(default_flag_values_[default_ind].second != input_value) {
  766 + throw(ArgumentMismatch::FlagOverride(name));
  767 + }
  768 + } else {
  769 + if(input_value != trueString) {
  770 + throw(ArgumentMismatch::FlagOverride(name));
  771 + }
  772 + }
  773 + }
  774 + }
  775 + auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
  776 + if((input_value.empty()) || (input_value == emptyString)) {
  777 + return (ind < 0) ? trueString : default_flag_values_[ind].second;
  778 + }
  779 + if(ind < 0) {
  780 + return input_value;
  781 + }
  782 + if(default_flag_values_[ind].second == falseString) {
  783 + try {
  784 + auto val = detail::to_flag_value(input_value);
  785 + return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
  786 + } catch(const std::invalid_argument &) {
  787 + return input_value;
  788 + }
  789 + } else {
  790 + return input_value;
  791 + }
732 } 792 }
733 793
734 /// Puts a result at the end 794 /// Puts a result at the end
@@ -760,6 +820,74 @@ class Option : public OptionBase&lt;Option&gt; { @@ -760,6 +820,74 @@ class Option : public OptionBase&lt;Option&gt; {
760 /// Get a copy of the results 820 /// Get a copy of the results
761 std::vector<std::string> results() const { return results_; } 821 std::vector<std::string> results() const { return results_; }
762 822
  823 + /// get the results as a particular type
  824 + template <typename T,
  825 + enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy>
  826 + void results(T &output) const {
  827 + bool retval;
  828 + if(results_.empty()) {
  829 + retval = detail::lexical_cast(defaultval_, output);
  830 + } else if(results_.size() == 1) {
  831 + retval = detail::lexical_cast(results_[0], output);
  832 + } else {
  833 + switch(multi_option_policy_) {
  834 + case MultiOptionPolicy::TakeFirst:
  835 + retval = detail::lexical_cast(results_.front(), output);
  836 + break;
  837 + case MultiOptionPolicy::TakeLast:
  838 + default:
  839 + retval = detail::lexical_cast(results_.back(), output);
  840 + break;
  841 + case MultiOptionPolicy::Throw:
  842 + throw ConversionError(get_name(), results_);
  843 + case MultiOptionPolicy::Join:
  844 + retval = detail::lexical_cast(detail::join(results_), output);
  845 + break;
  846 + }
  847 + }
  848 + if(!retval) {
  849 + throw ConversionError(get_name(), results_);
  850 + }
  851 + }
  852 + /// get the results as a vector of a particular type
  853 + template <typename T> void results(std::vector<T> &output, char delim = '\0') const {
  854 + output.clear();
  855 + bool retval = true;
  856 +
  857 + for(const auto &elem : results_) {
  858 + if(delim != '\0') {
  859 + for(const auto &var : CLI::detail::split(elem, delim)) {
  860 + if(!var.empty()) {
  861 + output.emplace_back();
  862 + retval &= detail::lexical_cast(var, output.back());
  863 + }
  864 + }
  865 + } else {
  866 + output.emplace_back();
  867 + retval &= detail::lexical_cast(elem, output.back());
  868 + }
  869 + }
  870 +
  871 + if(!retval) {
  872 + throw ConversionError(get_name(), results_);
  873 + }
  874 + }
  875 +
  876 + /// return the results as a particular type
  877 + template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> T as() const {
  878 + T output;
  879 + results(output);
  880 + return output;
  881 + }
  882 +
  883 + /// get the results as a vector of a particular type
  884 + template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
  885 + T as(char delim = '\0') const {
  886 + T output;
  887 + results(output, delim);
  888 + return output;
  889 + }
  890 +
763 /// See if the callback has been run already 891 /// See if the callback has been run already
764 bool get_callback_run() const { return callback_run_; } 892 bool get_callback_run() const { return callback_run_; }
765 893
include/CLI/Split.hpp
@@ -40,7 +40,7 @@ inline bool split_long(const std::string &amp;current, std::string &amp;name, std::strin @@ -40,7 +40,7 @@ inline bool split_long(const std::string &amp;current, std::string &amp;name, std::strin
40 } 40 }
41 41
42 // Returns false if not a windows style option. Otherwise, sets opt name and value and returns true 42 // Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
43 -inline bool split_windows(const std::string &current, std::string &name, std::string &value) { 43 +inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
44 if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { 44 if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
45 auto loc = current.find_first_of(':'); 45 auto loc = current.find_first_of(':');
46 if(loc != std::string::npos) { 46 if(loc != std::string::npos) {
@@ -67,22 +67,29 @@ inline std::vector&lt;std::string&gt; split_names(std::string current) { @@ -67,22 +67,29 @@ inline std::vector&lt;std::string&gt; split_names(std::string current) {
67 return output; 67 return output;
68 } 68 }
69 69
70 -/// extract negation arguments basically everything after a '|' and before the next comma  
71 -inline std::vector<std::string> get_false_flags(const std::string &str) {  
72 - std::vector<std::string> output = split_names(str);  
73 - output.erase(std::remove_if(output.begin(),  
74 - output.end(),  
75 - [](const std::string &name) {  
76 - return ((name.empty()) ||  
77 - ((name.find("{false}") == std::string::npos) && (name[0] != '!')));  
78 - }),  
79 - output.end());  
80 - for(auto &flag : output) {  
81 - auto false_loc = flag.find("{false}");  
82 - if(false_loc != std::string::npos) {  
83 - flag.erase(false_loc, std::string::npos); 70 +/// extract default flag values either {def} or starting with a !
  71 +inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
  72 + std::vector<std::string> flags = split_names(str);
  73 + flags.erase(std::remove_if(flags.begin(),
  74 + flags.end(),
  75 + [](const std::string &name) {
  76 + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
  77 + (name.back() == '}')) ||
  78 + (name[0] == '!'))));
  79 + }),
  80 + flags.end());
  81 + std::vector<std::pair<std::string, std::string>> output;
  82 + output.reserve(flags.size());
  83 + for(auto &flag : flags) {
  84 + auto def_start = flag.find_first_of('{');
  85 + std::string defval = "false";
  86 + if((def_start != std::string::npos) && (flag.back() == '}')) {
  87 + defval = flag.substr(def_start + 1);
  88 + defval.pop_back();
  89 + flag.erase(def_start, std::string::npos);
84 } 90 }
85 flag.erase(0, flag.find_first_not_of("-!")); 91 flag.erase(0, flag.find_first_not_of("-!"));
  92 + output.emplace_back(flag, defval);
86 } 93 }
87 return output; 94 return output;
88 } 95 }
include/CLI/StringTools.hpp
@@ -158,7 +158,7 @@ inline std::ostream &amp;format_help(std::ostream &amp;out, std::string name, std::strin @@ -158,7 +158,7 @@ inline std::ostream &amp;format_help(std::ostream &amp;out, std::string name, std::strin
158 } 158 }
159 159
160 /// Verify the first character of an option 160 /// Verify the first character of an option
161 -template <typename T> bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } 161 +template <typename T> bool valid_first_char(T c) { return std::isalnum(c, std::locale()) || c == '_'; }
162 162
163 /// Verify following characters of an option 163 /// Verify following characters of an option
164 template <typename T> bool valid_later_char(T c) { 164 template <typename T> bool valid_later_char(T c) {
@@ -203,39 +203,50 @@ inline std::string find_and_replace(std::string str, std::string from, std::stri @@ -203,39 +203,50 @@ inline std::string find_and_replace(std::string str, std::string from, std::stri
203 } 203 }
204 204
205 /// check if the flag definitions has possible false flags 205 /// check if the flag definitions has possible false flags
206 -inline bool has_false_flags(const std::string &flags) { return (flags.find_first_of("{!") != std::string::npos); } 206 +inline bool has_default_flag_values(const std::string &flags) {
  207 + return (flags.find_first_of("{!") != std::string::npos);
  208 +}
207 209
208 -inline void remove_false_flag_notation(std::string &flags) {  
209 - flags = detail::find_and_replace(flags, "{false}", std::string{});  
210 - flags = detail::find_and_replace(flags, "{true}", std::string{}); 210 +inline void remove_default_flag_values(std::string &flags) {
  211 + size_t loc = flags.find_first_of('{');
  212 + while(loc != std::string::npos) {
  213 + auto finish = flags.find_first_of("},", loc + 1);
  214 + if((finish != std::string::npos) && (flags[finish] == '}')) {
  215 + flags.erase(flags.begin() + loc, flags.begin() + finish + 1);
  216 + }
  217 + loc = flags.find_first_of('{', loc + 1);
  218 + }
211 flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); 219 flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
212 } 220 }
213 221
214 /// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores 222 /// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
215 -inline bool check_is_member(std::string name,  
216 - const std::vector<std::string> names,  
217 - bool ignore_case = false,  
218 - bool ignore_underscore = false) { 223 +inline std::ptrdiff_t find_member(std::string name,
  224 + const std::vector<std::string> names,
  225 + bool ignore_case = false,
  226 + bool ignore_underscore = false) {
  227 + auto it = std::end(names);
219 if(ignore_case) { 228 if(ignore_case) {
220 if(ignore_underscore) { 229 if(ignore_underscore) {
221 name = detail::to_lower(detail::remove_underscore(name)); 230 name = detail::to_lower(detail::remove_underscore(name));
222 - return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {  
223 - return detail::to_lower(detail::remove_underscore(local_name)) == name;  
224 - }) != std::end(names); 231 + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
  232 + return detail::to_lower(detail::remove_underscore(local_name)) == name;
  233 + });
225 } else { 234 } else {
226 name = detail::to_lower(name); 235 name = detail::to_lower(name);
227 - return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {  
228 - return detail::to_lower(local_name) == name;  
229 - }) != std::end(names); 236 + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
  237 + return detail::to_lower(local_name) == name;
  238 + });
230 } 239 }
231 240
232 } else if(ignore_underscore) { 241 } else if(ignore_underscore) {
233 name = detail::remove_underscore(name); 242 name = detail::remove_underscore(name);
234 - return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {  
235 - return detail::remove_underscore(local_name) == name;  
236 - }) != std::end(names); 243 + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
  244 + return detail::remove_underscore(local_name) == name;
  245 + });
237 } else 246 } else
238 - return std::find(std::begin(names), std::end(names), name) != std::end(names); 247 + it = std::find(std::begin(names), std::end(names), name);
  248 +
  249 + return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
239 } 250 }
240 251
241 /// Find a trigger string and call a modify callable function that takes the current string and starting position of the 252 /// Find a trigger string and call a modify callable function that takes the current string and starting position of the
@@ -248,49 +259,6 @@ template &lt;typename Callable&gt; inline std::string find_and_modify(std::string str, @@ -248,49 +259,6 @@ template &lt;typename Callable&gt; inline std::string find_and_modify(std::string str,
248 return str; 259 return str;
249 } 260 }
250 261
251 -/// generate a vector of values that represent a boolean they will be either "+" or "-"  
252 -inline std::string to_flag_value(std::string val) {  
253 - val = detail::to_lower(val);  
254 - std::string ret;  
255 - if(val.size() == 1) {  
256 - switch(val[0]) {  
257 - case '0':  
258 - case 'f':  
259 - case 'n':  
260 - case '-':  
261 - ret = "-1";  
262 - break;  
263 - case '1':  
264 - case 't':  
265 - case 'y':  
266 - case '+':  
267 - ret = "1";  
268 - break;  
269 - case '2':  
270 - case '3':  
271 - case '4':  
272 - case '5':  
273 - case '6':  
274 - case '7':  
275 - case '8':  
276 - case '9':  
277 - ret = val;  
278 - break;  
279 - default:  
280 - throw std::invalid_argument("unrecognized character");  
281 - }  
282 - return ret;  
283 - }  
284 - if(val == "true" || val == "on" || val == "yes" || val == "enable") {  
285 - ret = "1";  
286 - } else if(val == "false" || val == "off" || val == "no" || val == "disable") {  
287 - ret = "-1";  
288 - } else {  
289 - auto ui = std::stoll(val);  
290 - ret = (ui == 0) ? "-1" : val;  
291 - }  
292 - return ret;  
293 -}  
294 /// Split a string '"one two" "three"' into 'one two', 'three' 262 /// Split a string '"one two" "three"' into 'one two', 'three'
295 /// Quote characters can be ` ' or " 263 /// Quote characters can be ` ' or "
296 inline std::vector<std::string> split_up(std::string str) { 264 inline std::vector<std::string> split_up(std::string str) {
include/CLI/TypeTools.hpp
@@ -158,10 +158,62 @@ constexpr const char *type_name() { @@ -158,10 +158,62 @@ constexpr const char *type_name() {
158 158
159 // Lexical cast 159 // Lexical cast
160 160
161 -/// Signed integers / enums  
162 -template <typename T,  
163 - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value, detail::enabler> =  
164 - detail::dummy> 161 +/// convert a flag into an integer value typically binary flags
  162 +inline int64_t to_flag_value(std::string val) {
  163 + static const std::string trueString("true");
  164 + static const std::string falseString("false");
  165 + if(val == trueString) {
  166 + return 1;
  167 + }
  168 + if(val == falseString) {
  169 + return -1;
  170 + }
  171 + val = detail::to_lower(val);
  172 + int64_t ret;
  173 + if(val.size() == 1) {
  174 + switch(val[0]) {
  175 + case '0':
  176 + case 'f':
  177 + case 'n':
  178 + case '-':
  179 + ret = -1;
  180 + break;
  181 + case '1':
  182 + case 't':
  183 + case 'y':
  184 + case '+':
  185 + ret = 1;
  186 + break;
  187 + case '2':
  188 + case '3':
  189 + case '4':
  190 + case '5':
  191 + case '6':
  192 + case '7':
  193 + case '8':
  194 + case '9':
  195 + ret = val[0] - '0';
  196 + break;
  197 + default:
  198 + throw std::invalid_argument("unrecognized character");
  199 + }
  200 + return ret;
  201 + }
  202 + if(val == trueString || val == "on" || val == "yes" || val == "enable") {
  203 + ret = 1;
  204 + } else if(val == falseString || val == "off" || val == "no" || val == "disable") {
  205 + ret = -1;
  206 + } else {
  207 + ret = std::stoll(val);
  208 + }
  209 + return ret;
  210 +}
  211 +
  212 +/// Signed integers
  213 +template <
  214 + typename T,
  215 + enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::value,
  216 + detail::enabler> = detail::dummy>
165 bool lexical_cast(std::string input, T &output) { 217 bool lexical_cast(std::string input, T &output) {
166 try { 218 try {
167 size_t n = 0; 219 size_t n = 0;
@@ -200,13 +252,7 @@ template &lt;typename T, enable_if_t&lt;is_bool&lt;T&gt;::value, detail::enabler&gt; = detail:: @@ -200,13 +252,7 @@ template &lt;typename T, enable_if_t&lt;is_bool&lt;T&gt;::value, detail::enabler&gt; = detail::
200 bool lexical_cast(std::string input, T &output) { 252 bool lexical_cast(std::string input, T &output) {
201 try { 253 try {
202 auto out = to_flag_value(input); 254 auto out = to_flag_value(input);
203 - if(out == "1") {  
204 - output = true;  
205 - } else if(out == "-1") {  
206 - output = false;  
207 - } else {  
208 - output = (std::stoll(out) > 0);  
209 - } 255 + output = (out > 0);
210 return true; 256 return true;
211 } catch(const std::invalid_argument &) { 257 } catch(const std::invalid_argument &) {
212 return false; 258 return false;
@@ -270,10 +316,8 @@ template &lt;typename T, @@ -270,10 +316,8 @@ template &lt;typename T,
270 enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> 316 enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
271 void sum_flag_vector(const std::vector<std::string> &flags, T &output) { 317 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
272 int64_t count{0}; 318 int64_t count{0};
273 - static const auto trueString = std::string("1");  
274 - static const auto falseString = std::string("-1");  
275 for(auto &flag : flags) { 319 for(auto &flag : flags) {
276 - count += (flag == trueString) ? 1 : ((flag == falseString) ? (-1) : std::stoll(flag)); 320 + count += detail::to_flag_value(flag);
277 } 321 }
278 output = (count > 0) ? static_cast<T>(count) : T{0}; 322 output = (count > 0) ? static_cast<T>(count) : T{0};
279 } 323 }
@@ -286,10 +330,8 @@ template &lt;typename T, @@ -286,10 +330,8 @@ template &lt;typename T,
286 enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> 330 enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
287 void sum_flag_vector(const std::vector<std::string> &flags, T &output) { 331 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
288 int64_t count{0}; 332 int64_t count{0};
289 - static const auto trueString = std::string("1");  
290 - static const auto falseString = std::string("-1");  
291 for(auto &flag : flags) { 333 for(auto &flag : flags) {
292 - count += (flag == trueString) ? 1 : ((flag == falseString) ? (-1) : std::stoll(flag)); 334 + count += detail::to_flag_value(flag);
293 } 335 }
294 output = static_cast<T>(count); 336 output = static_cast<T>(count);
295 } 337 }
tests/AppTest.cpp
@@ -10,6 +10,43 @@ TEST_F(TApp, OneFlagShort) { @@ -10,6 +10,43 @@ TEST_F(TApp, OneFlagShort) {
10 EXPECT_EQ(1u, app.count("--count")); 10 EXPECT_EQ(1u, app.count("--count"));
11 } 11 }
12 12
  13 +TEST_F(TApp, OneFlagShortValues) {
  14 + app.add_flag("-c{v1},--count{v2}");
  15 + args = {"-c"};
  16 + run();
  17 + EXPECT_EQ(1u, app.count("-c"));
  18 + EXPECT_EQ(1u, app.count("--count"));
  19 + auto v = app["-c"]->results();
  20 + EXPECT_EQ(v[0], "v1");
  21 +
  22 + EXPECT_THROW(app["--invalid"], CLI::OptionNotFound);
  23 +}
  24 +
  25 +TEST_F(TApp, OneFlagShortValuesAs) {
  26 + auto flg = app.add_flag("-c{1},--count{2}");
  27 + args = {"-c"};
  28 + run();
  29 + auto opt = app["-c"];
  30 + EXPECT_EQ(opt->as<int>(), 1);
  31 + args = {"--count"};
  32 + run();
  33 + EXPECT_EQ(opt->as<int>(), 2);
  34 + flg->take_first();
  35 + args = {"-c", "--count"};
  36 + run();
  37 + EXPECT_EQ(opt->as<int>(), 1);
  38 + flg->take_last();
  39 + EXPECT_EQ(opt->as<int>(), 2);
  40 + flg->multi_option_policy(CLI::MultiOptionPolicy::Throw);
  41 + EXPECT_THROW(opt->as<int>(), CLI::ConversionError);
  42 +
  43 + auto vec = opt->as<std::vector<int>>();
  44 + EXPECT_EQ(vec[0], 1);
  45 + EXPECT_EQ(vec[1], 2);
  46 + flg->multi_option_policy(CLI::MultiOptionPolicy::Join);
  47 + EXPECT_EQ(opt->as<std::string>(), "1,2");
  48 +}
  49 +
13 TEST_F(TApp, OneFlagShortWindows) { 50 TEST_F(TApp, OneFlagShortWindows) {
14 app.add_flag("-c,--count"); 51 app.add_flag("-c,--count");
15 args = {"/c"}; 52 args = {"/c"};
@@ -81,6 +118,26 @@ TEST_F(TApp, DashedOptionsSingleString) { @@ -81,6 +118,26 @@ TEST_F(TApp, DashedOptionsSingleString) {
81 EXPECT_EQ(2u, app.count("--that")); 118 EXPECT_EQ(2u, app.count("--that"));
82 } 119 }
83 120
  121 +TEST_F(TApp, BoolFlagOverride) {
  122 + bool val;
  123 + auto flg = app.add_flag("--this,--that", val);
  124 +
  125 + app.parse("--this");
  126 + EXPECT_TRUE(val);
  127 + app.parse("--this=false");
  128 + EXPECT_FALSE(val);
  129 + flg->disable_flag_override(true);
  130 + app.parse("--this");
  131 + EXPECT_TRUE(val);
  132 + // this is allowed since the matching string is the default
  133 + app.parse("--this=true");
  134 + EXPECT_TRUE(val);
  135 +
  136 + EXPECT_THROW(app.parse("--this=false"), CLI::ArgumentMismatch);
  137 + // try a string that specifies 'use default val'
  138 + EXPECT_NO_THROW(app.parse("--this={}"));
  139 +}
  140 +
84 TEST_F(TApp, OneFlagRef) { 141 TEST_F(TApp, OneFlagRef) {
85 int ref; 142 int ref;
86 app.add_flag("-c,--count", ref); 143 app.add_flag("-c,--count", ref);
@@ -103,13 +160,14 @@ TEST_F(TApp, OneFlagRefValue) { @@ -103,13 +160,14 @@ TEST_F(TApp, OneFlagRefValue) {
103 160
104 TEST_F(TApp, OneFlagRefValueFalse) { 161 TEST_F(TApp, OneFlagRefValueFalse) {
105 int ref; 162 int ref;
106 - app.add_flag("-c,--count", ref); 163 + auto flg = app.add_flag("-c,--count", ref);
107 args = {"--count=false"}; 164 args = {"--count=false"};
108 run(); 165 run();
109 EXPECT_EQ(1u, app.count("-c")); 166 EXPECT_EQ(1u, app.count("-c"));
110 EXPECT_EQ(1u, app.count("--count")); 167 EXPECT_EQ(1u, app.count("--count"));
111 EXPECT_EQ(-1, ref); 168 EXPECT_EQ(-1, ref);
112 169
  170 + EXPECT_FALSE(flg->check_fname("c"));
113 args = {"--count=0"}; 171 args = {"--count=0"};
114 run(); 172 run();
115 EXPECT_EQ(1u, app.count("-c")); 173 EXPECT_EQ(1u, app.count("-c"));
@@ -122,8 +180,10 @@ TEST_F(TApp, OneFlagRefValueFalse) { @@ -122,8 +180,10 @@ TEST_F(TApp, OneFlagRefValueFalse) {
122 180
123 TEST_F(TApp, FlagNegation) { 181 TEST_F(TApp, FlagNegation) {
124 int ref; 182 int ref;
125 - app.add_flag("-c,--count,--ncount{false}", ref); 183 + auto flg = app.add_flag("-c,--count,--ncount{false}", ref);
126 args = {"--count", "-c", "--ncount"}; 184 args = {"--count", "-c", "--ncount"};
  185 + EXPECT_FALSE(flg->check_fname("count"));
  186 + EXPECT_TRUE(flg->check_fname("ncount"));
127 run(); 187 run();
128 EXPECT_EQ(3u, app.count("-c")); 188 EXPECT_EQ(3u, app.count("-c"));
129 EXPECT_EQ(3u, app.count("--count")); 189 EXPECT_EQ(3u, app.count("--count"));
@@ -133,8 +193,8 @@ TEST_F(TApp, FlagNegation) { @@ -133,8 +193,8 @@ TEST_F(TApp, FlagNegation) {
133 193
134 TEST_F(TApp, FlagNegationShortcutNotation) { 194 TEST_F(TApp, FlagNegationShortcutNotation) {
135 int ref; 195 int ref;
136 - app.add_flag("-c,--count,!--ncount", ref);  
137 - args = {"--count", "-c", "--ncount"}; 196 + app.add_flag("-c,--count{true},!--ncount", ref);
  197 + args = {"--count=TRUE", "-c", "--ncount"};
138 run(); 198 run();
139 EXPECT_EQ(3u, app.count("-c")); 199 EXPECT_EQ(3u, app.count("-c"));
140 EXPECT_EQ(3u, app.count("--count")); 200 EXPECT_EQ(3u, app.count("--count"));
@@ -142,6 +202,13 @@ TEST_F(TApp, FlagNegationShortcutNotation) { @@ -142,6 +202,13 @@ TEST_F(TApp, FlagNegationShortcutNotation) {
142 EXPECT_EQ(1, ref); 202 EXPECT_EQ(1, ref);
143 } 203 }
144 204
  205 +TEST_F(TApp, FlagNegationShortcutNotationInvalid) {
  206 + int ref;
  207 + app.add_flag("-c,--count,!--ncount", ref);
  208 + args = {"--ncount=happy"};
  209 + EXPECT_THROW(run(), CLI::ConversionError);
  210 +}
  211 +
145 TEST_F(TApp, OneString) { 212 TEST_F(TApp, OneString) {
146 std::string str; 213 std::string str;
147 app.add_option("-s,--string", str); 214 app.add_option("-s,--string", str);
@@ -325,6 +392,8 @@ TEST_F(TApp, TogetherInt) { @@ -325,6 +392,8 @@ TEST_F(TApp, TogetherInt) {
325 EXPECT_EQ(1u, app.count("--int")); 392 EXPECT_EQ(1u, app.count("--int"));
326 EXPECT_EQ(1u, app.count("-i")); 393 EXPECT_EQ(1u, app.count("-i"));
327 EXPECT_EQ(i, 4); 394 EXPECT_EQ(i, 4);
  395 + EXPECT_EQ(app["-i"]->as<std::string>(), "4");
  396 + EXPECT_EQ(app["--int"]->as<double>(), 4.0);
328 } 397 }
329 398
330 TEST_F(TApp, SepInt) { 399 TEST_F(TApp, SepInt) {
@@ -369,6 +438,8 @@ TEST_F(TApp, doubleFunction) { @@ -369,6 +438,8 @@ TEST_F(TApp, doubleFunction) {
369 args = {"--val", "-354.356"}; 438 args = {"--val", "-354.356"};
370 run(); 439 run();
371 EXPECT_EQ(res, 300.356); 440 EXPECT_EQ(res, 300.356);
  441 + // get the original value as entered as an integer
  442 + EXPECT_EQ(app["--val"]->as<float>(), -354.356f);
372 } 443 }
373 444
374 TEST_F(TApp, doubleFunctionFail) { 445 TEST_F(TApp, doubleFunctionFail) {
@@ -397,13 +468,18 @@ TEST_F(TApp, doubleVectorFunction) { @@ -397,13 +468,18 @@ TEST_F(TApp, doubleVectorFunction) {
397 468
398 TEST_F(TApp, doubleVectorFunctionFail) { 469 TEST_F(TApp, doubleVectorFunctionFail) {
399 std::vector<double> res; 470 std::vector<double> res;
400 - app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { 471 + std::string vstring = "--val";
  472 + app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
401 res = val; 473 res = val;
402 std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); 474 std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
403 return true; 475 return true;
404 }); 476 });
405 args = {"--val", "five", "--val", "nine", "--val", "7"}; 477 args = {"--val", "five", "--val", "nine", "--val", "7"};
406 EXPECT_THROW(run(), CLI::ConversionError); 478 EXPECT_THROW(run(), CLI::ConversionError);
  479 + // check that getting the results through the results function generates the same error
  480 + EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError);
  481 + auto strvec = app[vstring]->as<std::vector<std::string>>();
  482 + EXPECT_EQ(strvec.size(), 3u);
407 } 483 }
408 484
409 TEST_F(TApp, DefaultStringAgain) { 485 TEST_F(TApp, DefaultStringAgain) {
@@ -453,6 +529,35 @@ TEST_F(TApp, LotsOfFlags) { @@ -453,6 +529,35 @@ TEST_F(TApp, LotsOfFlags) {
453 EXPECT_EQ(1u, app.count("-A")); 529 EXPECT_EQ(1u, app.count("-A"));
454 } 530 }
455 531
  532 +TEST_F(TApp, NumberFlags) {
  533 +
  534 + int val;
  535 + app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val);
  536 +
  537 + args = {"-7"};
  538 + run();
  539 + EXPECT_EQ(1u, app.count("-1"));
  540 + EXPECT_EQ(val, 7);
  541 +}
  542 +
  543 +TEST_F(TApp, DisableFlagOverrideTest) {
  544 +
  545 + int val;
  546 + auto opt = app.add_flag("--1{1},--2{2},--3{3},--4{4},--5{5},--6{6}, --7{7}, --8{8}, --9{9}", val);
  547 + EXPECT_FALSE(opt->get_disable_flag_override());
  548 + opt->disable_flag_override();
  549 + args = {"--7=5"};
  550 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  551 + EXPECT_TRUE(opt->get_disable_flag_override());
  552 + opt->disable_flag_override(false);
  553 + EXPECT_FALSE(opt->get_disable_flag_override());
  554 + EXPECT_NO_THROW(run());
  555 + EXPECT_EQ(val, 5);
  556 + opt->disable_flag_override();
  557 + args = {"--7=7"};
  558 + EXPECT_NO_THROW(run());
  559 +}
  560 +
456 TEST_F(TApp, LotsOfFlagsSingleString) { 561 TEST_F(TApp, LotsOfFlagsSingleString) {
457 562
458 app.add_flag("-a"); 563 app.add_flag("-a");
@@ -1063,10 +1168,31 @@ TEST_F(TApp, CallbackFlags) { @@ -1063,10 +1168,31 @@ TEST_F(TApp, CallbackFlags) {
1063 EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); 1168 EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
1064 } 1169 }
1065 1170
  1171 +TEST_F(TApp, CallbackBoolFlags) {
  1172 +
  1173 + bool value = false;
  1174 +
  1175 + auto func = [&value]() { value = true; };
  1176 +
  1177 + auto cback = app.add_flag_callback("--val", func);
  1178 + args = {"--val"};
  1179 + run();
  1180 + EXPECT_TRUE(value);
  1181 + value = false;
  1182 + args = {"--val=false"};
  1183 + run();
  1184 + EXPECT_FALSE(value);
  1185 +
  1186 + EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
  1187 + cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
  1188 + args = {"--val", "--val=false"};
  1189 + EXPECT_THROW(run(), CLI::ConversionError);
  1190 +}
  1191 +
1066 TEST_F(TApp, CallbackFlagsFalse) { 1192 TEST_F(TApp, CallbackFlagsFalse) {
1067 - int value = 0; 1193 + int64_t value = 0;
1068 1194
1069 - auto func = [&value](int x) { value = x; }; 1195 + auto func = [&value](int64_t x) { value = x; };
1070 1196
1071 app.add_flag_function("-v,-f{false},--val,--fval{false}", func); 1197 app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
1072 1198
@@ -1093,9 +1219,9 @@ TEST_F(TApp, CallbackFlagsFalse) { @@ -1093,9 +1219,9 @@ TEST_F(TApp, CallbackFlagsFalse) {
1093 } 1219 }
1094 1220
1095 TEST_F(TApp, CallbackFlagsFalseShortcut) { 1221 TEST_F(TApp, CallbackFlagsFalseShortcut) {
1096 - int value = 0; 1222 + int64_t value = 0;
1097 1223
1098 - auto func = [&value](int x) { value = x; }; 1224 + auto func = [&value](int64_t x) { value = x; };
1099 1225
1100 app.add_flag_function("-v,!-f,--val,!--fval", func); 1226 app.add_flag_function("-v,!-f,--val,!--fval", func);
1101 1227
@@ -1361,6 +1487,24 @@ TEST_F(TApp, VectorDefaultedFixedString) { @@ -1361,6 +1487,24 @@ TEST_F(TApp, VectorDefaultedFixedString) {
1361 EXPECT_EQ(answer, strvec); 1487 EXPECT_EQ(answer, strvec);
1362 } 1488 }
1363 1489
  1490 +TEST_F(TApp, DefaultedResult) {
  1491 + std::string sval = "NA";
  1492 + int ival;
  1493 + auto opts = app.add_option("--string", sval, "", true);
  1494 + auto optv = app.add_option("--val", ival);
  1495 + args = {};
  1496 + run();
  1497 + EXPECT_EQ(sval, "NA");
  1498 + std::string nString;
  1499 + opts->results(nString);
  1500 + EXPECT_EQ(nString, "NA");
  1501 + int newIval;
  1502 + EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
  1503 + optv->default_str("442");
  1504 + optv->results(newIval);
  1505 + EXPECT_EQ(newIval, 442);
  1506 +}
  1507 +
1364 TEST_F(TApp, VectorUnlimString) { 1508 TEST_F(TApp, VectorUnlimString) {
1365 std::vector<std::string> strvec; 1509 std::vector<std::string> strvec;
1366 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 1510 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@@ -1620,7 +1764,6 @@ TEST_F(TApp, AllowExtras) { @@ -1620,7 +1764,6 @@ TEST_F(TApp, AllowExtras) {
1620 1764
1621 bool val = true; 1765 bool val = true;
1622 app.add_flag("-f", val); 1766 app.add_flag("-f", val);
1623 - EXPECT_FALSE(val);  
1624 1767
1625 args = {"-x", "-f"}; 1768 args = {"-x", "-f"};
1626 1769
@@ -1769,7 +1912,7 @@ TEST_F(TApp, RepeatingMultiArgumentOptions) { @@ -1769,7 +1912,7 @@ TEST_F(TApp, RepeatingMultiArgumentOptions) {
1769 // #122 1912 // #122
1770 TEST_F(TApp, EmptyOptionEach) { 1913 TEST_F(TApp, EmptyOptionEach) {
1771 std::string q; 1914 std::string q;
1772 - app.add_option("--each", {})->each([&q](std::string s) { q = s; }); 1915 + app.add_option("--each")->each([&q](std::string s) { q = s; });
1773 1916
1774 args = {"--each", "that"}; 1917 args = {"--each", "that"};
1775 run(); 1918 run();
@@ -1780,7 +1923,7 @@ TEST_F(TApp, EmptyOptionEach) { @@ -1780,7 +1923,7 @@ TEST_F(TApp, EmptyOptionEach) {
1780 // #122 1923 // #122
1781 TEST_F(TApp, EmptyOptionFail) { 1924 TEST_F(TApp, EmptyOptionFail) {
1782 std::string q; 1925 std::string q;
1783 - app.add_option("--each", {}); 1926 + app.add_option("--each");
1784 1927
1785 args = {"--each", "that"}; 1928 args = {"--each", "that"};
1786 run(); 1929 run();
@@ -1816,6 +1959,10 @@ TEST_F(TApp, CustomUserSepParse) { @@ -1816,6 +1959,10 @@ TEST_F(TApp, CustomUserSepParse) {
1816 auto opt = app.add_option("--idx", vals, "", ','); 1959 auto opt = app.add_option("--idx", vals, "", ',');
1817 run(); 1960 run();
1818 EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); 1961 EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
  1962 + std::vector<int> vals2;
  1963 + // check that the results vector gets the results in the same way
  1964 + opt->results(vals2, ',');
  1965 + EXPECT_EQ(vals2, vals);
1819 1966
1820 app.remove_option(opt); 1967 app.remove_option(opt);
1821 1968
tests/CreationTest.cpp
@@ -424,6 +424,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) { @@ -424,6 +424,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
424 EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw); 424 EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw);
425 EXPECT_FALSE(app.option_defaults()->get_ignore_case()); 425 EXPECT_FALSE(app.option_defaults()->get_ignore_case());
426 EXPECT_FALSE(app.option_defaults()->get_ignore_underscore()); 426 EXPECT_FALSE(app.option_defaults()->get_ignore_underscore());
  427 + EXPECT_FALSE(app.option_defaults()->get_disable_flag_override());
427 EXPECT_TRUE(app.option_defaults()->get_configurable()); 428 EXPECT_TRUE(app.option_defaults()->get_configurable());
428 EXPECT_EQ(app.option_defaults()->get_group(), "Options"); 429 EXPECT_EQ(app.option_defaults()->get_group(), "Options");
429 430
@@ -433,6 +434,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) { @@ -433,6 +434,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
433 ->ignore_case() 434 ->ignore_case()
434 ->ignore_underscore() 435 ->ignore_underscore()
435 ->configurable(false) 436 ->configurable(false)
  437 + ->disable_flag_override()
436 ->group("Something"); 438 ->group("Something");
437 439
438 auto app2 = app.add_subcommand("app2"); 440 auto app2 = app.add_subcommand("app2");
@@ -442,6 +444,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) { @@ -442,6 +444,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
442 EXPECT_TRUE(app2->option_defaults()->get_ignore_case()); 444 EXPECT_TRUE(app2->option_defaults()->get_ignore_case());
443 EXPECT_TRUE(app2->option_defaults()->get_ignore_underscore()); 445 EXPECT_TRUE(app2->option_defaults()->get_ignore_underscore());
444 EXPECT_FALSE(app2->option_defaults()->get_configurable()); 446 EXPECT_FALSE(app2->option_defaults()->get_configurable());
  447 + EXPECT_TRUE(app.option_defaults()->get_disable_flag_override());
445 EXPECT_EQ(app2->option_defaults()->get_group(), "Something"); 448 EXPECT_EQ(app2->option_defaults()->get_group(), "Something");
446 } 449 }
447 450
tests/HelpersTest.cpp
@@ -69,17 +69,17 @@ TEST(StringTools, Modify3) { @@ -69,17 +69,17 @@ TEST(StringTools, Modify3) {
69 } 69 }
70 70
71 TEST(StringTools, flagValues) { 71 TEST(StringTools, flagValues) {
72 - EXPECT_EQ(CLI::detail::to_flag_value("0"), "-1");  
73 - EXPECT_EQ(CLI::detail::to_flag_value("t"), "1");  
74 - EXPECT_EQ(CLI::detail::to_flag_value("1"), "1");  
75 - EXPECT_EQ(CLI::detail::to_flag_value("6"), "6");  
76 - EXPECT_EQ(CLI::detail::to_flag_value("-6"), "-6");  
77 - EXPECT_EQ(CLI::detail::to_flag_value("false"), "-1");  
78 - EXPECT_EQ(CLI::detail::to_flag_value("YES"), "1"); 72 + EXPECT_EQ(CLI::detail::to_flag_value("0"), -1);
  73 + EXPECT_EQ(CLI::detail::to_flag_value("t"), 1);
  74 + EXPECT_EQ(CLI::detail::to_flag_value("1"), 1);
  75 + EXPECT_EQ(CLI::detail::to_flag_value("6"), 6);
  76 + EXPECT_EQ(CLI::detail::to_flag_value("-6"), -6);
  77 + EXPECT_EQ(CLI::detail::to_flag_value("false"), -1);
  78 + EXPECT_EQ(CLI::detail::to_flag_value("YES"), 1);
79 EXPECT_THROW(CLI::detail::to_flag_value("frog"), std::invalid_argument); 79 EXPECT_THROW(CLI::detail::to_flag_value("frog"), std::invalid_argument);
80 EXPECT_THROW(CLI::detail::to_flag_value("q"), std::invalid_argument); 80 EXPECT_THROW(CLI::detail::to_flag_value("q"), std::invalid_argument);
81 - EXPECT_EQ(CLI::detail::to_flag_value("NO"), "-1");  
82 - EXPECT_EQ(CLI::detail::to_flag_value("4755263255233"), "4755263255233"); 81 + EXPECT_EQ(CLI::detail::to_flag_value("NO"), -1);
  82 + EXPECT_EQ(CLI::detail::to_flag_value("475555233"), 475555233);
83 } 83 }
84 84
85 TEST(Trim, Various) { 85 TEST(Trim, Various) {
tests/IniTest.cpp
@@ -516,8 +516,13 @@ TEST_F(TApp, IniFlagConvertFailure) { @@ -516,8 +516,13 @@ TEST_F(TApp, IniFlagConvertFailure) {
516 std::ofstream out{tmpini}; 516 std::ofstream out{tmpini};
517 out << "flag=moobook" << std::endl; 517 out << "flag=moobook" << std::endl;
518 } 518 }
519 -  
520 - EXPECT_THROW(run(), CLI::ConversionError); 519 + run();
  520 + bool result;
  521 + auto *opt = app.get_option("--flag");
  522 + EXPECT_THROW(opt->results(result), CLI::ConversionError);
  523 + std::string res;
  524 + opt->results(res);
  525 + EXPECT_EQ(res, "moobook");
521 } 526 }
522 527
523 TEST_F(TApp, IniFlagNumbers) { 528 TEST_F(TApp, IniFlagNumbers) {
@@ -664,6 +669,51 @@ TEST_F(TApp, IniFalseFlagsDef) { @@ -664,6 +669,51 @@ TEST_F(TApp, IniFalseFlagsDef) {
664 EXPECT_TRUE(five); 669 EXPECT_TRUE(five);
665 } 670 }
666 671
  672 +TEST_F(TApp, IniFalseFlagsDefDisableOverrideError) {
  673 + TempFile tmpini{"TestIniTmp.ini"};
  674 + app.set_config("--config", tmpini);
  675 +
  676 + {
  677 + std::ofstream out{tmpini};
  678 + out << "[default]" << std::endl;
  679 + out << "two=2" << std::endl;
  680 + out << "four=on" << std::endl;
  681 + out << "five" << std::endl;
  682 + }
  683 +
  684 + int two;
  685 + bool four, five;
  686 + app.add_flag("--two{false}", two)->disable_flag_override();
  687 + app.add_flag("!--four", four);
  688 + app.add_flag("--five", five);
  689 +
  690 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  691 +}
  692 +
  693 +TEST_F(TApp, IniFalseFlagsDefDisableOverrideSuccess) {
  694 + TempFile tmpini{"TestIniTmp.ini"};
  695 + app.set_config("--config", tmpini);
  696 +
  697 + {
  698 + std::ofstream out{tmpini};
  699 + out << "[default]" << std::endl;
  700 + out << "two=2" << std::endl;
  701 + out << "four={}" << std::endl;
  702 + out << "val=15" << std::endl;
  703 + }
  704 +
  705 + int two, four, val;
  706 + app.add_flag("--two{2}", two)->disable_flag_override();
  707 + app.add_flag("--four{4}", four)->disable_flag_override();
  708 + app.add_flag("--val", val);
  709 +
  710 + run();
  711 +
  712 + EXPECT_EQ(2, two);
  713 + EXPECT_EQ(4, four);
  714 + EXPECT_EQ(15, val);
  715 +}
  716 +
667 TEST_F(TApp, IniOutputSimple) { 717 TEST_F(TApp, IniOutputSimple) {
668 718
669 int v; 719 int v;
@@ -693,7 +743,7 @@ TEST_F(TApp, IniOutputNoConfigurable) { @@ -693,7 +743,7 @@ TEST_F(TApp, IniOutputNoConfigurable) {
693 743
694 TEST_F(TApp, IniOutputShortSingleDescription) { 744 TEST_F(TApp, IniOutputShortSingleDescription) {
695 std::string flag = "some_flag"; 745 std::string flag = "some_flag";
696 - std::string description = "Some short description."; 746 + const std::string description = "Some short description.";
697 app.add_flag("--" + flag, description); 747 app.add_flag("--" + flag, description);
698 748
699 run(); 749 run();
@@ -705,8 +755,8 @@ TEST_F(TApp, IniOutputShortSingleDescription) { @@ -705,8 +755,8 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
705 TEST_F(TApp, IniOutputShortDoubleDescription) { 755 TEST_F(TApp, IniOutputShortDoubleDescription) {
706 std::string flag1 = "flagnr1"; 756 std::string flag1 = "flagnr1";
707 std::string flag2 = "flagnr2"; 757 std::string flag2 = "flagnr2";
708 - std::string description1 = "First description.";  
709 - std::string description2 = "Second description."; 758 + const std::string description1 = "First description.";
  759 + const std::string description2 = "Second description.";
710 app.add_flag("--" + flag1, description1); 760 app.add_flag("--" + flag1, description1);
711 app.add_flag("--" + flag2, description2); 761 app.add_flag("--" + flag2, description2);
712 762
@@ -718,7 +768,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) { @@ -718,7 +768,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
718 768
719 TEST_F(TApp, IniOutputMultiLineDescription) { 769 TEST_F(TApp, IniOutputMultiLineDescription) {
720 std::string flag = "some_flag"; 770 std::string flag = "some_flag";
721 - std::string description = "Some short description.\nThat has lines."; 771 + const std::string description = "Some short description.\nThat has lines.";
722 app.add_flag("--" + flag, description); 772 app.add_flag("--" + flag, description);
723 773
724 run(); 774 run();
tests/SubcommandTest.cpp
@@ -856,11 +856,21 @@ TEST_F(ManySubcommands, MaxCommands) { @@ -856,11 +856,21 @@ TEST_F(ManySubcommands, MaxCommands) {
856 TEST_F(TApp, UnnamedSub) { 856 TEST_F(TApp, UnnamedSub) {
857 double val; 857 double val;
858 auto sub = app.add_subcommand("", "empty name"); 858 auto sub = app.add_subcommand("", "empty name");
859 - sub->add_option("-v,--value", val); 859 + auto opt = sub->add_option("-v,--value", val);
860 args = {"-v", "4.56"}; 860 args = {"-v", "4.56"};
861 861
862 run(); 862 run();
863 EXPECT_EQ(val, 4.56); 863 EXPECT_EQ(val, 4.56);
  864 + // make sure unnamed sub options can be found from the main app
  865 + auto opt2 = app.get_option("-v");
  866 + EXPECT_EQ(opt, opt2);
  867 +
  868 + EXPECT_THROW(app.get_option("--vvvv"), CLI::OptionNotFound);
  869 + // now test in the constant context
  870 + const auto &appC = app;
  871 + auto opt3 = appC.get_option("-v");
  872 + EXPECT_EQ(opt3->get_name(), "--value");
  873 + EXPECT_THROW(appC.get_option("--vvvv"), CLI::OptionNotFound);
864 } 874 }
865 875
866 TEST_F(TApp, UnnamedSubMix) { 876 TEST_F(TApp, UnnamedSubMix) {