From 1a6ed01d87b61903b33c2a03f276869eb366003c Mon Sep 17 00:00:00 2001 From: Philip Top Date: Wed, 23 Jan 2019 06:51:40 -0800 Subject: [PATCH] add some additional tests for the failure case, and update the README with documentation on the new function. --- README.md | 6 ++++++ include/CLI/App.hpp | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ tests/AppTest.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c9b939c..2f58802 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,10 @@ app.add_option(option_name, help_string="", default=false) +app.add_option_function(option_name, + function , // int, float, vector, or string-like + help_string="") + app.add_complex(... // Special case: support for complex numbers app.add_flag(option_name, @@ -208,6 +212,8 @@ App* subcom = app.add_subcommand(name, description); 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. +The `add_option_function(...` 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 + 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. 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. diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index eb8f45a..4c1b258 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -370,6 +370,26 @@ class App { return opt; } + /// Add option for a callback of a specific type + template ::value, detail::enabler> = detail::dummy> + Option *add_option_function(std::string option_name, + const std::function &func, ///< the callback to execute + std::string description = "") { + + CLI::callback_t fun = [func](CLI::results_t res) { + T variable; + bool result = detail::lexical_cast(res[0], variable); + if(result) { + return func(variable); + } + return result; + }; + + Option *opt = add_option(option_name, std::move(fun), description, false); + opt->type_name(detail::type_name()); + return opt; + } + /// Add option for non-vectors with a default print template ::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, @@ -434,6 +454,31 @@ class App { return opt; } + /// Add option for a vector callback of a specific type + template ::value, detail::enabler> = detail::dummy> + Option *add_option_function(std::string option_name, + const std::function &func, ///< the callback to execute + std::string description = "") { + + CLI::callback_t fun = [func](CLI::results_t res) { + T values; + bool retval = true; + values.reserve(res.size()); + for(const auto &a : res) { + values.emplace_back(); + retval &= detail::lexical_cast(a, values.back()); + } + if(retval) { + return func(values); + } + return retval; + }; + + Option *opt = add_option(option_name, std::move(fun), description, false); + opt->type_name(detail::type_name())->type_size(-1); + return opt; + } + /// Set a help flag, replace the existing one if present Option *set_help_flag(std::string flag_name = "", std::string description = "") { if(help_ptr_ != nullptr) { @@ -559,7 +604,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -581,7 +626,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); opt->type_name_fn( [&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -604,7 +649,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -632,7 +677,7 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); opt->type_name_fn( [&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); if(defaulted) { @@ -663,7 +708,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -692,7 +737,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -721,7 +766,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -752,7 +797,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -782,7 +827,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -811,7 +856,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -840,7 +885,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -872,7 +917,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -902,7 +947,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -931,7 +976,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, false); + Option *opt = add_option(option_name, std::move(fun), description, false); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -960,7 +1005,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; opt->type_name(typeval); @@ -992,7 +1037,7 @@ class App { } }; - Option *opt = add_option(option_name, fun, description, defaulted); + Option *opt = add_option(option_name, std::move(fun), description, defaulted); opt->type_name_fn([&options]() { return std::string(detail::type_name()) + " in {" + detail::join(options) + "}"; }); @@ -1021,7 +1066,7 @@ class App { return worked; }; - CLI::Option *opt = add_option(option_name, fun, description, defaulted); + CLI::Option *opt = add_option(option_name, std::move(fun), description, defaulted); opt->type_name(label)->type_size(2); if(defaulted) { std::stringstream out; @@ -1218,7 +1263,7 @@ class App { auto args = detail::split_up(std::move(commandline)); // remove all empty strings - args.erase(std::remove(args.begin(), args.end(), std::string()), args.end()); + args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); std::reverse(args.begin(), args.end()); parse(args); diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 7461cdf..b40f147 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -296,6 +296,65 @@ TEST_F(TApp, OneStringAgain) { EXPECT_EQ(str, "mystring"); } +TEST_F(TApp, OneStringFunction) { + std::string str; + app.add_option_function("-s,--string", [&str](const std::string &val) { + str = val; + return true; + }); + args = {"--string", "mystring"}; + run(); + EXPECT_EQ((size_t)1, app.count("-s")); + EXPECT_EQ((size_t)1, app.count("--string")); + EXPECT_EQ(str, "mystring"); +} + +TEST_F(TApp, doubleFunction) { + double res; + app.add_option_function("--val", [&res](double val) { + res = std::abs(val + 54); + return true; + }); + args = {"--val", "-354.356"}; + run(); + EXPECT_EQ(res, 300.356); +} + +TEST_F(TApp, doubleFunctionFail) { + double res; + app.add_option_function("--val", [&res](double val) { + res = std::abs(val + 54); + return true; + }); + args = {"--val", "not_double"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + +TEST_F(TApp, doubleVectorFunction) { + std::vector res; + app.add_option_function>("--val", [&res](const std::vector &val) { + res = val; + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); + return true; + }); + args = {"--val", "5", "--val", "6", "--val", "7"}; + run(); + EXPECT_EQ(res.size(), 3); + EXPECT_EQ(res[0], 10.0); + EXPECT_EQ(res[2], 12.0); +} + +TEST_F(TApp, doubleVectorFunctionFail) { + std::vector res; + app.add_option_function>("--val", [&res](const std::vector &val) { + res = val; + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); + return true; + }); + args = {"--val", "five", "--val", "nine", "--val", "7"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + TEST_F(TApp, DefaultStringAgain) { std::string str = "previous"; app.add_option("-s,--string", str); -- libgit2 0.21.4