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 186 app.add_complex(... // Special case: support for complex numbers
187 187  
188 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 193 help_string="")
191 194  
192 195 app.add_flag_function(option_name,
193   - function <void(size_t count)>,
  196 + function <void(int count)>,
194 197 help_string="")
195 198  
196 199 app.add_set(option_name,
... ... @@ -212,7 +215,33 @@ App* subcom = app.add_subcommand(name, description);
212 215  
213 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 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 270 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
242 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 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 274 - `->check(CLI::ExistingFile)`: Requires that the file exists if given.
246 275 - `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
247 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 290 - `-ffilename` (no space required)
262 291 - `-abcf filename` (flags and option can be combined)
263 292 - `--long` (long flag)
  293 +- `--long_flag=true` (long flag with equals)
264 294 - `--file filename` (space)
265 295 - `--file=filename` (equals)
266 296  
... ... @@ -272,6 +302,8 @@ If allow_windows_style_options() is specified in the application or subcommand o
272 302 - `/file:filename` (colon)
273 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 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 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 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 397 To print a configuration file from the passed
366 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 538 /// Add option for flag
539 539 Option *add_flag(std::string flag_name, std::string description = "") {
540 540 CLI::callback_t fun = [](CLI::results_t) { return true; };
541   -
542 541 Option *opt = add_option(flag_name, fun, description, false);
543 542 if(opt->get_positional())
544 543 throw IncorrectConstruction::PositionalFlag(flag_name);
... ... @@ -552,14 +551,21 @@ class App {
552 551 Option *add_flag(std::string flag_name,
553 552 T &flag_count, ///< A variable holding the count
554 553 std::string description = "") {
555   -
556 554 flag_count = 0;
  555 + Option *opt;
557 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 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 569 if(opt->get_positional())
564 570 throw IncorrectConstruction::PositionalFlag(flag_name);
565 571 opt->type_size(0);
... ... @@ -570,16 +576,23 @@ class App {
570 576 /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
571 577 template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
572 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 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 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 596 if(opt->get_positional())
584 597 throw IncorrectConstruction::PositionalFlag(flag_name);
585 598 opt->type_size(0);
... ... @@ -589,15 +602,25 @@ class App {
589 602  
590 603 /// Add option for callback
591 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 606 std::string description = "") {
594 607  
595 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 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 624 if(opt->get_positional())
602 625 throw IncorrectConstruction::PositionalFlag(flag_name);
603 626 opt->type_size(0);
... ... @@ -607,9 +630,9 @@ class App {
607 630 #ifdef CLI11_CPP14
608 631 /// Add option for callback (C++14 or better only)
609 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 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 637 #endif
615 638  
... ... @@ -628,7 +651,7 @@ class App {
628 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 655 std::string typeval = detail::type_name<T>();
633 656 typeval += " in {" + detail::join(options) + "}";
634 657 opt->type_name(typeval);
... ... @@ -650,7 +673,7 @@ class App {
650 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 677 opt->type_name_fn(
655 678 [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; });
656 679  
... ... @@ -673,7 +696,7 @@ class App {
673 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 700 std::string typeval = detail::type_name<T>();
678 701 typeval += " in {" + detail::join(options) + "}";
679 702 opt->type_name(typeval);
... ... @@ -701,7 +724,7 @@ class App {
701 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 728 opt->type_name_fn(
706 729 [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; });
707 730 if(defaulted) {
... ... @@ -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 759 std::string typeval = detail::type_name<std::string>();
737 760 typeval += " in {" + detail::join(options) + "}";
738 761 opt->type_name(typeval);
... ... @@ -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 788 opt->type_name_fn([&options]() {
766 789 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
767 790 });
... ... @@ -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 817 std::string typeval = detail::type_name<std::string>();
795 818 typeval += " in {" + detail::join(options) + "}";
796 819 opt->type_name(typeval);
... ... @@ -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 848 opt->type_name_fn([&options]() {
826 849 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
827 850 });
... ... @@ -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 878 std::string typeval = detail::type_name<std::string>();
856 879 typeval += " in {" + detail::join(options) + "}";
857 880 opt->type_name(typeval);
... ... @@ -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 907 opt->type_name_fn([&options]() {
885 908 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
886 909 });
... ... @@ -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 936 std::string typeval = detail::type_name<std::string>();
914 937 typeval += " in {" + detail::join(options) + "}";
915 938 opt->type_name(typeval);
... ... @@ -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 968 opt->type_name_fn([&options]() {
946 969 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
947 970 });
... ... @@ -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 998 std::string typeval = detail::type_name<std::string>();
976 999 typeval += " in {" + detail::join(options) + "}";
977 1000 opt->type_name(typeval);
... ... @@ -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 1027 opt->type_name_fn([&options]() {
1005 1028 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
1006 1029 });
... ... @@ -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 1056 std::string typeval = detail::type_name<std::string>();
1034 1057 typeval += " in {" + detail::join(options) + "}";
1035 1058 opt->type_name(typeval);
... ... @@ -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 1088 opt->type_name_fn([&options]() {
1066 1089 return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
1067 1090 });
... ... @@ -1090,7 +1113,7 @@ class App {
1090 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 1117 opt->type_name(label)->type_size(2);
1095 1118 if(defaulted) {
1096 1119 std::stringstream out;
... ... @@ -1149,7 +1172,7 @@ class App {
1149 1172  
1150 1173 /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
1151 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 1176 return add_subcommand(std::move(subcom));
1154 1177 }
1155 1178  
... ... @@ -1186,7 +1209,7 @@ class App {
1186 1209 }
1187 1210 /// Get a pointer to subcommand by index
1188 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 1213 return subcommands_[index].get();
1191 1214 throw OptionNotFound(std::to_string(index));
1192 1215 }
... ... @@ -1211,7 +1234,7 @@ class App {
1211 1234  
1212 1235 /// Get an owning pointer to subcommand by index
1213 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 1238 return subcommands_[index];
1216 1239 throw OptionNotFound(std::to_string(index));
1217 1240 }
... ... @@ -1957,7 +1980,12 @@ class App {
1957 1980 if(op->empty()) {
1958 1981 // Flag parsing
1959 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 1989 } else {
1962 1990 op->set_results(item.inputs);
1963 1991 op->run_callback();
... ... @@ -2138,18 +2166,27 @@ class App {
2138 2166  
2139 2167 // Make sure we always eat the minimum for unlimited vectors
2140 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 2182 // --this=value
2143   - if(!value.empty()) {
  2183 + else if(!value.empty()) {
2144 2184 // If exact number expected
2145 2185 if(num > 0)
2146 2186 num--;
2147 2187 op->add_result(value);
2148 2188 parse_order_.push_back(op.get());
2149 2189 collected += 1;
2150   - } else if(num == 0) {
2151   - op->add_result("");
2152   - parse_order_.push_back(op.get());
2153 2190 // -Trest
2154 2191 } else if(!rest.empty()) {
2155 2192 if(num > 0)
... ...
include/CLI/ConfigFwd.hpp
... ... @@ -69,27 +69,16 @@ 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
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 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 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 59 inline std::string Formatter::make_description(const App *app) const {
60 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 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 240 out << "(" << std::to_string(opt->get_expected()) << "x)";
244 241 else if(opt->get_expected() < 0)
245 242 out << "...";
246   -
247 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 173 /// A list of the long names (`--a`) without the leading dashes
174 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 180 /// A positional name
177 181 std::string pname_;
178 182  
... ... @@ -478,6 +482,9 @@ class Option : public OptionBase&lt;Option&gt; {
478 482 /// Get the short names
479 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 488 /// The number of times the option expects to be included
482 489 int get_expected() const { return expected_; }
483 490  
... ... @@ -542,12 +549,27 @@ class Option : public OptionBase&lt;Option&gt; {
542 549 /// The all list will never include a positional unless asked or that's the only name.
543 550 if((positional && pname_.length()) || (snames_.empty() && lnames_.empty()))
544 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 574 return detail::join(name_list);
553 575  
... ... @@ -651,62 +673,55 @@ class Option : public OptionBase&lt;Option&gt; {
651 673 /// Check a name. Requires "-" or "--" for short / long, supports positional name
652 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 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 679 return check_sname(name.substr(1));
658 680 else {
659 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 682 if(ignore_underscore_) {
665 683 local_pname = detail::remove_underscore(local_pname);
666 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 690 return name == local_pname;
669 691 }
670 692 }
671 693  
672 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 697 /// Requires "--" to be removed from string
684 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 710 /// Puts a result at the end
708 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 725 callback_run_ = false;
711 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 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 90 /// Get a vector of short names, one of long names, and a single name
71 91 inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
72 92 get_names(const std::vector<std::string> &input) {
... ...
include/CLI/StringTools.hpp
... ... @@ -7,6 +7,7 @@
7 7 #include <iomanip>
8 8 #include <locale>
9 9 #include <sstream>
  10 +#include <stdexcept>
10 11 #include <string>
11 12 #include <type_traits>
12 13 #include <vector>
... ... @@ -161,6 +162,42 @@ inline std::string find_and_replace(std::string str, std::string from, std::stri
161 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 201 /// Find a trigger string and call a modify callable function that takes the current string and starting position of the
165 202 /// trigger and returns the position in the string to search for the next trigger string
166 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 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 254 /// Split a string '"one two" "three"' into 'one two', 'three'
175 255 /// Quote characters can be ` ' or "
176 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 80  
81 81 /// Signed integers / enums
82 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 85 bool lexical_cast(std::string input, T &output) {
85 86 try {
86 87 size_t n = 0;
... ... @@ -96,7 +97,8 @@ bool lexical_cast(std::string input, T &amp;output) {
96 97  
97 98 /// Unsigned integers
98 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 102 bool lexical_cast(std::string input, T &output) {
101 103 if(!input.empty() && input.front() == '-')
102 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 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 136 /// Floats
117 137 template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
118 138 bool lexical_cast(std::string input, T &output) {
... ... @@ -150,5 +170,37 @@ bool lexical_cast(std::string input, T &amp;output) {
150 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 205 } // namespace detail
154 206 } // namespace CLI
... ...
tests/AppTest.cpp
... ... @@ -91,6 +91,57 @@ TEST_F(TApp, OneFlagRef) {
91 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 145 TEST_F(TApp, OneString) {
95 146 std::string str;
96 147 app.add_option("-s,--string", str);
... ... @@ -467,6 +518,23 @@ TEST_F(TApp, BoolOnlyFlag) {
467 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 538 TEST_F(TApp, ShortOpts) {
471 539  
472 540 unsigned long long funnyint;
... ... @@ -976,6 +1044,64 @@ TEST_F(TApp, CallbackFlags) {
976 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 1105 #if __cplusplus >= 201402L || _MSC_VER >= 1900
980 1106 TEST_F(TApp, CallbackFlagsAuto) {
981 1107  
... ...
tests/FormatterTest.cpp
... ... @@ -83,6 +83,25 @@ TEST(Formatter, OptCustomizeSimple) {
83 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 105 TEST(Formatter, AppCustomize) {
87 106 CLI::App app{"My prog"};
88 107 app.add_subcommand("subcom1", "This");
... ...
tests/HelpersTest.cpp
... ... @@ -68,6 +68,20 @@ TEST(StringTools, Modify3) {
68 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 85 TEST(Trim, Various) {
72 86 std::string s1{" sdlfkj sdflk sd s "};
73 87 std::string a1{"sdlfkj sdflk sd s"};
... ... @@ -568,6 +582,20 @@ TEST(Types, LexicalCastDouble) {
568 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 599 TEST(Types, LexicalCastString) {
572 600 std::string input = "one";
573 601 std::string output;
... ...
tests/IniTest.cpp
... ... @@ -608,6 +608,62 @@ TEST_F(TApp, IniFlags) {
608 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 667 TEST_F(TApp, IniOutputSimple) {
612 668  
613 669 int v;
... ...