Commit c8bd97156bb2c144707119f3e290b925917196f9

Authored by Philip Top
Committed by Henry Schreiner
1 parent 59ae97d0

click-style boolean flags (#219)

Updates to the readme

update the readme with some documentation

add a few more tests to complete code coverage

update with count strings in flags instead an array of strings for each count

add the '!' shortcut notation.  add some checks on the help output

allow the false flag syntax to support --option{false}

add a bool lexical cast to make everything consistent when converting to a bool.  Moved a few functions around

make the command line behave like the INI file wrt flags, flag options are allowed to process the value so `--flag=false` actually does the expected thing.

Add functionality similar to click style argument that allow specifying a false flag that when used generates a false result on the flag.
README.md
@@ -186,11 +186,14 @@ app.add_option_function<type>(option_name, @@ -186,11 +186,14 @@ app.add_option_function<type>(option_name,
186 app.add_complex(... // Special case: support for complex numbers 186 app.add_complex(... // Special case: support for complex numbers
187 187
188 app.add_flag(option_name, 188 app.add_flag(option_name,
189 - int_or_bool = nothing, 189 + help_string="")
  190 +
  191 +app.add_flag(option_name,
  192 + int_or_bool,
190 help_string="") 193 help_string="")
191 194
192 app.add_flag_function(option_name, 195 app.add_flag_function(option_name,
193 - function <void(size_t count)>, 196 + function <void(int count)>,
194 help_string="") 197 help_string="")
195 198
196 app.add_set(option_name, 199 app.add_set(option_name,
@@ -212,7 +215,33 @@ App* subcom = app.add_subcommand(name, description); @@ -212,7 +215,33 @@ App* subcom = app.add_subcommand(name, description);
212 215
213 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` or `add_set`. The set options allow your users to pick from a set of predefined options, and you can use an initializer list directly if you like. If you need to modify the set later, use the `mutable` forms. 216 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` or `add_set`. The set options allow your users to pick from a set of predefined options, and you can use an initializer list directly if you like. If you need to modify the set later, use the `mutable` forms.
214 217
215 -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 218 +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.
  219 +
  220 +Flag options specified through the functions
  221 +
  222 +```cpp
  223 +app.add_flag(option_name,
  224 + int_or_bool,
  225 + help_string="")
  226 +
  227 +app.add_flag_function(option_name,
  228 + function <void(int count)>,
  229 + help_string="")
  230 +```
  231 +
  232 +allow a syntax for the option names to default particular options to a false value if some flags are passed. For example:
  233 +
  234 +```cpp
  235 +app.add_flag("--flag,!--no-flag,result,"help for flag");`
  236 +``````
  237 +
  238 +specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is
  239 +passed result will contain false or -1 if result is a signed integer type, or 0 if it is an unsigned type. An
  240 +alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous
  241 +example. This also works for short form options `"-f,!-n"` or `"-f,-n{false}"` If `int_or_bool` is a boolean value the
  242 +default behavior is to take the last value given, while if `int_or_bool` is an integer type the behavior will be to sum
  243 +all the given arguments and return the result. This can be modifed if needed by changing the `multi_option_policy` on
  244 +each flag (this is not inherited).
216 245
217 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. 246 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.
218 247
@@ -241,7 +270,7 @@ Before parsing, you can set the following options: @@ -241,7 +270,7 @@ Before parsing, you can set the following options:
241 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). 270 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
242 - `->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 271 - `->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
243 - `->description(str)`: Set/change the description. 272 - `->description(str)`: Set/change the description.
244 -- `->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 always default to take last). 273 +- `->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).
245 - `->check(CLI::ExistingFile)`: Requires that the file exists if given. 274 - `->check(CLI::ExistingFile)`: Requires that the file exists if given.
246 - `->check(CLI::ExistingDirectory)`: Requires that the directory exists. 275 - `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
247 - `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists. 276 - `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists.
@@ -261,6 +290,7 @@ On the command line, options can be given as: @@ -261,6 +290,7 @@ On the command line, options can be given as:
261 - `-ffilename` (no space required) 290 - `-ffilename` (no space required)
262 - `-abcf filename` (flags and option can be combined) 291 - `-abcf filename` (flags and option can be combined)
263 - `--long` (long flag) 292 - `--long` (long flag)
  293 +- `--long_flag=true` (long flag with equals)
264 - `--file filename` (space) 294 - `--file filename` (space)
265 - `--file=filename` (equals) 295 - `--file=filename` (equals)
266 296
@@ -272,6 +302,8 @@ If allow_windows_style_options() is specified in the application or subcommand o @@ -272,6 +302,8 @@ If allow_windows_style_options() is specified in the application or subcommand o
272 - `/file:filename` (colon) 302 - `/file:filename` (colon)
273 = Windows style options do not allow combining short options or values not separated from the short option like with `-` options 303 = Windows style options do not allow combining short options or values not separated from the short option like with `-` options
274 304
  305 +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.
  306 +
275 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. 307 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.
276 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). 308 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).
277 309
@@ -360,7 +392,7 @@ in_subcommand = Wow @@ -360,7 +392,7 @@ in_subcommand = Wow
360 sub.subcommand = true 392 sub.subcommand = true
361 ``` 393 ```
362 394
363 -Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". You cannot set positional-only arguments or force subcommands to be present in the command line. 395 +Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`,`enable`; or `false`, `off`, `0`, `no`,`disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". You cannot set positional-only arguments or force subcommands to be present in the command line.
364 396
365 To print a configuration file from the passed 397 To print a configuration file from the passed
366 arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. 398 arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions.
include/CLI/App.hpp
@@ -538,7 +538,6 @@ class App { @@ -538,7 +538,6 @@ class App {
538 /// Add option for flag 538 /// Add option for flag
539 Option *add_flag(std::string flag_name, std::string description = "") { 539 Option *add_flag(std::string flag_name, std::string description = "") {
540 CLI::callback_t fun = [](CLI::results_t) { return true; }; 540 CLI::callback_t fun = [](CLI::results_t) { return true; };
541 -  
542 Option *opt = add_option(flag_name, fun, description, false); 541 Option *opt = add_option(flag_name, fun, description, false);
543 if(opt->get_positional()) 542 if(opt->get_positional())
544 throw IncorrectConstruction::PositionalFlag(flag_name); 543 throw IncorrectConstruction::PositionalFlag(flag_name);
@@ -552,14 +551,21 @@ class App { @@ -552,14 +551,21 @@ class App {
552 Option *add_flag(std::string flag_name, 551 Option *add_flag(std::string flag_name,
553 T &flag_count, ///< A variable holding the count 552 T &flag_count, ///< A variable holding the count
554 std::string description = "") { 553 std::string description = "") {
555 -  
556 flag_count = 0; 554 flag_count = 0;
  555 + Option *opt;
557 CLI::callback_t fun = [&flag_count](CLI::results_t res) { 556 CLI::callback_t fun = [&flag_count](CLI::results_t res) {
558 - flag_count = static_cast<T>(res.size()); 557 + detail::sum_flag_vector(res, flag_count);
559 return true; 558 return true;
560 }; 559 };
  560 + if(detail::has_false_flags(flag_name)) {
  561 + std::vector<std::string> neg = detail::get_false_flags(flag_name);
  562 + detail::remove_false_flag_notation(flag_name);
  563 + opt = add_option(flag_name, fun, description, false);
  564 + opt->fnames_ = std::move(neg);
  565 + } else {
  566 + opt = add_option(flag_name, fun, description, false);
  567 + }
561 568
562 - Option *opt = add_option(flag_name, fun, description, false);  
563 if(opt->get_positional()) 569 if(opt->get_positional())
564 throw IncorrectConstruction::PositionalFlag(flag_name); 570 throw IncorrectConstruction::PositionalFlag(flag_name);
565 opt->type_size(0); 571 opt->type_size(0);
@@ -570,16 +576,23 @@ class App { @@ -570,16 +576,23 @@ class App {
570 /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. 576 /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
571 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> 577 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
572 Option *add_flag(std::string flag_name, 578 Option *add_flag(std::string flag_name,
573 - T &flag_count, ///< A variable holding true if passed 579 + T &flag_result, ///< A variable holding true if passed
574 std::string description = "") { 580 std::string description = "") {
575 -  
576 - flag_count = false;  
577 - CLI::callback_t fun = [&flag_count](CLI::results_t res) {  
578 - flag_count = true; 581 + flag_result = false;
  582 + Option *opt;
  583 + CLI::callback_t fun = [&flag_result](CLI::results_t res) {
  584 + flag_result = (res[0][0] != '-');
579 return res.size() == 1; 585 return res.size() == 1;
580 }; 586 };
  587 + if(detail::has_false_flags(flag_name)) {
  588 + std::vector<std::string> neg = detail::get_false_flags(flag_name);
  589 + detail::remove_false_flag_notation(flag_name);
  590 + opt = add_option(flag_name, fun, std::move(description), false);
  591 + opt->fnames_ = std::move(neg);
  592 + } else {
  593 + opt = add_option(flag_name, fun, std::move(description), false);
  594 + }
581 595
582 - Option *opt = add_option(flag_name, fun, description, false);  
583 if(opt->get_positional()) 596 if(opt->get_positional())
584 throw IncorrectConstruction::PositionalFlag(flag_name); 597 throw IncorrectConstruction::PositionalFlag(flag_name);
585 opt->type_size(0); 598 opt->type_size(0);
@@ -589,15 +602,25 @@ class App { @@ -589,15 +602,25 @@ class App {
589 602
590 /// Add option for callback 603 /// Add option for callback
591 Option *add_flag_function(std::string flag_name, 604 Option *add_flag_function(std::string flag_name,
592 - std::function<void(size_t)> function, ///< A function to call, void(size_t) 605 + std::function<void(int)> function, ///< A function to call, void(size_t)
593 std::string description = "") { 606 std::string description = "") {
594 607
595 CLI::callback_t fun = [function](CLI::results_t res) { 608 CLI::callback_t fun = [function](CLI::results_t res) {
596 - function(res.size()); 609 + int flag_count = 0;
  610 + detail::sum_flag_vector(res, flag_count);
  611 + function(flag_count);
597 return true; 612 return true;
598 }; 613 };
  614 + Option *opt;
  615 + if(detail::has_false_flags(flag_name)) {
  616 + std::vector<std::string> neg = detail::get_false_flags(flag_name);
  617 + detail::remove_false_flag_notation(flag_name);
  618 + opt = add_option(flag_name, fun, std::move(description), false);
  619 + opt->fnames_ = std::move(neg);
  620 + } else {
  621 + opt = add_option(flag_name, fun, std::move(description), false);
  622 + }
599 623
600 - Option *opt = add_option(flag_name, fun, description, false);  
601 if(opt->get_positional()) 624 if(opt->get_positional())
602 throw IncorrectConstruction::PositionalFlag(flag_name); 625 throw IncorrectConstruction::PositionalFlag(flag_name);
603 opt->type_size(0); 626 opt->type_size(0);
@@ -607,9 +630,9 @@ class App { @@ -607,9 +630,9 @@ class App {
607 #ifdef CLI11_CPP14 630 #ifdef CLI11_CPP14
608 /// Add option for callback (C++14 or better only) 631 /// Add option for callback (C++14 or better only)
609 Option *add_flag(std::string flag_name, 632 Option *add_flag(std::string flag_name,
610 - std::function<void(size_t)> function, ///< A function to call, void(size_t) 633 + std::function<void(int)> function, ///< A function to call, void(int)
611 std::string description = "") { 634 std::string description = "") {
612 - return add_flag_function(flag_name, std::move(function), description); 635 + return add_flag_function(std::move(flag_name), std::move(function), std::move(description));
613 } 636 }
614 #endif 637 #endif
615 638
@@ -628,7 +651,7 @@ class App { @@ -628,7 +651,7 @@ class App {
628 return std::find(std::begin(options), std::end(options), member) != std::end(options); 651 return std::find(std::begin(options), std::end(options), member) != std::end(options);
629 }; 652 };
630 653
631 - Option *opt = add_option(option_name, std::move(fun), description, false); 654 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
632 std::string typeval = detail::type_name<T>(); 655 std::string typeval = detail::type_name<T>();
633 typeval += " in {" + detail::join(options) + "}"; 656 typeval += " in {" + detail::join(options) + "}";
634 opt->type_name(typeval); 657 opt->type_name(typeval);
@@ -650,7 +673,7 @@ class App { @@ -650,7 +673,7 @@ class App {
650 return std::find(std::begin(options), std::end(options), member) != std::end(options); 673 return std::find(std::begin(options), std::end(options), member) != std::end(options);
651 }; 674 };
652 675
653 - Option *opt = add_option(option_name, std::move(fun), description, false); 676 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
654 opt->type_name_fn( 677 opt->type_name_fn(
655 [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); 678 [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; });
656 679
@@ -673,7 +696,7 @@ class App { @@ -673,7 +696,7 @@ class App {
673 return std::find(std::begin(options), std::end(options), member) != std::end(options); 696 return std::find(std::begin(options), std::end(options), member) != std::end(options);
674 }; 697 };
675 698
676 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 699 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
677 std::string typeval = detail::type_name<T>(); 700 std::string typeval = detail::type_name<T>();
678 typeval += " in {" + detail::join(options) + "}"; 701 typeval += " in {" + detail::join(options) + "}";
679 opt->type_name(typeval); 702 opt->type_name(typeval);
@@ -701,7 +724,7 @@ class App { @@ -701,7 +724,7 @@ class App {
701 return std::find(std::begin(options), std::end(options), member) != std::end(options); 724 return std::find(std::begin(options), std::end(options), member) != std::end(options);
702 }; 725 };
703 726
704 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 727 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
705 opt->type_name_fn( 728 opt->type_name_fn(
706 [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); 729 [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; });
707 if(defaulted) { 730 if(defaulted) {
@@ -732,7 +755,7 @@ class App { @@ -732,7 +755,7 @@ class App {
732 } 755 }
733 }; 756 };
734 757
735 - Option *opt = add_option(option_name, std::move(fun), description, false); 758 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
736 std::string typeval = detail::type_name<std::string>(); 759 std::string typeval = detail::type_name<std::string>();
737 typeval += " in {" + detail::join(options) + "}"; 760 typeval += " in {" + detail::join(options) + "}";
738 opt->type_name(typeval); 761 opt->type_name(typeval);
@@ -761,7 +784,7 @@ class App { @@ -761,7 +784,7 @@ class App {
761 } 784 }
762 }; 785 };
763 786
764 - Option *opt = add_option(option_name, std::move(fun), description, false); 787 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
765 opt->type_name_fn([&options]() { 788 opt->type_name_fn([&options]() {
766 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; 789 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
767 }); 790 });
@@ -790,7 +813,7 @@ class App { @@ -790,7 +813,7 @@ class App {
790 } 813 }
791 }; 814 };
792 815
793 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 816 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
794 std::string typeval = detail::type_name<std::string>(); 817 std::string typeval = detail::type_name<std::string>();
795 typeval += " in {" + detail::join(options) + "}"; 818 typeval += " in {" + detail::join(options) + "}";
796 opt->type_name(typeval); 819 opt->type_name(typeval);
@@ -821,7 +844,7 @@ class App { @@ -821,7 +844,7 @@ class App {
821 } 844 }
822 }; 845 };
823 846
824 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 847 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
825 opt->type_name_fn([&options]() { 848 opt->type_name_fn([&options]() {
826 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; 849 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
827 }); 850 });
@@ -851,7 +874,7 @@ class App { @@ -851,7 +874,7 @@ class App {
851 } 874 }
852 }; 875 };
853 876
854 - Option *opt = add_option(option_name, std::move(fun), description, false); 877 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
855 std::string typeval = detail::type_name<std::string>(); 878 std::string typeval = detail::type_name<std::string>();
856 typeval += " in {" + detail::join(options) + "}"; 879 typeval += " in {" + detail::join(options) + "}";
857 opt->type_name(typeval); 880 opt->type_name(typeval);
@@ -880,7 +903,7 @@ class App { @@ -880,7 +903,7 @@ class App {
880 } 903 }
881 }; 904 };
882 905
883 - Option *opt = add_option(option_name, std::move(fun), description, false); 906 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
884 opt->type_name_fn([&options]() { 907 opt->type_name_fn([&options]() {
885 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; 908 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
886 }); 909 });
@@ -909,7 +932,7 @@ class App { @@ -909,7 +932,7 @@ class App {
909 } 932 }
910 }; 933 };
911 934
912 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 935 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
913 std::string typeval = detail::type_name<std::string>(); 936 std::string typeval = detail::type_name<std::string>();
914 typeval += " in {" + detail::join(options) + "}"; 937 typeval += " in {" + detail::join(options) + "}";
915 opt->type_name(typeval); 938 opt->type_name(typeval);
@@ -941,7 +964,7 @@ class App { @@ -941,7 +964,7 @@ class App {
941 } 964 }
942 }; 965 };
943 966
944 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 967 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
945 opt->type_name_fn([&options]() { 968 opt->type_name_fn([&options]() {
946 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; 969 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
947 }); 970 });
@@ -971,7 +994,7 @@ class App { @@ -971,7 +994,7 @@ class App {
971 } 994 }
972 }; 995 };
973 996
974 - Option *opt = add_option(option_name, std::move(fun), description, false); 997 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
975 std::string typeval = detail::type_name<std::string>(); 998 std::string typeval = detail::type_name<std::string>();
976 typeval += " in {" + detail::join(options) + "}"; 999 typeval += " in {" + detail::join(options) + "}";
977 opt->type_name(typeval); 1000 opt->type_name(typeval);
@@ -1000,7 +1023,7 @@ class App { @@ -1000,7 +1023,7 @@ class App {
1000 } 1023 }
1001 }; 1024 };
1002 1025
1003 - Option *opt = add_option(option_name, std::move(fun), description, false); 1026 + Option *opt = add_option(option_name, std::move(fun), std::move(description), false);
1004 opt->type_name_fn([&options]() { 1027 opt->type_name_fn([&options]() {
1005 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; 1028 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
1006 }); 1029 });
@@ -1029,7 +1052,7 @@ class App { @@ -1029,7 +1052,7 @@ class App {
1029 } 1052 }
1030 }; 1053 };
1031 1054
1032 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 1055 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
1033 std::string typeval = detail::type_name<std::string>(); 1056 std::string typeval = detail::type_name<std::string>();
1034 typeval += " in {" + detail::join(options) + "}"; 1057 typeval += " in {" + detail::join(options) + "}";
1035 opt->type_name(typeval); 1058 opt->type_name(typeval);
@@ -1061,7 +1084,7 @@ class App { @@ -1061,7 +1084,7 @@ class App {
1061 } 1084 }
1062 }; 1085 };
1063 1086
1064 - Option *opt = add_option(option_name, std::move(fun), description, defaulted); 1087 + Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
1065 opt->type_name_fn([&options]() { 1088 opt->type_name_fn([&options]() {
1066 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; 1089 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
1067 }); 1090 });
@@ -1090,7 +1113,7 @@ class App { @@ -1090,7 +1113,7 @@ class App {
1090 return worked; 1113 return worked;
1091 }; 1114 };
1092 1115
1093 - CLI::Option *opt = add_option(option_name, std::move(fun), description, defaulted); 1116 + CLI::Option *opt = add_option(option_name, std::move(fun), std::move(description), defaulted);
1094 opt->type_name(label)->type_size(2); 1117 opt->type_name(label)->type_size(2);
1095 if(defaulted) { 1118 if(defaulted) {
1096 std::stringstream out; 1119 std::stringstream out;
@@ -1149,7 +1172,7 @@ class App { @@ -1149,7 +1172,7 @@ class App {
1149 1172
1150 /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag 1173 /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
1151 App *add_subcommand(std::string subcommand_name = "", std::string description = "") { 1174 App *add_subcommand(std::string subcommand_name = "", std::string description = "") {
1152 - CLI::App_p subcom = std::shared_ptr<App>(new App(description, subcommand_name, this)); 1175 + CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(description), subcommand_name, this));
1153 return add_subcommand(std::move(subcom)); 1176 return add_subcommand(std::move(subcom));
1154 } 1177 }
1155 1178
@@ -1186,7 +1209,7 @@ class App { @@ -1186,7 +1209,7 @@ class App {
1186 } 1209 }
1187 /// Get a pointer to subcommand by index 1210 /// Get a pointer to subcommand by index
1188 App *get_subcommand(int index = 0) const { 1211 App *get_subcommand(int index = 0) const {
1189 - if((index >= 0) && (index < subcommands_.size())) 1212 + if((index >= 0) && (index < static_cast<int>(subcommands_.size())))
1190 return subcommands_[index].get(); 1213 return subcommands_[index].get();
1191 throw OptionNotFound(std::to_string(index)); 1214 throw OptionNotFound(std::to_string(index));
1192 } 1215 }
@@ -1211,7 +1234,7 @@ class App { @@ -1211,7 +1234,7 @@ class App {
1211 1234
1212 /// Get an owning pointer to subcommand by index 1235 /// Get an owning pointer to subcommand by index
1213 CLI::App_p get_subcommand_ptr(int index = 0) const { 1236 CLI::App_p get_subcommand_ptr(int index = 0) const {
1214 - if((index >= 0) && (index < subcommands_.size())) 1237 + if((index >= 0) && (index < static_cast<int>(subcommands_.size())))
1215 return subcommands_[index]; 1238 return subcommands_[index];
1216 throw OptionNotFound(std::to_string(index)); 1239 throw OptionNotFound(std::to_string(index));
1217 } 1240 }
@@ -1957,7 +1980,12 @@ class App { @@ -1957,7 +1980,12 @@ class App {
1957 if(op->empty()) { 1980 if(op->empty()) {
1958 // Flag parsing 1981 // Flag parsing
1959 if(op->get_type_size() == 0) { 1982 if(op->get_type_size() == 0) {
1960 - op->set_results(config_formatter_->to_flag(item)); 1983 + auto res = config_formatter_->to_flag(item);
  1984 + if(op->check_fname(item.name)) {
  1985 + res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res));
  1986 + }
  1987 + op->add_result(res);
  1988 +
1961 } else { 1989 } else {
1962 op->set_results(item.inputs); 1990 op->set_results(item.inputs);
1963 op->run_callback(); 1991 op->run_callback();
@@ -2138,18 +2166,27 @@ class App { @@ -2138,18 +2166,27 @@ class App {
2138 2166
2139 // Make sure we always eat the minimum for unlimited vectors 2167 // Make sure we always eat the minimum for unlimited vectors
2140 int collected = 0; 2168 int collected = 0;
2141 - 2169 + // deal with flag like things
  2170 + if(num == 0) {
  2171 + try {
  2172 + auto res = (value.empty()) ? std ::string("1") : detail::to_flag_value(value);
  2173 + if(op->check_fname(arg_name)) {
  2174 + res = (res == "1") ? "-1" : ((res[0] == '-') ? res.substr(1) : std::string("-" + res));
  2175 + }
  2176 + op->add_result(res);
  2177 + parse_order_.push_back(op.get());
  2178 + } catch(const std::invalid_argument &) {
  2179 + throw ConversionError::TrueFalse(arg_name);
  2180 + }
  2181 + }
2142 // --this=value 2182 // --this=value
2143 - if(!value.empty()) { 2183 + else if(!value.empty()) {
2144 // If exact number expected 2184 // If exact number expected
2145 if(num > 0) 2185 if(num > 0)
2146 num--; 2186 num--;
2147 op->add_result(value); 2187 op->add_result(value);
2148 parse_order_.push_back(op.get()); 2188 parse_order_.push_back(op.get());
2149 collected += 1; 2189 collected += 1;
2150 - } else if(num == 0) {  
2151 - op->add_result("");  
2152 - parse_order_.push_back(op.get());  
2153 // -Trest 2190 // -Trest
2154 } else if(!rest.empty()) { 2191 } else if(!rest.empty()) {
2155 if(num > 0) 2192 if(num > 0)
include/CLI/ConfigFwd.hpp
@@ -69,27 +69,16 @@ class Config { @@ -69,27 +69,16 @@ 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  
73 - virtual std::vector<std::string> to_flag(const ConfigItem &item) const { 72 + /// Convert a flag to a bool representation
  73 + virtual std::string to_flag(const ConfigItem &item) const {
74 if(item.inputs.size() == 1) { 74 if(item.inputs.size() == 1) {
75 - std::string val = item.inputs.at(0);  
76 - val = detail::to_lower(val);  
77 -  
78 - if(val == "true" || val == "on" || val == "yes") {  
79 - return std::vector<std::string>(1);  
80 - } else if(val == "false" || val == "off" || val == "no") {  
81 - return std::vector<std::string>();  
82 - } else {  
83 - try {  
84 - size_t ui = std::stoul(val);  
85 - return std::vector<std::string>(ui);  
86 - } catch(const std::invalid_argument &) {  
87 - throw ConversionError::TrueFalse(item.fullname());  
88 - } 75 + try {
  76 + return detail::to_flag_value(item.inputs.at(0));
  77 + } catch(const std::invalid_argument &) {
  78 + throw ConversionError::TrueFalse(item.fullname());
89 } 79 }
90 - } else {  
91 - throw ConversionError::TooManyInputsFlag(item.fullname());  
92 } 80 }
  81 + throw ConversionError::TooManyInputsFlag(item.fullname());
93 } 82 }
94 83
95 /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure 84 /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
include/CLI/Formatter.hpp
@@ -59,10 +59,7 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co @@ -59,10 +59,7 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co
59 inline std::string Formatter::make_description(const App *app) const { 59 inline std::string Formatter::make_description(const App *app) const {
60 std::string desc = app->get_description(); 60 std::string desc = app->get_description();
61 61
62 - if(!desc.empty())  
63 - return desc + "\n";  
64 - else  
65 - return ""; 62 + return (!desc.empty()) ? desc + "\n" : std::string{};
66 } 63 }
67 64
68 inline std::string Formatter::make_usage(const App *app, std::string name) const { 65 inline std::string Formatter::make_usage(const App *app, std::string name) const {
@@ -243,7 +240,6 @@ inline std::string Formatter::make_option_usage(const Option *opt) const { @@ -243,7 +240,6 @@ inline std::string Formatter::make_option_usage(const Option *opt) const {
243 out << "(" << std::to_string(opt->get_expected()) << "x)"; 240 out << "(" << std::to_string(opt->get_expected()) << "x)";
244 else if(opt->get_expected() < 0) 241 else if(opt->get_expected() < 0)
245 out << "..."; 242 out << "...";
246 -  
247 return opt->get_required() ? out.str() : "[" + out.str() + "]"; 243 return opt->get_required() ? out.str() : "[" + out.str() + "]";
248 } 244 }
249 245
include/CLI/Option.hpp
@@ -173,6 +173,10 @@ class Option : public OptionBase&lt;Option&gt; { @@ -173,6 +173,10 @@ class Option : public OptionBase&lt;Option&gt; {
173 /// A list of the long names (`--a`) without the leading dashes 173 /// A list of the long names (`--a`) without the leading dashes
174 std::vector<std::string> lnames_; 174 std::vector<std::string> lnames_;
175 175
  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
  178 + std::vector<std::string> fnames_;
  179 +
176 /// A positional name 180 /// A positional name
177 std::string pname_; 181 std::string pname_;
178 182
@@ -478,6 +482,9 @@ class Option : public OptionBase&lt;Option&gt; { @@ -478,6 +482,9 @@ class Option : public OptionBase&lt;Option&gt; {
478 /// Get the short names 482 /// Get the short names
479 const std::vector<std::string> get_snames() const { return snames_; } 483 const std::vector<std::string> get_snames() const { return snames_; }
480 484
  485 + /// get the negative flag names
  486 + const std::vector<std::string> get_fnames() const { return fnames_; }
  487 +
481 /// The number of times the option expects to be included 488 /// The number of times the option expects to be included
482 int get_expected() const { return expected_; } 489 int get_expected() const { return expected_; }
483 490
@@ -542,12 +549,27 @@ class Option : public OptionBase&lt;Option&gt; { @@ -542,12 +549,27 @@ class Option : public OptionBase&lt;Option&gt; {
542 /// The all list will never include a positional unless asked or that's the only name. 549 /// The all list will never include a positional unless asked or that's the only name.
543 if((positional && pname_.length()) || (snames_.empty() && lnames_.empty())) 550 if((positional && pname_.length()) || (snames_.empty() && lnames_.empty()))
544 name_list.push_back(pname_); 551 name_list.push_back(pname_);
  552 + if((get_items_expected() == 0) && (!fnames_.empty())) {
  553 + for(const std::string &sname : snames_) {
  554 + name_list.push_back("-" + sname);
  555 + if(check_fname(sname)) {
  556 + name_list.back() += "{false}";
  557 + }
  558 + }
545 559
546 - for(const std::string &sname : snames_)  
547 - name_list.push_back("-" + sname); 560 + for(const std::string &lname : lnames_) {
  561 + name_list.push_back("--" + lname);
  562 + if(check_fname(lname)) {
  563 + name_list.back() += "{false}";
  564 + }
  565 + }
  566 + } else {
  567 + for(const std::string &sname : snames_)
  568 + name_list.push_back("-" + sname);
548 569
549 - for(const std::string &lname : lnames_)  
550 - name_list.push_back("--" + lname); 570 + for(const std::string &lname : lnames_)
  571 + name_list.push_back("--" + lname);
  572 + }
551 573
552 return detail::join(name_list); 574 return detail::join(name_list);
553 575
@@ -651,62 +673,55 @@ class Option : public OptionBase&lt;Option&gt; { @@ -651,62 +673,55 @@ class Option : public OptionBase&lt;Option&gt; {
651 /// Check a name. Requires "-" or "--" for short / long, supports positional name 673 /// Check a name. Requires "-" or "--" for short / long, supports positional name
652 bool check_name(std::string name) const { 674 bool check_name(std::string name) const {
653 675
654 - if(name.length() > 2 && name.substr(0, 2) == "--") 676 + if(name.length() > 2 && name[0] == '-' && name[1] == '-')
655 return check_lname(name.substr(2)); 677 return check_lname(name.substr(2));
656 - else if(name.length() > 1 && name.substr(0, 1) == "-") 678 + else if(name.length() > 1 && name.front() == '-')
657 return check_sname(name.substr(1)); 679 return check_sname(name.substr(1));
658 else { 680 else {
659 std::string local_pname = pname_; 681 std::string local_pname = pname_;
660 - if(ignore_case_) {  
661 - local_pname = detail::to_lower(local_pname);  
662 - name = detail::to_lower(name);  
663 - }  
664 if(ignore_underscore_) { 682 if(ignore_underscore_) {
665 local_pname = detail::remove_underscore(local_pname); 683 local_pname = detail::remove_underscore(local_pname);
666 name = detail::remove_underscore(name); 684 name = detail::remove_underscore(name);
667 } 685 }
  686 + if(ignore_case_) {
  687 + local_pname = detail::to_lower(local_pname);
  688 + name = detail::to_lower(name);
  689 + }
668 return name == local_pname; 690 return name == local_pname;
669 } 691 }
670 } 692 }
671 693
672 /// Requires "-" to be removed from string 694 /// Requires "-" to be removed from string
673 - bool check_sname(std::string name) const {  
674 - if(ignore_case_) { // there can be no extra underscores in check_sname  
675 - name = detail::to_lower(name);  
676 - return std::find_if(std::begin(snames_), std::end(snames_), [&name](std::string local_sname) {  
677 - return detail::to_lower(local_sname) == name;  
678 - }) != std::end(snames_);  
679 - } else  
680 - return std::find(std::begin(snames_), std::end(snames_), name) != std::end(snames_);  
681 - } 695 + bool check_sname(std::string name) const { return detail::check_is_member(name, snames_, ignore_case_); }
682 696
683 /// Requires "--" to be removed from string 697 /// Requires "--" to be removed from string
684 bool check_lname(std::string name) const { 698 bool check_lname(std::string name) const {
685 - if(ignore_case_) {  
686 - if(ignore_underscore_) {  
687 - name = detail::to_lower(detail::remove_underscore(name));  
688 - return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {  
689 - return detail::to_lower(detail::remove_underscore(local_sname)) == name;  
690 - }) != std::end(lnames_);  
691 - } else {  
692 - name = detail::to_lower(name);  
693 - return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {  
694 - return detail::to_lower(local_sname) == name;  
695 - }) != std::end(lnames_);  
696 - } 699 + return detail::check_is_member(name, lnames_, ignore_case_, ignore_underscore_);
  700 + }
697 701
698 - } else if(ignore_underscore_) {  
699 - name = detail::remove_underscore(name);  
700 - return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {  
701 - return detail::remove_underscore(local_sname) == name;  
702 - }) != std::end(lnames_);  
703 - } else  
704 - return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_); 702 + /// Requires "--" to be removed from string
  703 + bool check_fname(std::string name) const {
  704 + if(fnames_.empty()) {
  705 + return false;
  706 + }
  707 + return detail::check_is_member(name, fnames_, ignore_case_, ignore_underscore_);
705 } 708 }
706 709
707 /// Puts a result at the end 710 /// Puts a result at the end
708 Option *add_result(std::string s) { 711 Option *add_result(std::string s) {
709 - results_.push_back(s); 712 + results_.push_back(std::move(s));
  713 + callback_run_ = false;
  714 + return this;
  715 + }
  716 +
  717 + /// Puts a result at the end
  718 + Option *add_result(std::vector<std::string> s) {
  719 + if(results_.empty()) {
  720 + results_ = std::move(s);
  721 + } else {
  722 + results_.insert(results_.end(), s.begin(), s.end());
  723 + }
  724 +
710 callback_run_ = false; 725 callback_run_ = false;
711 return this; 726 return this;
712 } 727 }
include/CLI/Split.hpp
@@ -67,6 +67,26 @@ inline std::vector&lt;std::string&gt; split_names(std::string current) { @@ -67,6 +67,26 @@ 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);
  84 + }
  85 + flag.erase(0, flag.find_first_not_of("-!"));
  86 + }
  87 + return output;
  88 +}
  89 +
70 /// Get a vector of short names, one of long names, and a single name 90 /// Get a vector of short names, one of long names, and a single name
71 inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string> 91 inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
72 get_names(const std::vector<std::string> &input) { 92 get_names(const std::vector<std::string> &input) {
include/CLI/StringTools.hpp
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 #include <iomanip> 7 #include <iomanip>
8 #include <locale> 8 #include <locale>
9 #include <sstream> 9 #include <sstream>
  10 +#include <stdexcept>
10 #include <string> 11 #include <string>
11 #include <type_traits> 12 #include <type_traits>
12 #include <vector> 13 #include <vector>
@@ -161,6 +162,42 @@ inline std::string find_and_replace(std::string str, std::string from, std::stri @@ -161,6 +162,42 @@ inline std::string find_and_replace(std::string str, std::string from, std::stri
161 return str; 162 return str;
162 } 163 }
163 164
  165 +/// check if the flag definitions has possible false flags
  166 +inline bool has_false_flags(const std::string &flags) { return (flags.find_first_of("{!") != std::string::npos); }
  167 +
  168 +inline void remove_false_flag_notation(std::string &flags) {
  169 + flags = detail::find_and_replace(flags, "{false}", std::string{});
  170 + flags = detail::find_and_replace(flags, "{true}", std::string{});
  171 + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
  172 +}
  173 +
  174 +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
  175 +inline bool check_is_member(std::string name,
  176 + const std::vector<std::string> names,
  177 + bool ignore_case = false,
  178 + bool ignore_underscore = false) {
  179 + if(ignore_case) {
  180 + if(ignore_underscore) {
  181 + name = detail::to_lower(detail::remove_underscore(name));
  182 + return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
  183 + return detail::to_lower(detail::remove_underscore(local_name)) == name;
  184 + }) != std::end(names);
  185 + } else {
  186 + name = detail::to_lower(name);
  187 + return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
  188 + return detail::to_lower(local_name) == name;
  189 + }) != std::end(names);
  190 + }
  191 +
  192 + } else if(ignore_underscore) {
  193 + name = detail::remove_underscore(name);
  194 + return std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
  195 + return detail::remove_underscore(local_name) == name;
  196 + }) != std::end(names);
  197 + } else
  198 + return std::find(std::begin(names), std::end(names), name) != std::end(names);
  199 +}
  200 +
164 /// Find a trigger string and call a modify callable function that takes the current string and starting position of the 201 /// Find a trigger string and call a modify callable function that takes the current string and starting position of the
165 /// trigger and returns the position in the string to search for the next trigger string 202 /// trigger and returns the position in the string to search for the next trigger string
166 template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { 203 template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
@@ -171,6 +208,49 @@ template &lt;typename Callable&gt; inline std::string find_and_modify(std::string str, @@ -171,6 +208,49 @@ template &lt;typename Callable&gt; inline std::string find_and_modify(std::string str,
171 return str; 208 return str;
172 } 209 }
173 210
  211 +/// generate a vector of values that represent a boolean they will be either "+" or "-"
  212 +inline std::string to_flag_value(std::string val) {
  213 + val = detail::to_lower(val);
  214 + std::string ret;
  215 + if(val.size() == 1) {
  216 + switch(val[0]) {
  217 + case '0':
  218 + case 'f':
  219 + case 'n':
  220 + case '-':
  221 + ret = "-1";
  222 + break;
  223 + case '1':
  224 + case 't':
  225 + case 'y':
  226 + case '+':
  227 + ret = "1";
  228 + break;
  229 + case '2':
  230 + case '3':
  231 + case '4':
  232 + case '5':
  233 + case '6':
  234 + case '7':
  235 + case '8':
  236 + case '9':
  237 + ret = val;
  238 + break;
  239 + default:
  240 + throw std::invalid_argument("unrecognized character");
  241 + }
  242 + return ret;
  243 + }
  244 + if(val == "true" || val == "on" || val == "yes" || val == "enable") {
  245 + ret = "1";
  246 + } else if(val == "false" || val == "off" || val == "no" || val == "disable") {
  247 + ret = "-1";
  248 + } else {
  249 + auto ui = std::stoll(val);
  250 + ret = (ui == 0) ? "-1" : val;
  251 + }
  252 + return ret;
  253 +}
174 /// Split a string '"one two" "three"' into 'one two', 'three' 254 /// Split a string '"one two" "three"' into 'one two', 'three'
175 /// Quote characters can be ` ' or " 255 /// Quote characters can be ` ' or "
176 inline std::vector<std::string> split_up(std::string str) { 256 inline std::vector<std::string> split_up(std::string str) {
include/CLI/TypeTools.hpp
@@ -80,7 +80,8 @@ constexpr const char *type_name() { @@ -80,7 +80,8 @@ constexpr const char *type_name() {
80 80
81 /// Signed integers / enums 81 /// Signed integers / enums
82 template <typename T, 82 template <typename T,
83 - enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), detail::enabler> = detail::dummy> 83 + enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value, detail::enabler> =
  84 + detail::dummy>
84 bool lexical_cast(std::string input, T &output) { 85 bool lexical_cast(std::string input, T &output) {
85 try { 86 try {
86 size_t n = 0; 87 size_t n = 0;
@@ -96,7 +97,8 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -96,7 +97,8 @@ bool lexical_cast(std::string input, T &amp;output) {
96 97
97 /// Unsigned integers 98 /// Unsigned integers
98 template <typename T, 99 template <typename T,
99 - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> 100 + enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value, detail::enabler> =
  101 + detail::dummy>
100 bool lexical_cast(std::string input, T &output) { 102 bool lexical_cast(std::string input, T &output) {
101 if(!input.empty() && input.front() == '-') 103 if(!input.empty() && input.front() == '-')
102 return false; // std::stoull happily converts negative values to junk without any errors. 104 return false; // std::stoull happily converts negative values to junk without any errors.
@@ -113,6 +115,24 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -113,6 +115,24 @@ bool lexical_cast(std::string input, T &amp;output) {
113 } 115 }
114 } 116 }
115 117
  118 +/// boolean values
  119 +template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
  120 +bool lexical_cast(std::string input, T &output) {
  121 + try {
  122 + auto out = to_flag_value(input);
  123 + if(out == "1") {
  124 + output = true;
  125 + } else if(out == "-1") {
  126 + output = false;
  127 + } else {
  128 + output = (std::stoll(out) > 0);
  129 + }
  130 + return true;
  131 + } catch(const std::invalid_argument &) {
  132 + return false;
  133 + }
  134 +}
  135 +
116 /// Floats 136 /// Floats
117 template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> 137 template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
118 bool lexical_cast(std::string input, T &output) { 138 bool lexical_cast(std::string input, T &output) {
@@ -150,5 +170,37 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -150,5 +170,37 @@ bool lexical_cast(std::string input, T &amp;output) {
150 return !is.fail() && !is.rdbuf()->in_avail(); 170 return !is.fail() && !is.rdbuf()->in_avail();
151 } 171 }
152 172
  173 +/// sum a vector of flag representations
  174 +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
  175 +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
  176 +/// common true and false strings then uses stoll to convert the rest for summing
  177 +template <typename T,
  178 + enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
  179 +void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
  180 + int64_t count{0};
  181 + static const auto trueString = std::string("1");
  182 + static const auto falseString = std::string("-1");
  183 + for(auto &flag : flags) {
  184 + count += (flag == trueString) ? 1 : ((flag == falseString) ? (-1) : std::stoll(flag));
  185 + }
  186 + output = (count > 0) ? static_cast<T>(count) : T{0};
  187 +}
  188 +
  189 +/// sum a vector of flag representations
  190 +/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
  191 +/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
  192 +/// common true and false strings then uses stoll to convert the rest for summing
  193 +template <typename T,
  194 + enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy>
  195 +void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
  196 + int64_t count{0};
  197 + static const auto trueString = std::string("1");
  198 + static const auto falseString = std::string("-1");
  199 + for(auto &flag : flags) {
  200 + count += (flag == trueString) ? 1 : ((flag == falseString) ? (-1) : std::stoll(flag));
  201 + }
  202 + output = static_cast<T>(count);
  203 +}
  204 +
153 } // namespace detail 205 } // namespace detail
154 } // namespace CLI 206 } // namespace CLI
tests/AppTest.cpp
@@ -91,6 +91,57 @@ TEST_F(TApp, OneFlagRef) { @@ -91,6 +91,57 @@ TEST_F(TApp, OneFlagRef) {
91 EXPECT_EQ(1, ref); 91 EXPECT_EQ(1, ref);
92 } 92 }
93 93
  94 +TEST_F(TApp, OneFlagRefValue) {
  95 + int ref;
  96 + app.add_flag("-c,--count", ref);
  97 + args = {"--count=7"};
  98 + run();
  99 + EXPECT_EQ(1u, app.count("-c"));
  100 + EXPECT_EQ(1u, app.count("--count"));
  101 + EXPECT_EQ(7, ref);
  102 +}
  103 +
  104 +TEST_F(TApp, OneFlagRefValueFalse) {
  105 + int ref;
  106 + app.add_flag("-c,--count", ref);
  107 + args = {"--count=false"};
  108 + run();
  109 + EXPECT_EQ(1u, app.count("-c"));
  110 + EXPECT_EQ(1u, app.count("--count"));
  111 + EXPECT_EQ(-1, ref);
  112 +
  113 + args = {"--count=0"};
  114 + run();
  115 + EXPECT_EQ(1u, app.count("-c"));
  116 + EXPECT_EQ(1u, app.count("--count"));
  117 + EXPECT_EQ(-1, ref);
  118 +
  119 + args = {"--count=happy"};
  120 + EXPECT_THROW(run(), CLI::ConversionError);
  121 +}
  122 +
  123 +TEST_F(TApp, FlagNegation) {
  124 + int ref;
  125 + app.add_flag("-c,--count,--ncount{false}", ref);
  126 + args = {"--count", "-c", "--ncount"};
  127 + run();
  128 + EXPECT_EQ(3u, app.count("-c"));
  129 + EXPECT_EQ(3u, app.count("--count"));
  130 + EXPECT_EQ(3u, app.count("--ncount"));
  131 + EXPECT_EQ(1, ref);
  132 +}
  133 +
  134 +TEST_F(TApp, FlagNegationShortcutNotation) {
  135 + int ref;
  136 + app.add_flag("-c,--count,!--ncount", ref);
  137 + args = {"--count", "-c", "--ncount"};
  138 + run();
  139 + EXPECT_EQ(3u, app.count("-c"));
  140 + EXPECT_EQ(3u, app.count("--count"));
  141 + EXPECT_EQ(3u, app.count("--ncount"));
  142 + EXPECT_EQ(1, ref);
  143 +}
  144 +
94 TEST_F(TApp, OneString) { 145 TEST_F(TApp, OneString) {
95 std::string str; 146 std::string str;
96 app.add_option("-s,--string", str); 147 app.add_option("-s,--string", str);
@@ -467,6 +518,23 @@ TEST_F(TApp, BoolOnlyFlag) { @@ -467,6 +518,23 @@ TEST_F(TApp, BoolOnlyFlag) {
467 EXPECT_THROW(run(), CLI::ConversionError); 518 EXPECT_THROW(run(), CLI::ConversionError);
468 } 519 }
469 520
  521 +TEST_F(TApp, BoolOption) {
  522 + bool bflag;
  523 + app.add_option("-b", bflag);
  524 +
  525 + args = {"-b", "false"};
  526 + run();
  527 + EXPECT_FALSE(bflag);
  528 +
  529 + args = {"-b", "1"};
  530 + run();
  531 + EXPECT_TRUE(bflag);
  532 +
  533 + args = {"-b", "-7"};
  534 + run();
  535 + EXPECT_FALSE(bflag);
  536 +}
  537 +
470 TEST_F(TApp, ShortOpts) { 538 TEST_F(TApp, ShortOpts) {
471 539
472 unsigned long long funnyint; 540 unsigned long long funnyint;
@@ -976,6 +1044,64 @@ TEST_F(TApp, CallbackFlags) { @@ -976,6 +1044,64 @@ TEST_F(TApp, CallbackFlags) {
976 EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); 1044 EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
977 } 1045 }
978 1046
  1047 +TEST_F(TApp, CallbackFlagsFalse) {
  1048 + int value = 0;
  1049 +
  1050 + auto func = [&value](int x) { value = x; };
  1051 +
  1052 + app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
  1053 +
  1054 + run();
  1055 + EXPECT_EQ(value, 0u);
  1056 +
  1057 + args = {"-f"};
  1058 + run();
  1059 + EXPECT_EQ(value, -1);
  1060 +
  1061 + args = {"-vfv"};
  1062 + run();
  1063 + EXPECT_EQ(value, 1);
  1064 +
  1065 + args = {"--fval"};
  1066 + run();
  1067 + EXPECT_EQ(value, -1);
  1068 +
  1069 + args = {"--fval=2"};
  1070 + run();
  1071 + EXPECT_EQ(value, -2);
  1072 +
  1073 + EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
  1074 +}
  1075 +
  1076 +TEST_F(TApp, CallbackFlagsFalseShortcut) {
  1077 + int value = 0;
  1078 +
  1079 + auto func = [&value](int x) { value = x; };
  1080 +
  1081 + app.add_flag_function("-v,!-f,--val,!--fval", func);
  1082 +
  1083 + run();
  1084 + EXPECT_EQ(value, 0u);
  1085 +
  1086 + args = {"-f"};
  1087 + run();
  1088 + EXPECT_EQ(value, -1);
  1089 +
  1090 + args = {"-vfv"};
  1091 + run();
  1092 + EXPECT_EQ(value, 1);
  1093 +
  1094 + args = {"--fval"};
  1095 + run();
  1096 + EXPECT_EQ(value, -1);
  1097 +
  1098 + args = {"--fval=2"};
  1099 + run();
  1100 + EXPECT_EQ(value, -2);
  1101 +
  1102 + EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
  1103 +}
  1104 +
979 #if __cplusplus >= 201402L || _MSC_VER >= 1900 1105 #if __cplusplus >= 201402L || _MSC_VER >= 1900
980 TEST_F(TApp, CallbackFlagsAuto) { 1106 TEST_F(TApp, CallbackFlagsAuto) {
981 1107
tests/FormatterTest.cpp
@@ -83,6 +83,25 @@ TEST(Formatter, OptCustomizeSimple) { @@ -83,6 +83,25 @@ TEST(Formatter, OptCustomizeSimple) {
83 " --opt INT (MUST HAVE) Something\n"); 83 " --opt INT (MUST HAVE) Something\n");
84 } 84 }
85 85
  86 +TEST(Formatter, FalseFlagExample) {
  87 + CLI::App app{"My prog"};
  88 +
  89 + app.get_formatter()->column_width(25);
  90 + app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
  91 +
  92 + int v;
  93 + app.add_flag("--opt,!--no_opt", v, "Something");
  94 +
  95 + bool flag;
  96 + app.add_flag("!-O,--opt2,--no_opt2{false}", flag, "Something else");
  97 +
  98 + std::string help = app.help();
  99 +
  100 + EXPECT_THAT(help, HasSubstr("--no_opt{false}"));
  101 + EXPECT_THAT(help, HasSubstr("--no_opt2{false}"));
  102 + EXPECT_THAT(help, HasSubstr("-O{false}"));
  103 +}
  104 +
86 TEST(Formatter, AppCustomize) { 105 TEST(Formatter, AppCustomize) {
87 CLI::App app{"My prog"}; 106 CLI::App app{"My prog"};
88 app.add_subcommand("subcom1", "This"); 107 app.add_subcommand("subcom1", "This");
tests/HelpersTest.cpp
@@ -68,6 +68,20 @@ TEST(StringTools, Modify3) { @@ -68,6 +68,20 @@ TEST(StringTools, Modify3) {
68 EXPECT_EQ(newString, "aba"); 68 EXPECT_EQ(newString, "aba");
69 } 69 }
70 70
  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");
  79 + EXPECT_THROW(CLI::detail::to_flag_value("frog"), 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");
  83 +}
  84 +
71 TEST(Trim, Various) { 85 TEST(Trim, Various) {
72 std::string s1{" sdlfkj sdflk sd s "}; 86 std::string s1{" sdlfkj sdflk sd s "};
73 std::string a1{"sdlfkj sdflk sd s"}; 87 std::string a1{"sdlfkj sdflk sd s"};
@@ -568,6 +582,20 @@ TEST(Types, LexicalCastDouble) { @@ -568,6 +582,20 @@ TEST(Types, LexicalCastDouble) {
568 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, x)); 582 EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, x));
569 } 583 }
570 584
  585 +TEST(Types, LexicalCastBool) {
  586 + std::string input = "false";
  587 + bool x;
  588 + EXPECT_TRUE(CLI::detail::lexical_cast(input, x));
  589 + EXPECT_FALSE(x);
  590 +
  591 + std::string bad_input = "happy";
  592 + EXPECT_FALSE(CLI::detail::lexical_cast(bad_input, x));
  593 +
  594 + std::string input_true = "EnaBLE";
  595 + EXPECT_TRUE(CLI::detail::lexical_cast(input_true, x));
  596 + EXPECT_TRUE(x);
  597 +}
  598 +
571 TEST(Types, LexicalCastString) { 599 TEST(Types, LexicalCastString) {
572 std::string input = "one"; 600 std::string input = "one";
573 std::string output; 601 std::string output;
tests/IniTest.cpp
@@ -608,6 +608,62 @@ TEST_F(TApp, IniFlags) { @@ -608,6 +608,62 @@ TEST_F(TApp, IniFlags) {
608 EXPECT_EQ(true, five); 608 EXPECT_EQ(true, five);
609 } 609 }
610 610
  611 +TEST_F(TApp, IniFalseFlags) {
  612 + TempFile tmpini{"TestIniTmp.ini"};
  613 + app.set_config("--config", tmpini);
  614 +
  615 + {
  616 + std::ofstream out{tmpini};
  617 + out << "[default]" << std::endl;
  618 + out << "two=-2" << std::endl;
  619 + out << "three=false" << std::endl;
  620 + out << "four=1" << std::endl;
  621 + out << "five" << std::endl;
  622 + }
  623 +
  624 + int two;
  625 + bool three, four, five;
  626 + app.add_flag("--two", two);
  627 + app.add_flag("--three", three);
  628 + app.add_flag("--four", four);
  629 + app.add_flag("--five", five);
  630 +
  631 + run();
  632 +
  633 + EXPECT_EQ(-2, two);
  634 + EXPECT_EQ(false, three);
  635 + EXPECT_EQ(true, four);
  636 + EXPECT_EQ(true, five);
  637 +}
  638 +
  639 +TEST_F(TApp, IniFalseFlagsDef) {
  640 + TempFile tmpini{"TestIniTmp.ini"};
  641 + app.set_config("--config", tmpini);
  642 +
  643 + {
  644 + std::ofstream out{tmpini};
  645 + out << "[default]" << std::endl;
  646 + out << "two=2" << std::endl;
  647 + out << "three=true" << std::endl;
  648 + out << "four=on" << std::endl;
  649 + out << "five" << std::endl;
  650 + }
  651 +
  652 + int two;
  653 + bool three, four, five;
  654 + app.add_flag("--two{false}", two);
  655 + app.add_flag("--three", three);
  656 + app.add_flag("!--four", four);
  657 + app.add_flag("--five", five);
  658 +
  659 + run();
  660 +
  661 + EXPECT_EQ(-2, two);
  662 + EXPECT_EQ(true, three);
  663 + EXPECT_EQ(false, four);
  664 + EXPECT_EQ(true, five);
  665 +}
  666 +
611 TEST_F(TApp, IniOutputSimple) { 667 TEST_F(TApp, IniOutputSimple) {
612 668
613 int v; 669 int v;