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 | 179 | help_string="", |
| 180 | 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 | 186 | app.add_complex(... // Special case: support for complex numbers |
| 183 | 187 | |
| 184 | 188 | app.add_flag(option_name, |
| ... | ... | @@ -208,6 +212,8 @@ App* subcom = app.add_subcommand(name, description); |
| 208 | 212 | |
| 209 | 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 | 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 | 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 | 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 | 393 | /// Add option for non-vectors with a default print |
| 374 | 394 | template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> |
| 375 | 395 | Option *add_option(std::string option_name, |
| ... | ... | @@ -434,6 +454,31 @@ class App { |
| 434 | 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 | 482 | /// Set a help flag, replace the existing one if present |
| 438 | 483 | Option *set_help_flag(std::string flag_name = "", std::string description = "") { |
| 439 | 484 | if(help_ptr_ != nullptr) { |
| ... | ... | @@ -559,7 +604,7 @@ class App { |
| 559 | 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 | 608 | std::string typeval = detail::type_name<T>(); |
| 564 | 609 | typeval += " in {" + detail::join(options) + "}"; |
| 565 | 610 | opt->type_name(typeval); |
| ... | ... | @@ -581,7 +626,7 @@ class App { |
| 581 | 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 | 630 | opt->type_name_fn( |
| 586 | 631 | [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); |
| 587 | 632 | |
| ... | ... | @@ -604,7 +649,7 @@ class App { |
| 604 | 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 | 653 | std::string typeval = detail::type_name<T>(); |
| 609 | 654 | typeval += " in {" + detail::join(options) + "}"; |
| 610 | 655 | opt->type_name(typeval); |
| ... | ... | @@ -632,7 +677,7 @@ class App { |
| 632 | 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 | 681 | opt->type_name_fn( |
| 637 | 682 | [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); |
| 638 | 683 | if(defaulted) { |
| ... | ... | @@ -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 | 712 | std::string typeval = detail::type_name<std::string>(); |
| 668 | 713 | typeval += " in {" + detail::join(options) + "}"; |
| 669 | 714 | opt->type_name(typeval); |
| ... | ... | @@ -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 | 741 | opt->type_name_fn([&options]() { |
| 697 | 742 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 698 | 743 | }); |
| ... | ... | @@ -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 | 770 | std::string typeval = detail::type_name<std::string>(); |
| 726 | 771 | typeval += " in {" + detail::join(options) + "}"; |
| 727 | 772 | opt->type_name(typeval); |
| ... | ... | @@ -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 | 801 | opt->type_name_fn([&options]() { |
| 757 | 802 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 758 | 803 | }); |
| ... | ... | @@ -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 | 831 | std::string typeval = detail::type_name<std::string>(); |
| 787 | 832 | typeval += " in {" + detail::join(options) + "}"; |
| 788 | 833 | opt->type_name(typeval); |
| ... | ... | @@ -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 | 860 | opt->type_name_fn([&options]() { |
| 816 | 861 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 817 | 862 | }); |
| ... | ... | @@ -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 | 889 | std::string typeval = detail::type_name<std::string>(); |
| 845 | 890 | typeval += " in {" + detail::join(options) + "}"; |
| 846 | 891 | opt->type_name(typeval); |
| ... | ... | @@ -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 | 921 | opt->type_name_fn([&options]() { |
| 877 | 922 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 878 | 923 | }); |
| ... | ... | @@ -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 | 951 | std::string typeval = detail::type_name<std::string>(); |
| 907 | 952 | typeval += " in {" + detail::join(options) + "}"; |
| 908 | 953 | opt->type_name(typeval); |
| ... | ... | @@ -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 | 980 | opt->type_name_fn([&options]() { |
| 936 | 981 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 937 | 982 | }); |
| ... | ... | @@ -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 | 1009 | std::string typeval = detail::type_name<std::string>(); |
| 965 | 1010 | typeval += " in {" + detail::join(options) + "}"; |
| 966 | 1011 | opt->type_name(typeval); |
| ... | ... | @@ -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 | 1041 | opt->type_name_fn([&options]() { |
| 997 | 1042 | return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; |
| 998 | 1043 | }); |
| ... | ... | @@ -1021,7 +1066,7 @@ class App { |
| 1021 | 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 | 1070 | opt->type_name(label)->type_size(2); |
| 1026 | 1071 | if(defaulted) { |
| 1027 | 1072 | std::stringstream out; |
| ... | ... | @@ -1218,7 +1263,7 @@ class App { |
| 1218 | 1263 | |
| 1219 | 1264 | auto args = detail::split_up(std::move(commandline)); |
| 1220 | 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 | 1267 | std::reverse(args.begin(), args.end()); |
| 1223 | 1268 | |
| 1224 | 1269 | parse(args); | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -296,6 +296,65 @@ TEST_F(TApp, OneStringAgain) { |
| 296 | 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 | 358 | TEST_F(TApp, DefaultStringAgain) { |
| 300 | 359 | std::string str = "previous"; |
| 301 | 360 | app.add_option("-s,--string", str); | ... | ... |