Commit 1a6ed01d87b61903b33c2a03f276869eb366003c

Authored by Philip Top
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
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);
... ...