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 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 179 ```cpp
  180 +app.add_option(option_name, help_str="")
  181 +
180 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 184 help_string="",
183 185 default=false)
184 186  
185 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 189 help_string="")
188 190  
189 191 app.add_complex(... // Special case: support for complex numbers
... ... @@ -192,17 +194,19 @@ app.add_flag(option_name,
192 194 help_string="")
193 195  
194 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 198 help_string="")
197 199  
198 200 app.add_flag_function(option_name,
199   - function <void(int count)>,
  201 + function <void(int64_t count)>,
200 202 help_string="")
201 203  
  204 +app.add_flag_callback(option_name,function<void(void)>,help_string="")
  205 +
202 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 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 214  
211 215 ```cpp
212 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 221 help_string="")
215 222  
216 223 app.add_flag_function(option_name,
217   - function <void(int count)>,
  224 + function <void(int64_t count)>,
218 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 232 ```cpp
224 233 app.add_flag("--flag,!--no-flag,result,"help for flag");`
225 234 ``````
226 235  
227 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 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 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 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 273 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
260 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 276 - `->description(str)`: Set/change the description.
262 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 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 311 - `-ffilename` (no space required)
297 312 - `-abcf filename` (flags and option can be combined)
298 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 315 - `--file filename` (space)
301 316 - `--file=filename` (equals)
302 317  
... ... @@ -306,9 +321,10 @@ If `allow_windows_style_options()` is specified in the application or subcommand
306 321 - `/long` (long flag)
307 322 - `/file filename` (space)
308 323 - `/file:filename` (colon)
  324 +- `/long_flag:false (long flag with : to override the default value)
309 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 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 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 333 If `--` is present in the command line that does not end an unlimited option, then
318 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 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 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 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 359 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
336 360  
337 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 364 #### Subcommand options
341 365  
... ... @@ -353,7 +377,8 @@ There are several options that are supported on the main app and subcommands. Th
353 377 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
354 378 - `.get_subcommands(filter)`: The list of subcommands given on the command line.
355 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 382 - `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
358 383 - `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates).
359 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 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 396 - `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
372 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 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 434  
409 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 439 ```cpp
414 440 app.option_defaults()->required();
... ...
examples/CMakeLists.txt
... ... @@ -132,6 +132,11 @@ add_test(NAME enum_fail COMMAND enum -l 4)
132 132 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
133 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 140 add_cli_exe(modhelp modhelp.cpp)
136 141 add_test(NAME modhelp COMMAND modhelp -a test -h)
137 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 402 opt->type_name(detail::type_name<T>());
403 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 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 421 Option *add_option(std::string option_name,
409 422 T &variable, ///< The variable to set
410 423 std::string option_description,
... ... @@ -523,7 +536,8 @@ class App {
523 536 }
524 537  
525 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 541 if(help_ptr_ != nullptr) {
528 542 remove_option(help_ptr_);
529 543 help_ptr_ = nullptr;
... ... @@ -531,7 +545,7 @@ class App {
531 545  
532 546 // Empty name will simply remove the help flag
533 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 549 help_ptr_->configurable(false);
536 550 }
537 551  
... ... @@ -539,7 +553,8 @@ class App {
539 553 }
540 554  
541 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 558 if(help_all_ptr_ != nullptr) {
544 559 remove_option(help_all_ptr_);
545 560 help_all_ptr_ = nullptr;
... ... @@ -547,109 +562,149 @@ class App {
547 562  
548 563 // Empty name will simply remove the help all flag
549 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 566 help_all_ptr_->configurable(false);
552 567 }
553 568  
554 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 594 opt->type_size(0);
564 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 614 template <typename T,
569 615 enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy>
570 616 Option *add_flag(std::string flag_name,
571 617 T &flag_count, ///< A variable holding the count
572 618 std::string flag_description = "") {
573 619 flag_count = 0;
574   - Option *opt;
575 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 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 638 Option *add_flag(std::string flag_name,
598 639 T &flag_result, ///< A variable holding true if passed
599 640 std::string flag_description = "") {
600   - flag_result = false;
601   - Option *opt;
  641 +
602 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 686 opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
619 687 return opt;
620 688 }
621 689  
622   - /// Add option for callback
  690 + /// Add option for callback with an integer value
623 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 693 std::string flag_description = "") {
626 694  
627 695 CLI::callback_t fun = [function](CLI::results_t res) {
628   - int flag_count = 0;
  696 + int64_t flag_count = 0;
629 697 detail::sum_flag_vector(res, flag_count);
630 698 function(flag_count);
631 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 704 #ifdef CLI11_CPP14
650 705 /// Add option for callback (C++14 or better only)
651 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 708 std::string flag_description = "") {
654 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 1242 ///@{
1188 1243  
1189 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 1247 /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command
1200 1248 /// line order; use parsed = false to get the original definition list.)
... ... @@ -1312,26 +1360,68 @@ class App {
1312 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 1366 if(opt->check_name(option_name)) {
1319 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 1385 if(opt->check_name(option_name)) {
1329 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 1425 /// Check the status of ignore_case
1336 1426 bool get_ignore_case() const { return ignore_case_; }
1337 1427  
... ... @@ -1351,7 +1441,7 @@ class App {
1351 1441 const std::string &get_group() const { return group_; }
1352 1442  
1353 1443 /// Get footer.
1354   - std::string get_footer() const { return footer_; }
  1444 + const std::string &get_footer() const { return footer_; }
1355 1445  
1356 1446 /// Get the required min subcommand value
1357 1447 size_t get_require_subcommand_min() const { return require_subcommand_min_; }
... ... @@ -1526,7 +1616,7 @@ class App {
1526 1616 return detail::Classifier::LONG;
1527 1617 if(detail::split_short(current, dummy1, dummy2))
1528 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 1620 return detail::Classifier::WINDOWS;
1531 1621 return detail::Classifier::NONE;
1532 1622 }
... ... @@ -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 1830 // If the option was not present
1743 1831 if(get_allow_config_extras())
1744 1832 // Should we worry about classifying the extras properly?
... ... @@ -1753,9 +1841,8 @@ class App {
1753 1841 // Flag parsing
1754 1842 if(op->get_type_size() == 0) {
1755 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 1846 op->add_result(res);
1760 1847  
1761 1848 } else {
... ... @@ -1894,7 +1981,7 @@ class App {
1894 1981 throw HorribleError("Short parsed but missing! You should not see this");
1895 1982 break;
1896 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 1985 throw HorribleError("windows option parsed but missing! You should not see this");
1899 1986 break;
1900 1987 default:
... ... @@ -1946,16 +2033,9 @@ class App {
1946 2033 int collected = 0;
1947 2034 // deal with flag like things
1948 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 2040 // --this=value
1961 2041 else if(!value.empty()) {
... ...
include/CLI/ConfigFwd.hpp
... ... @@ -69,14 +69,10 @@ class Config {
69 69 /// Convert a configuration into an app
70 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 73 virtual std::string to_flag(const ConfigItem &item) const {
74 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 77 throw ConversionError::TooManyInputsFlag(item.fullname());
82 78 }
... ... @@ -90,7 +86,7 @@ class Config {
90 86 return from_config(input);
91 87 }
92 88  
93   - /// virtual destructor
  89 + /// Virtual destructor
94 90 virtual ~Config() = default;
95 91 };
96 92  
... ...
include/CLI/Error.hpp
... ... @@ -231,6 +231,9 @@ class ArgumentMismatch : public ParseError {
231 231 static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
232 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 239 /// Thrown when a requires option is missing
... ...
include/CLI/Option.hpp
... ... @@ -28,7 +28,7 @@ class App;
28 28  
29 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 33 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
34 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 50  
51 51 /// Allow this option to be given in a configuration file
52 52 bool configurable_{true};
  53 + /// Disable overriding flag values with '=value'
  54 + bool disable_flag_override_{false};
53 55  
54 56 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
55 57 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
... ... @@ -61,6 +63,7 @@ template &lt;typename CRTP&gt; class OptionBase {
61 63 other->ignore_case(ignore_case_);
62 64 other->ignore_underscore(ignore_underscore_);
63 65 other->configurable(configurable_);
  66 + other->disable_flag_override(disable_flag_override_);
64 67 other->multi_option_policy(multi_option_policy_);
65 68 }
66 69  
... ... @@ -100,6 +103,9 @@ template &lt;typename CRTP&gt; class OptionBase {
100 103 /// The status of configurable
101 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 109 /// The status of the multi option policy
104 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 164 ignore_underscore_ = value;
159 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 175 class Option : public OptionBase<Option> {
... ... @@ -173,8 +185,11 @@ class Option : public OptionBase&lt;Option&gt; {
173 185 /// A list of the long names (`--a`) without the leading dashes
174 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 193 std::vector<std::string> fnames_;
179 194  
180 195 /// A positional name
... ... @@ -251,7 +266,7 @@ class Option : public OptionBase&lt;Option&gt; {
251 266 bool defaulted,
252 267 App *parent)
253 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 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 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 495 /// @name Accessors
476 496 ///@{
... ... @@ -499,7 +519,7 @@ class Option : public OptionBase&lt;Option&gt; {
499 519 /// Get the short names
500 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 523 const std::vector<std::string> get_fnames() const { return fnames_; }
504 524  
505 525 /// The number of times the option expects to be included
... ... @@ -570,14 +590,14 @@ class Option : public OptionBase&lt;Option&gt; {
570 590 for(const std::string &sname : snames_) {
571 591 name_list.push_back("-" + sname);
572 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 597 for(const std::string &lname : lnames_) {
578 598 name_list.push_back("--" + lname);
579 599 if(check_fname(lname)) {
580   - name_list.back() += "{false}";
  600 + name_list.back() += "{" + get_flag_value(lname, "") + "}";
581 601 }
582 602 }
583 603 } else {
... ... @@ -635,7 +655,9 @@ class Option : public OptionBase&lt;Option&gt; {
635 655 throw ValidationError(get_name(), err_msg);
636 656 }
637 657 }
638   -
  658 + if(!(callback_)) {
  659 + return;
  660 + }
639 661 bool local_result;
640 662  
641 663 // Num items expected or length of vector, always at least 1
... ... @@ -716,11 +738,11 @@ class Option : public OptionBase&lt;Option&gt; {
716 738 }
717 739  
718 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 743 /// Requires "--" to be removed from string
722 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 748 /// Requires "--" to be removed from string
... ... @@ -728,7 +750,45 @@ class Option : public OptionBase&lt;Option&gt; {
728 750 if(fnames_.empty()) {
729 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 794 /// Puts a result at the end
... ... @@ -760,6 +820,74 @@ class Option : public OptionBase&lt;Option&gt; {
760 820 /// Get a copy of the results
761 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 891 /// See if the callback has been run already
764 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 40 }
41 41  
42 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 44 if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
45 45 auto loc = current.find_first_of(':');
46 46 if(loc != std::string::npos) {
... ... @@ -67,22 +67,29 @@ inline std::vector&lt;std::string&gt; split_names(std::string current) {
67 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 91 flag.erase(0, flag.find_first_not_of("-!"));
  92 + output.emplace_back(flag, defval);
86 93 }
87 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 158 }
159 159  
160 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 163 /// Verify following characters of an option
164 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 203 }
204 204  
205 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 219 flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
212 220 }
213 221  
214 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 228 if(ignore_case) {
220 229 if(ignore_underscore) {
221 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 234 } else {
226 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 241 } else if(ignore_underscore) {
233 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 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 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 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 262 /// Split a string '"one two" "three"' into 'one two', 'three'
295 263 /// Quote characters can be ` ' or "
296 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 158  
159 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 217 bool lexical_cast(std::string input, T &output) {
166 218 try {
167 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 252 bool lexical_cast(std::string input, T &output) {
201 253 try {
202 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 256 return true;
211 257 } catch(const std::invalid_argument &) {
212 258 return false;
... ... @@ -270,10 +316,8 @@ template &lt;typename T,
270 316 enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
271 317 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
272 318 int64_t count{0};
273   - static const auto trueString = std::string("1");
274   - static const auto falseString = std::string("-1");
275 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 322 output = (count > 0) ? static_cast<T>(count) : T{0};
279 323 }
... ... @@ -286,10 +330,8 @@ template &lt;typename T,
286 330 enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
287 331 void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
288 332 int64_t count{0};
289   - static const auto trueString = std::string("1");
290   - static const auto falseString = std::string("-1");
291 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 336 output = static_cast<T>(count);
295 337 }
... ...
tests/AppTest.cpp
... ... @@ -10,6 +10,43 @@ TEST_F(TApp, OneFlagShort) {
10 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 50 TEST_F(TApp, OneFlagShortWindows) {
14 51 app.add_flag("-c,--count");
15 52 args = {"/c"};
... ... @@ -81,6 +118,26 @@ TEST_F(TApp, DashedOptionsSingleString) {
81 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 141 TEST_F(TApp, OneFlagRef) {
85 142 int ref;
86 143 app.add_flag("-c,--count", ref);
... ... @@ -103,13 +160,14 @@ TEST_F(TApp, OneFlagRefValue) {
103 160  
104 161 TEST_F(TApp, OneFlagRefValueFalse) {
105 162 int ref;
106   - app.add_flag("-c,--count", ref);
  163 + auto flg = app.add_flag("-c,--count", ref);
107 164 args = {"--count=false"};
108 165 run();
109 166 EXPECT_EQ(1u, app.count("-c"));
110 167 EXPECT_EQ(1u, app.count("--count"));
111 168 EXPECT_EQ(-1, ref);
112 169  
  170 + EXPECT_FALSE(flg->check_fname("c"));
113 171 args = {"--count=0"};
114 172 run();
115 173 EXPECT_EQ(1u, app.count("-c"));
... ... @@ -122,8 +180,10 @@ TEST_F(TApp, OneFlagRefValueFalse) {
122 180  
123 181 TEST_F(TApp, FlagNegation) {
124 182 int ref;
125   - app.add_flag("-c,--count,--ncount{false}", ref);
  183 + auto flg = app.add_flag("-c,--count,--ncount{false}", ref);
126 184 args = {"--count", "-c", "--ncount"};
  185 + EXPECT_FALSE(flg->check_fname("count"));
  186 + EXPECT_TRUE(flg->check_fname("ncount"));
127 187 run();
128 188 EXPECT_EQ(3u, app.count("-c"));
129 189 EXPECT_EQ(3u, app.count("--count"));
... ... @@ -133,8 +193,8 @@ TEST_F(TApp, FlagNegation) {
133 193  
134 194 TEST_F(TApp, FlagNegationShortcutNotation) {
135 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 198 run();
139 199 EXPECT_EQ(3u, app.count("-c"));
140 200 EXPECT_EQ(3u, app.count("--count"));
... ... @@ -142,6 +202,13 @@ TEST_F(TApp, FlagNegationShortcutNotation) {
142 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 212 TEST_F(TApp, OneString) {
146 213 std::string str;
147 214 app.add_option("-s,--string", str);
... ... @@ -325,6 +392,8 @@ TEST_F(TApp, TogetherInt) {
325 392 EXPECT_EQ(1u, app.count("--int"));
326 393 EXPECT_EQ(1u, app.count("-i"));
327 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 399 TEST_F(TApp, SepInt) {
... ... @@ -369,6 +438,8 @@ TEST_F(TApp, doubleFunction) {
369 438 args = {"--val", "-354.356"};
370 439 run();
371 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 445 TEST_F(TApp, doubleFunctionFail) {
... ... @@ -397,13 +468,18 @@ TEST_F(TApp, doubleVectorFunction) {
397 468  
398 469 TEST_F(TApp, doubleVectorFunctionFail) {
399 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 473 res = val;
402 474 std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
403 475 return true;
404 476 });
405 477 args = {"--val", "five", "--val", "nine", "--val", "7"};
406 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 485 TEST_F(TApp, DefaultStringAgain) {
... ... @@ -453,6 +529,35 @@ TEST_F(TApp, LotsOfFlags) {
453 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 561 TEST_F(TApp, LotsOfFlagsSingleString) {
457 562  
458 563 app.add_flag("-a");
... ... @@ -1063,10 +1168,31 @@ TEST_F(TApp, CallbackFlags) {
1063 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 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 1197 app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
1072 1198  
... ... @@ -1093,9 +1219,9 @@ TEST_F(TApp, CallbackFlagsFalse) {
1093 1219 }
1094 1220  
1095 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 1226 app.add_flag_function("-v,!-f,--val,!--fval", func);
1101 1227  
... ... @@ -1361,6 +1487,24 @@ TEST_F(TApp, VectorDefaultedFixedString) {
1361 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 1508 TEST_F(TApp, VectorUnlimString) {
1365 1509 std::vector<std::string> strvec;
1366 1510 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
... ... @@ -1620,7 +1764,6 @@ TEST_F(TApp, AllowExtras) {
1620 1764  
1621 1765 bool val = true;
1622 1766 app.add_flag("-f", val);
1623   - EXPECT_FALSE(val);
1624 1767  
1625 1768 args = {"-x", "-f"};
1626 1769  
... ... @@ -1769,7 +1912,7 @@ TEST_F(TApp, RepeatingMultiArgumentOptions) {
1769 1912 // #122
1770 1913 TEST_F(TApp, EmptyOptionEach) {
1771 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 1917 args = {"--each", "that"};
1775 1918 run();
... ... @@ -1780,7 +1923,7 @@ TEST_F(TApp, EmptyOptionEach) {
1780 1923 // #122
1781 1924 TEST_F(TApp, EmptyOptionFail) {
1782 1925 std::string q;
1783   - app.add_option("--each", {});
  1926 + app.add_option("--each");
1784 1927  
1785 1928 args = {"--each", "that"};
1786 1929 run();
... ... @@ -1816,6 +1959,10 @@ TEST_F(TApp, CustomUserSepParse) {
1816 1959 auto opt = app.add_option("--idx", vals, "", ',');
1817 1960 run();
1818 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 1967 app.remove_option(opt);
1821 1968  
... ...
tests/CreationTest.cpp
... ... @@ -424,6 +424,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
424 424 EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw);
425 425 EXPECT_FALSE(app.option_defaults()->get_ignore_case());
426 426 EXPECT_FALSE(app.option_defaults()->get_ignore_underscore());
  427 + EXPECT_FALSE(app.option_defaults()->get_disable_flag_override());
427 428 EXPECT_TRUE(app.option_defaults()->get_configurable());
428 429 EXPECT_EQ(app.option_defaults()->get_group(), "Options");
429 430  
... ... @@ -433,6 +434,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
433 434 ->ignore_case()
434 435 ->ignore_underscore()
435 436 ->configurable(false)
  437 + ->disable_flag_override()
436 438 ->group("Something");
437 439  
438 440 auto app2 = app.add_subcommand("app2");
... ... @@ -442,6 +444,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
442 444 EXPECT_TRUE(app2->option_defaults()->get_ignore_case());
443 445 EXPECT_TRUE(app2->option_defaults()->get_ignore_underscore());
444 446 EXPECT_FALSE(app2->option_defaults()->get_configurable());
  447 + EXPECT_TRUE(app.option_defaults()->get_disable_flag_override());
445 448 EXPECT_EQ(app2->option_defaults()->get_group(), "Something");
446 449 }
447 450  
... ...
tests/HelpersTest.cpp
... ... @@ -69,17 +69,17 @@ TEST(StringTools, Modify3) {
69 69 }
70 70  
71 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 79 EXPECT_THROW(CLI::detail::to_flag_value("frog"), std::invalid_argument);
80 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 85 TEST(Trim, Various) {
... ...
tests/IniTest.cpp
... ... @@ -516,8 +516,13 @@ TEST_F(TApp, IniFlagConvertFailure) {
516 516 std::ofstream out{tmpini};
517 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 528 TEST_F(TApp, IniFlagNumbers) {
... ... @@ -664,6 +669,51 @@ TEST_F(TApp, IniFalseFlagsDef) {
664 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 717 TEST_F(TApp, IniOutputSimple) {
668 718  
669 719 int v;
... ... @@ -693,7 +743,7 @@ TEST_F(TApp, IniOutputNoConfigurable) {
693 743  
694 744 TEST_F(TApp, IniOutputShortSingleDescription) {
695 745 std::string flag = "some_flag";
696   - std::string description = "Some short description.";
  746 + const std::string description = "Some short description.";
697 747 app.add_flag("--" + flag, description);
698 748  
699 749 run();
... ... @@ -705,8 +755,8 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
705 755 TEST_F(TApp, IniOutputShortDoubleDescription) {
706 756 std::string flag1 = "flagnr1";
707 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 760 app.add_flag("--" + flag1, description1);
711 761 app.add_flag("--" + flag2, description2);
712 762  
... ... @@ -718,7 +768,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
718 768  
719 769 TEST_F(TApp, IniOutputMultiLineDescription) {
720 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 772 app.add_flag("--" + flag, description);
723 773  
724 774 run();
... ...
tests/SubcommandTest.cpp
... ... @@ -856,11 +856,21 @@ TEST_F(ManySubcommands, MaxCommands) {
856 856 TEST_F(TApp, UnnamedSub) {
857 857 double val;
858 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 860 args = {"-v", "4.56"};
861 861  
862 862 run();
863 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 876 TEST_F(TApp, UnnamedSubMix) {
... ...