Commit c8bd97156bb2c144707119f3e290b925917196f9
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.
Showing
12 changed files
with
560 additions
and
110 deletions
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<Option> { | @@ -173,6 +173,10 @@ class Option : public OptionBase<Option> { | ||
| 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<Option> { | @@ -478,6 +482,9 @@ class Option : public OptionBase<Option> { | ||
| 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<Option> { | @@ -542,12 +549,27 @@ class Option : public OptionBase<Option> { | ||
| 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<Option> { | @@ -651,62 +673,55 @@ class Option : public OptionBase<Option> { | ||
| 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<std::string> split_names(std::string current) { | @@ -67,6 +67,26 @@ inline std::vector<std::string> 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 <typename Callable> inline std::string find_and_modify(std::string str, | @@ -171,6 +208,49 @@ template <typename Callable> 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 &output) { | @@ -96,7 +97,8 @@ bool lexical_cast(std::string input, T &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 &output) { | @@ -113,6 +115,24 @@ bool lexical_cast(std::string input, T &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 &output) { | @@ -150,5 +170,37 @@ bool lexical_cast(std::string input, T &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; |