From e8fd26824691d0a25b57930e68c126ffc74f791e Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 2 Jun 2017 11:33:03 -0400 Subject: [PATCH 1/6] Adding enum support (basic only) --- include/CLI/TypeTools.hpp | 10 +++++++--- tests/AppTest.cpp | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 475e2fa2..e43d1e2c 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -74,8 +74,10 @@ constexpr const char *type_name() { // Lexical cast -/// Integers -template ::value, detail::enabler> = detail::dummy> +/// Integers / enums +template ::value + || std::is_enum::value + , detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { output = static_cast(std::stoll(input)); @@ -103,7 +105,9 @@ bool lexical_cast(std::string input, T &output) { /// String and similar template < typename T, - enable_if_t::value && !std::is_integral::value, detail::enabler> = detail::dummy> + enable_if_t::value + && !std::is_integral::value + && !std::is_enum::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { output = input; return true; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index ca19f33a..8e3922a9 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -203,6 +203,22 @@ TEST_F(TApp, DefaultOpts) { EXPECT_EQ("9", s); } +TEST_F(TApp, EnumTest) { + enum Level : std::int32_t { + High, + Medium, + Low + }; + Level level = Level::Low; + app.add_option("--level", level); + + args = {"--level", "1"}; + run(); + EXPECT_EQ(level, Level::Medium); +} + +// New style enums do not work, since << is not supported. Could be fixed without changing API by duplicating the `add_` methods with and without the extra flag. + TEST_F(TApp, RequiredFlags) { app.add_flag("-a")->required(); app.add_flag("-b")->mandatory(); // Alternate term From 081bb93b005ea9f90e5b4a2f2fc37bde27c463da Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 2 Jun 2017 11:50:35 -0400 Subject: [PATCH 2/6] Adding recent addition to Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a1a17d..ce1f6fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Version 1.1 (in progress) +* Added support for basic enumerations [#12](https://github.com/CLIUtils/CLI11/issues/12) + ## Version 1.0 * Cleanup using `clang-tidy` and `clang-format` * Small improvements to Timers, easier to subclass Error From f7cf890543693e5cb8653f61ad8910861e2a1223 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 2 Jun 2017 12:06:04 -0400 Subject: [PATCH 3/6] Adding new style enums too --- include/CLI/App.hpp | 118 +++++++++++++++++++++++++++++++++++++++----- tests/AppTest.cpp | 15 +++++- 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index bae90197..a76b6f3f 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -213,13 +213,14 @@ class App { } else throw OptionAlreadyAdded(myopt.get_name()); } + + - /// Add option for non-vectors + /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) template ::value, detail::enabler> = detail::dummy> Option *add_option(std::string name, T &variable, ///< The variable to set - std::string description = "", - bool defaulted = false) { + std::string description = "") { CLI::callback_t fun = [&variable](CLI::results_t res) { if(res.size() != 1) @@ -227,6 +228,24 @@ class App { return detail::lexical_cast(res[0], variable); }; + Option *opt = add_option(name, fun, description, false); + opt->set_custom_option(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 name, + T &variable, ///< The variable to set + std::string description, + bool defaulted) { + + CLI::callback_t fun = [&variable](CLI::results_t res) { + if(res.size() != 1) + return false; + return detail::lexical_cast(res[0], variable); + }; + Option *opt = add_option(name, fun, description, defaulted); opt->set_custom_option(detail::type_name()); if(defaulted) { @@ -236,13 +255,34 @@ class App { } return opt; } - + + /// Add option for vectors (no default) + template + Option *add_option(std::string name, + std::vector &variable, ///< The variable vector to set + std::string description = "") { + + CLI::callback_t fun = [&variable](CLI::results_t res) { + bool retval = true; + variable.clear(); + for(const auto &a : res) { + variable.emplace_back(); + retval &= detail::lexical_cast(a, variable.back()); + } + return variable.size() > 0 && retval; + }; + + Option *opt = add_option(name, fun, description, false); + opt->set_custom_option(detail::type_name(), -1, true); + return opt; + } + /// Add option for vectors template Option *add_option(std::string name, std::vector &variable, ///< The variable vector to set - std::string description = "", - bool defaulted = false) { + std::string description, + bool defaulted) { CLI::callback_t fun = [&variable](CLI::results_t res) { bool retval = true; @@ -311,13 +351,12 @@ class App { return opt; } - /// Add set of options + /// Add set of options (No default) template Option *add_set(std::string name, T &member, ///< The selected member of the set std::set options, ///< The set of posibilities - std::string description = "", - bool defaulted = false) { + std::string description = "") { CLI::callback_t fun = [&member, options](CLI::results_t res) { if(res.size() != 1) { @@ -329,6 +368,31 @@ class App { return std::find(std::begin(options), std::end(options), member) != std::end(options); }; + Option *opt = add_option(name, fun, description, false); + std::string typeval = detail::type_name(); + typeval += " in {" + detail::join(options) + "}"; + opt->set_custom_option(typeval); + return opt; + } + + /// Add set of options + template + Option *add_set(std::string name, + T &member, ///< The selected member of the set + std::set options, ///< The set of posibilities + std::string description, + bool defaulted) { + + CLI::callback_t fun = [&member, options](CLI::results_t res) { + if(res.size() != 1) { + return false; + } + bool retval = detail::lexical_cast(res[0], member); + if(!retval) + return false; + return std::find(std::begin(options), std::end(options), member) != std::end(options); + }; + Option *opt = add_option(name, fun, description, defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; @@ -341,12 +405,11 @@ class App { return opt; } - /// Add set of options, string only, ignore case + /// Add set of options, string only, ignore case (no default) Option *add_set_ignore_case(std::string name, std::string &member, ///< The selected member of the set std::set options, ///< The set of posibilities - std::string description = "", - bool defaulted = false) { + std::string description = "") { CLI::callback_t fun = [&member, options](CLI::results_t res) { if(res.size() != 1) { @@ -364,6 +427,37 @@ class App { } }; + Option *opt = add_option(name, fun, description, false); + std::string typeval = detail::type_name(); + typeval += " in {" + detail::join(options) + "}"; + opt->set_custom_option(typeval); + + return opt; + } + + /// Add set of options, string only, ignore case + Option *add_set_ignore_case(std::string name, + std::string &member, ///< The selected member of the set + std::set options, ///< The set of posibilities + std::string description, + bool defaulted) { + + CLI::callback_t fun = [&member, options](CLI::results_t res) { + if(res.size() != 1) { + return false; + } + member = detail::to_lower(res[0]); + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { + return detail::to_lower(val) == member; + }); + if(iter == std::end(options)) + return false; + else { + member = *iter; + return true; + } + }; + Option *opt = add_option(name, fun, description, defaulted); std::string typeval = detail::type_name(); typeval += " in {" + detail::join(options) + "}"; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 8e3922a9..db8900da 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -217,7 +217,20 @@ TEST_F(TApp, EnumTest) { EXPECT_EQ(level, Level::Medium); } -// New style enums do not work, since << is not supported. Could be fixed without changing API by duplicating the `add_` methods with and without the extra flag. +TEST_F(TApp, NewEnumTest) { + enum class Level2 : std::int32_t { + High, + Medium, + Low + }; + Level2 level = Level2::Low; + app.add_option("--level", level); + + args = {"--level", "1"}; + run(); + EXPECT_EQ(level, Level2::Medium); +} + TEST_F(TApp, RequiredFlags) { app.add_flag("-a")->required(); From da5e8ee4a982d889e55ffd1b19c2b48c4aea198e Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 5 Jun 2017 09:10:04 -0400 Subject: [PATCH 4/6] Adding a few more tests --- include/CLI/App.hpp | 2 +- tests/AppTest.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index a58cae06..b9dc4f63 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -281,7 +281,7 @@ class App { variable.emplace_back(); retval &= detail::lexical_cast(a, variable.back()); } - return variable.size() > 0 && retval; + return (!variable.empty()) && retval; }; Option *opt = add_option(name, fun, description, false); diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 779e164e..5990e99b 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -420,6 +420,47 @@ TEST_F(TApp, InSet) { EXPECT_THROW(run(), CLI::ConversionError); } +TEST_F(TApp, InSetWithDefault) { + + std::string choice = "one"; + app.add_set("-q,--quick", choice, {"one", "two", "three"}, "", true); + + run(); + EXPECT_EQ("one", choice); + app.reset(); + + args = {"--quick", "two"}; + + run(); + EXPECT_EQ("two", choice); + + app.reset(); + + args = {"--quick", "four"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + + +TEST_F(TApp, InCaselessSetWithDefault) { + + std::string choice = "one"; + app.add_set_ignore_case("-q,--quick", choice, {"one", "two", "three"}, "", true); + + run(); + EXPECT_EQ("one", choice); + app.reset(); + + args = {"--quick", "tWo"}; + + run(); + EXPECT_EQ("two", choice); + + app.reset(); + + args = {"--quick", "four"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + TEST_F(TApp, InIntSet) { int choice; @@ -491,6 +532,20 @@ TEST_F(TApp, VectorFixedString) { EXPECT_EQ(answer, strvec); } + +TEST_F(TApp, VectorDefaultedFixedString) { + std::vector strvec{"one"}; + std::vector answer{"mystring", "mystring2", "mystring3"}; + + CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3); + EXPECT_EQ(3, opt->get_expected()); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ((size_t)3, app.count("--string")); + EXPECT_EQ(answer, strvec); +} + TEST_F(TApp, VectorUnlimString) { std::vector strvec; std::vector answer{"mystring", "mystring2", "mystring3"}; From 3da480792b18f0debd181270372cb2d560494a52 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 9 Jun 2017 08:31:47 -0400 Subject: [PATCH 5/6] Adding enum example --- examples/CMakeLists.txt | 1 + examples/enum.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 examples/enum.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1b6f2abc..68124dbc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,3 +19,4 @@ add_cli_exe(subcommands subcommands.cpp) add_cli_exe(groups groups.cpp) add_cli_exe(inter_argument_order inter_argument_order.cpp) add_cli_exe(prefix_command prefix_command.cpp) +add_cli_exe(enum enum.cpp) diff --git a/examples/enum.cpp b/examples/enum.cpp new file mode 100644 index 00000000..ce97db39 --- /dev/null +++ b/examples/enum.cpp @@ -0,0 +1,23 @@ +#include + +enum Level : std::int32_t { + High, + Medium, + Low +}; + +int main(int argc, char** argv) { + CLI::App app; + + Level level; + app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings") + ->set_type_name("enum/Level in {High=0, Medium=1, Low=2}"); + + try { + app.parse(argc, argv); + } catch (CLI::Error const& e) { + app.exit(e); + } + return 0; +} + From 0973f348d2f652f04f26e90fe7a2657ec60c476f Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 9 Jun 2017 13:36:43 -0400 Subject: [PATCH 6/6] Adding new tests for red lines --- tests/AppTest.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 5990e99b..dd9a3684 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -892,3 +892,43 @@ TEST_F(TApp, CheckSubcomFail) { EXPECT_THROW(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError); } + +// Added to test defaults on dual method +TEST_F(TApp, OptionWithDefaults) { + int someint=2; + app.add_option("-a", someint, "", true); + + args = {"-a1", "-a2"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +// Added to test defaults on dual method +TEST_F(TApp, SetWithDefaults) { + int someint=2; + app.add_set("-a", someint, {1,2,3,4}, "", true); + + args = {"-a1", "-a2"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +// Added to test defaults on dual method +TEST_F(TApp, SetWithDefaultsConversion) { + int someint=2; + app.add_set("-a", someint, {1,2,3,4}, "", true); + + args = {"-a", "hi"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +// Added to test defaults on dual method +TEST_F(TApp, SetWithDefaultsIC) { + std::string someint="ho"; + app.add_set_ignore_case("-a", someint, {"Hi", "Ho"}, "", true); + + args = {"-aHi", "-aHo"}; + + EXPECT_THROW(run(), CLI::ConversionError); +}