Commit 1a6ed01d87b61903b33c2a03f276869eb366003c
Committed by
Henry Schreiner
1 parent
49ac989a
add some additional tests for the failure case, and update the README with docum…
…entation on the new function. use bracket initialization add callback functions for options with the derived values
Showing
3 changed files
with
128 additions
and
18 deletions
README.md
| @@ -179,6 +179,10 @@ app.add_option(option_name, | @@ -179,6 +179,10 @@ app.add_option(option_name, | ||
| 179 | help_string="", | 179 | help_string="", |
| 180 | default=false) | 180 | default=false) |
| 181 | 181 | ||
| 182 | +app.add_option_function<type>(option_name, | ||
| 183 | + function <void(const type &value)>, // int, float, vector, or string-like | ||
| 184 | + help_string="") | ||
| 185 | + | ||
| 182 | app.add_complex(... // Special case: support for complex numbers | 186 | app.add_complex(... // Special case: support for complex numbers |
| 183 | 187 | ||
| 184 | app.add_flag(option_name, | 188 | app.add_flag(option_name, |
| @@ -208,6 +212,8 @@ App* subcom = app.add_subcommand(name, description); | @@ -208,6 +212,8 @@ App* subcom = app.add_subcommand(name, description); | ||
| 208 | 212 | ||
| 209 | 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. | 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. |
| 210 | 214 | ||
| 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 | ||
| 216 | + | ||
| 211 | 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. | 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. |
| 212 | 218 | ||
| 213 | On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters. | 219 | On a compiler that supports C++17's `__has_include`, you can also use `std::optional`, `std::experimental::optional`, and `boost::optional` directly in an `add_option` call. If you don't have `__has_include`, you can define `CLI11_BOOST_OPTIONAL 1` before including CLI11 to manually add support (or 0 to remove) for `boost::optional`. See [CLI11 Internals][] for information on how this was done and how you can add your own converters. |
include/CLI/App.hpp
| @@ -370,6 +370,26 @@ class App { | @@ -370,6 +370,26 @@ class App { | ||
| 370 | return opt; | 370 | return opt; |
| 371 | } | 371 | } |
| 372 | 372 | ||
| 373 | + /// Add option for a callback of a specific type | ||
| 374 | + template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> | ||
| 375 | + Option *add_option_function(std::string option_name, | ||
| 376 | + const std::function<bool(const T &)> &func, ///< the callback to execute | ||
| 377 | + std::string description = "") { | ||
| 378 | + | ||
| 379 | + CLI::callback_t fun = [func](CLI::results_t res) { | ||
| 380 | + T variable; | ||
| 381 | + bool result = detail::lexical_cast(res[0], variable); | ||
| 382 | + if(result) { | ||
| 383 | + return func(variable); | ||
| 384 | + } | ||
| 385 | + return result; | ||
| 386 | + }; | ||
| 387 | + | ||
| 388 | + Option *opt = add_option(option_name, std::move(fun), description, false); | ||
| 389 | + opt->type_name(detail::type_name<T>()); | ||
| 390 | + return opt; | ||
| 391 | + } | ||
| 392 | + | ||
| 373 | /// Add option for non-vectors with a default print | 393 | /// Add option for non-vectors with a default print |
| 374 | template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> | 394 | template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> |
| 375 | Option *add_option(std::string option_name, | 395 | Option *add_option(std::string option_name, |
| @@ -434,6 +454,31 @@ class App { | @@ -434,6 +454,31 @@ class App { | ||
| 434 | return opt; | 454 | return opt; |
| 435 | } | 455 | } |
| 436 | 456 | ||
| 457 | + /// Add option for a vector callback of a specific type | ||
| 458 | + template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | ||
| 459 | + Option *add_option_function(std::string option_name, | ||
| 460 | + const std::function<bool(const T &)> &func, ///< the callback to execute | ||
| 461 | + std::string description = "") { | ||
| 462 | + | ||
| 463 | + CLI::callback_t fun = [func](CLI::results_t res) { | ||
| 464 | + T values; | ||
| 465 | + bool retval = true; | ||
| 466 | + values.reserve(res.size()); | ||
| 467 | + for(const auto &a : res) { | ||
| 468 | + values.emplace_back(); | ||
| 469 | + retval &= detail::lexical_cast(a, values.back()); | ||
| 470 | + } | ||
| 471 | + if(retval) { | ||
| 472 | + return func(values); | ||
| 473 | + } | ||
| 474 | + return retval; | ||
| 475 | + }; | ||
| 476 | + | ||
| 477 | + Option *opt = add_option(option_name, std::move(fun), description, false); | ||
| 478 | + opt->type_name(detail::type_name<T>())->type_size(-1); | ||
| 479 | + return opt; | ||
| 480 | + } | ||
| 481 | + | ||
| 437 | /// Set a help flag, replace the existing one if present | 482 | /// Set a help flag, replace the existing one if present |
| 438 | Option *set_help_flag(std::string flag_name = "", std::string description = "") { | 483 | Option *set_help_flag(std::string flag_name = "", std::string description = "") { |
| 439 | if(help_ptr_ != nullptr) { | 484 | if(help_ptr_ != nullptr) { |
| @@ -559,7 +604,7 @@ class App { | @@ -559,7 +604,7 @@ class App { | ||
| 559 | return std::find(std::begin(options), std::end(options), member) != std::end(options); | 604 | return std::find(std::begin(options), std::end(options), member) != std::end(options); |
| 560 | }; | 605 | }; |
| 561 | 606 | ||
| 562 | - Option *opt = add_option(option_name, fun, description, false); | 607 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 563 | std::string typeval = detail::type_name<T>(); | 608 | std::string typeval = detail::type_name<T>(); |
| 564 | typeval += " in {" + detail::join(options) + "}"; | 609 | typeval += " in {" + detail::join(options) + "}"; |
| 565 | opt->type_name(typeval); | 610 | opt->type_name(typeval); |
| @@ -581,7 +626,7 @@ class App { | @@ -581,7 +626,7 @@ class App { | ||
| 581 | return std::find(std::begin(options), std::end(options), member) != std::end(options); | 626 | return std::find(std::begin(options), std::end(options), member) != std::end(options); |
| 582 | }; | 627 | }; |
| 583 | 628 | ||
| 584 | - Option *opt = add_option(option_name, fun, description, false); | 629 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 585 | opt->type_name_fn( | 630 | opt->type_name_fn( |
| 586 | [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); | 631 | [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); |
| 587 | 632 | ||
| @@ -604,7 +649,7 @@ class App { | @@ -604,7 +649,7 @@ class App { | ||
| 604 | return std::find(std::begin(options), std::end(options), member) != std::end(options); | 649 | return std::find(std::begin(options), std::end(options), member) != std::end(options); |
| 605 | }; | 650 | }; |
| 606 | 651 | ||
| 607 | - Option *opt = add_option(option_name, fun, description, defaulted); | 652 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 608 | std::string typeval = detail::type_name<T>(); | 653 | std::string typeval = detail::type_name<T>(); |
| 609 | typeval += " in {" + detail::join(options) + "}"; | 654 | typeval += " in {" + detail::join(options) + "}"; |
| 610 | opt->type_name(typeval); | 655 | opt->type_name(typeval); |
| @@ -632,7 +677,7 @@ class App { | @@ -632,7 +677,7 @@ class App { | ||
| 632 | return std::find(std::begin(options), std::end(options), member) != std::end(options); | 677 | return std::find(std::begin(options), std::end(options), member) != std::end(options); |
| 633 | }; | 678 | }; |
| 634 | 679 | ||
| 635 | - Option *opt = add_option(option_name, fun, description, defaulted); | 680 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 636 | opt->type_name_fn( | 681 | opt->type_name_fn( |
| 637 | [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); | 682 | [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); |
| 638 | if(defaulted) { | 683 | if(defaulted) { |
| @@ -663,7 +708,7 @@ class App { | @@ -663,7 +708,7 @@ class App { | ||
| 663 | } | 708 | } |
| 664 | }; | 709 | }; |
| 665 | 710 | ||
| 666 | - Option *opt = add_option(option_name, fun, description, false); | 711 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 667 | std::string typeval = detail::type_name<std::string>(); | 712 | std::string typeval = detail::type_name<std::string>(); |
| 668 | typeval += " in {" + detail::join(options) + "}"; | 713 | typeval += " in {" + detail::join(options) + "}"; |
| 669 | opt->type_name(typeval); | 714 | opt->type_name(typeval); |
| @@ -692,7 +737,7 @@ class App { | @@ -692,7 +737,7 @@ class App { | ||
| 692 | } | 737 | } |
| 693 | }; | 738 | }; |
| 694 | 739 | ||
| 695 | - Option *opt = add_option(option_name, fun, description, false); | 740 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 696 | opt->type_name_fn([&options]() { | 741 | opt->type_name_fn([&options]() { |
| 697 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; | 742 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 698 | }); | 743 | }); |
| @@ -721,7 +766,7 @@ class App { | @@ -721,7 +766,7 @@ class App { | ||
| 721 | } | 766 | } |
| 722 | }; | 767 | }; |
| 723 | 768 | ||
| 724 | - Option *opt = add_option(option_name, fun, description, defaulted); | 769 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 725 | std::string typeval = detail::type_name<std::string>(); | 770 | std::string typeval = detail::type_name<std::string>(); |
| 726 | typeval += " in {" + detail::join(options) + "}"; | 771 | typeval += " in {" + detail::join(options) + "}"; |
| 727 | opt->type_name(typeval); | 772 | opt->type_name(typeval); |
| @@ -752,7 +797,7 @@ class App { | @@ -752,7 +797,7 @@ class App { | ||
| 752 | } | 797 | } |
| 753 | }; | 798 | }; |
| 754 | 799 | ||
| 755 | - Option *opt = add_option(option_name, fun, description, defaulted); | 800 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 756 | opt->type_name_fn([&options]() { | 801 | opt->type_name_fn([&options]() { |
| 757 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; | 802 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 758 | }); | 803 | }); |
| @@ -782,7 +827,7 @@ class App { | @@ -782,7 +827,7 @@ class App { | ||
| 782 | } | 827 | } |
| 783 | }; | 828 | }; |
| 784 | 829 | ||
| 785 | - Option *opt = add_option(option_name, fun, description, false); | 830 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 786 | std::string typeval = detail::type_name<std::string>(); | 831 | std::string typeval = detail::type_name<std::string>(); |
| 787 | typeval += " in {" + detail::join(options) + "}"; | 832 | typeval += " in {" + detail::join(options) + "}"; |
| 788 | opt->type_name(typeval); | 833 | opt->type_name(typeval); |
| @@ -811,7 +856,7 @@ class App { | @@ -811,7 +856,7 @@ class App { | ||
| 811 | } | 856 | } |
| 812 | }; | 857 | }; |
| 813 | 858 | ||
| 814 | - Option *opt = add_option(option_name, fun, description, false); | 859 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 815 | opt->type_name_fn([&options]() { | 860 | opt->type_name_fn([&options]() { |
| 816 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; | 861 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 817 | }); | 862 | }); |
| @@ -840,7 +885,7 @@ class App { | @@ -840,7 +885,7 @@ class App { | ||
| 840 | } | 885 | } |
| 841 | }; | 886 | }; |
| 842 | 887 | ||
| 843 | - Option *opt = add_option(option_name, fun, description, defaulted); | 888 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 844 | std::string typeval = detail::type_name<std::string>(); | 889 | std::string typeval = detail::type_name<std::string>(); |
| 845 | typeval += " in {" + detail::join(options) + "}"; | 890 | typeval += " in {" + detail::join(options) + "}"; |
| 846 | opt->type_name(typeval); | 891 | opt->type_name(typeval); |
| @@ -872,7 +917,7 @@ class App { | @@ -872,7 +917,7 @@ class App { | ||
| 872 | } | 917 | } |
| 873 | }; | 918 | }; |
| 874 | 919 | ||
| 875 | - Option *opt = add_option(option_name, fun, description, defaulted); | 920 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 876 | opt->type_name_fn([&options]() { | 921 | opt->type_name_fn([&options]() { |
| 877 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; | 922 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 878 | }); | 923 | }); |
| @@ -902,7 +947,7 @@ class App { | @@ -902,7 +947,7 @@ class App { | ||
| 902 | } | 947 | } |
| 903 | }; | 948 | }; |
| 904 | 949 | ||
| 905 | - Option *opt = add_option(option_name, fun, description, false); | 950 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 906 | std::string typeval = detail::type_name<std::string>(); | 951 | std::string typeval = detail::type_name<std::string>(); |
| 907 | typeval += " in {" + detail::join(options) + "}"; | 952 | typeval += " in {" + detail::join(options) + "}"; |
| 908 | opt->type_name(typeval); | 953 | opt->type_name(typeval); |
| @@ -931,7 +976,7 @@ class App { | @@ -931,7 +976,7 @@ class App { | ||
| 931 | } | 976 | } |
| 932 | }; | 977 | }; |
| 933 | 978 | ||
| 934 | - Option *opt = add_option(option_name, fun, description, false); | 979 | + Option *opt = add_option(option_name, std::move(fun), description, false); |
| 935 | opt->type_name_fn([&options]() { | 980 | opt->type_name_fn([&options]() { |
| 936 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; | 981 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 937 | }); | 982 | }); |
| @@ -960,7 +1005,7 @@ class App { | @@ -960,7 +1005,7 @@ class App { | ||
| 960 | } | 1005 | } |
| 961 | }; | 1006 | }; |
| 962 | 1007 | ||
| 963 | - Option *opt = add_option(option_name, fun, description, defaulted); | 1008 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 964 | std::string typeval = detail::type_name<std::string>(); | 1009 | std::string typeval = detail::type_name<std::string>(); |
| 965 | typeval += " in {" + detail::join(options) + "}"; | 1010 | typeval += " in {" + detail::join(options) + "}"; |
| 966 | opt->type_name(typeval); | 1011 | opt->type_name(typeval); |
| @@ -992,7 +1037,7 @@ class App { | @@ -992,7 +1037,7 @@ class App { | ||
| 992 | } | 1037 | } |
| 993 | }; | 1038 | }; |
| 994 | 1039 | ||
| 995 | - Option *opt = add_option(option_name, fun, description, defaulted); | 1040 | + Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 996 | opt->type_name_fn([&options]() { | 1041 | opt->type_name_fn([&options]() { |
| 997 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; | 1042 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 998 | }); | 1043 | }); |
| @@ -1021,7 +1066,7 @@ class App { | @@ -1021,7 +1066,7 @@ class App { | ||
| 1021 | return worked; | 1066 | return worked; |
| 1022 | }; | 1067 | }; |
| 1023 | 1068 | ||
| 1024 | - CLI::Option *opt = add_option(option_name, fun, description, defaulted); | 1069 | + CLI::Option *opt = add_option(option_name, std::move(fun), description, defaulted); |
| 1025 | opt->type_name(label)->type_size(2); | 1070 | opt->type_name(label)->type_size(2); |
| 1026 | if(defaulted) { | 1071 | if(defaulted) { |
| 1027 | std::stringstream out; | 1072 | std::stringstream out; |
| @@ -1218,7 +1263,7 @@ class App { | @@ -1218,7 +1263,7 @@ class App { | ||
| 1218 | 1263 | ||
| 1219 | auto args = detail::split_up(std::move(commandline)); | 1264 | auto args = detail::split_up(std::move(commandline)); |
| 1220 | // remove all empty strings | 1265 | // remove all empty strings |
| 1221 | - args.erase(std::remove(args.begin(), args.end(), std::string()), args.end()); | 1266 | + args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); |
| 1222 | std::reverse(args.begin(), args.end()); | 1267 | std::reverse(args.begin(), args.end()); |
| 1223 | 1268 | ||
| 1224 | parse(args); | 1269 | parse(args); |
tests/AppTest.cpp
| @@ -296,6 +296,65 @@ TEST_F(TApp, OneStringAgain) { | @@ -296,6 +296,65 @@ TEST_F(TApp, OneStringAgain) { | ||
| 296 | EXPECT_EQ(str, "mystring"); | 296 | EXPECT_EQ(str, "mystring"); |
| 297 | } | 297 | } |
| 298 | 298 | ||
| 299 | +TEST_F(TApp, OneStringFunction) { | ||
| 300 | + std::string str; | ||
| 301 | + app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { | ||
| 302 | + str = val; | ||
| 303 | + return true; | ||
| 304 | + }); | ||
| 305 | + args = {"--string", "mystring"}; | ||
| 306 | + run(); | ||
| 307 | + EXPECT_EQ((size_t)1, app.count("-s")); | ||
| 308 | + EXPECT_EQ((size_t)1, app.count("--string")); | ||
| 309 | + EXPECT_EQ(str, "mystring"); | ||
| 310 | +} | ||
| 311 | + | ||
| 312 | +TEST_F(TApp, doubleFunction) { | ||
| 313 | + double res; | ||
| 314 | + app.add_option_function<double>("--val", [&res](double val) { | ||
| 315 | + res = std::abs(val + 54); | ||
| 316 | + return true; | ||
| 317 | + }); | ||
| 318 | + args = {"--val", "-354.356"}; | ||
| 319 | + run(); | ||
| 320 | + EXPECT_EQ(res, 300.356); | ||
| 321 | +} | ||
| 322 | + | ||
| 323 | +TEST_F(TApp, doubleFunctionFail) { | ||
| 324 | + double res; | ||
| 325 | + app.add_option_function<double>("--val", [&res](double val) { | ||
| 326 | + res = std::abs(val + 54); | ||
| 327 | + return true; | ||
| 328 | + }); | ||
| 329 | + args = {"--val", "not_double"}; | ||
| 330 | + EXPECT_THROW(run(), CLI::ConversionError); | ||
| 331 | +} | ||
| 332 | + | ||
| 333 | +TEST_F(TApp, doubleVectorFunction) { | ||
| 334 | + std::vector<double> res; | ||
| 335 | + app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { | ||
| 336 | + res = val; | ||
| 337 | + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | ||
| 338 | + return true; | ||
| 339 | + }); | ||
| 340 | + args = {"--val", "5", "--val", "6", "--val", "7"}; | ||
| 341 | + run(); | ||
| 342 | + EXPECT_EQ(res.size(), 3); | ||
| 343 | + EXPECT_EQ(res[0], 10.0); | ||
| 344 | + EXPECT_EQ(res[2], 12.0); | ||
| 345 | +} | ||
| 346 | + | ||
| 347 | +TEST_F(TApp, doubleVectorFunctionFail) { | ||
| 348 | + std::vector<double> res; | ||
| 349 | + app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { | ||
| 350 | + res = val; | ||
| 351 | + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); | ||
| 352 | + return true; | ||
| 353 | + }); | ||
| 354 | + args = {"--val", "five", "--val", "nine", "--val", "7"}; | ||
| 355 | + EXPECT_THROW(run(), CLI::ConversionError); | ||
| 356 | +} | ||
| 357 | + | ||
| 299 | TEST_F(TApp, DefaultStringAgain) { | 358 | TEST_F(TApp, DefaultStringAgain) { |
| 300 | std::string str = "previous"; | 359 | std::string str = "previous"; |
| 301 | app.add_option("-s,--string", str); | 360 | app.add_option("-s,--string", str); |