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,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);