From b734e80790db92640c3b00828f8eeac2916328c1 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Wed, 20 Feb 2019 06:10:04 -0800 Subject: [PATCH] add some notes about enums in the readme add some helpers tests for enumerations Add better enum support in the library --- README.md | 4 ++-- examples/enum.cpp | 4 +--- include/CLI/ConfigFwd.hpp | 6 +++--- include/CLI/StringTools.hpp | 17 ++++++++++++++++- include/CLI/TypeTools.hpp | 24 ++++++++++++++++++++++-- tests/HelpersTest.cpp | 23 +++++++++++++++++++++++ 6 files changed, 67 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c4ac160c9..fc3aa5511 100644 --- a/README.md +++ b/README.md @@ -178,12 +178,12 @@ While all options internally are the same type, there are several ways to add an ```cpp app.add_option(option_name, - variable_to_bind_to, // bool, int, float, vector, or string-like + variable_to_bind_to, // bool, int, float, vector, enum, or string-like help_string="", default=false) app.add_option_function(option_name, - function , // int, float, vector, or string-like + function , // int, float, enum, vector, or string-like help_string="") app.add_complex(... // Special case: support for complex numbers diff --git a/examples/enum.cpp b/examples/enum.cpp index 19b92fbde..b61aa4880 100644 --- a/examples/enum.cpp +++ b/examples/enum.cpp @@ -1,15 +1,13 @@ #include #include -#include enum class Level : int { High, Medium, Low }; int main(int argc, char **argv) { CLI::App app; - std::map map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; - Level level; + std::map map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; app.add_option("-l,--level", level, "Level settings") ->required() diff --git a/include/CLI/ConfigFwd.hpp b/include/CLI/ConfigFwd.hpp index 31068ae3f..4044e36c7 100644 --- a/include/CLI/ConfigFwd.hpp +++ b/include/CLI/ConfigFwd.hpp @@ -27,10 +27,10 @@ inline std::string ini_join(std::vector args) { auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); if(it == arg.end()) s << arg; - else if(arg.find(R"(")") == std::string::npos) - s << R"(")" << arg << R"(")"; + else if(arg.find_first_of('\"') == std::string::npos) + s << '\"' << arg << '\"'; else - s << R"(')" << arg << R"(')"; + s << '\'' << arg << '\''; } return s.str(); diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp index 499295666..7c38ba7bf 100644 --- a/include/CLI/StringTools.hpp +++ b/include/CLI/StringTools.hpp @@ -46,7 +46,7 @@ inline std::vector split(const std::string &s, char delim) { std::vector elems; // Check to see if empty string, give consistent result if(s.empty()) - elems.emplace_back(""); + elems.emplace_back(); else { std::stringstream ss; ss.str(s); @@ -70,6 +70,21 @@ template std::string join(const T &v, std::string delim = ",") { return s.str(); } +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << func(i); + } + return s.str(); +} + /// Join a string in reverse order template std::string rjoin(const T &v, std::string delim = ",") { std::ostringstream s; diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 6904eb2ae..6f6f1fb18 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -3,6 +3,7 @@ // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/CLIUtils/CLI11 for details. +#include "StringTools.hpp" #include #include #include @@ -140,9 +141,16 @@ template ::value, detail::enabler> = detail constexpr const char *type_name() { return "VECTOR"; } +/// Print name for enumeration types +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} +/// Print for all other types template ::value && !std::is_integral::value && !is_vector::value, + enable_if_t::value && !std::is_integral::value && !is_vector::value && + !std::is_enum::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "TEXT"; @@ -229,10 +237,22 @@ bool lexical_cast(std::string input, T &output) { return true; } +/// enumerations +template ::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + typename std::underlying_type::type val; + bool retval = detail::lexical_cast(input, val); + if(!retval) { + return false; + } + output = static_cast(val); + return true; +} + /// Non-string parsable template ::value && !std::is_integral::value && - !std::is_assignable::value, + !std::is_assignable::value && !std::is_enum::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { std::istringstream is; diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index e4b3d013d..f01a81f0c 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -527,6 +527,10 @@ TEST(Types, TypeName) { std::string text2_name = CLI::detail::type_name(); EXPECT_EQ("TEXT", text2_name); + + enum class test { test1, test2, test3 }; + std::string enum_name = CLI::detail::type_name(); + EXPECT_EQ("ENUM", text2_name); } TEST(Types, OverflowSmall) { @@ -617,6 +621,25 @@ TEST(Types, LexicalCastParsable) { EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); } +TEST(Types, LexicalCastEnum) { + enum t1 : char { v1 = 5, v3 = 7, v5 = -9 }; + + t1 output; + EXPECT_TRUE(CLI::detail::lexical_cast("-9", output)); + EXPECT_EQ(output, v5); + + EXPECT_FALSE(CLI::detail::lexical_cast("invalid", output)); + enum class t2 : uint64_t { enum1 = 65, enum2 = 45667, enum3 = 9999999999999 }; + t2 output2; + EXPECT_TRUE(CLI::detail::lexical_cast("65", output2)); + EXPECT_EQ(output2, t2::enum1); + + EXPECT_FALSE(CLI::detail::lexical_cast("invalid", output2)); + + EXPECT_TRUE(CLI::detail::lexical_cast("9999999999999", output2)); + EXPECT_EQ(output2, t2::enum3); +} + TEST(FixNewLines, BasicCheck) { std::string input = "one\ntwo"; std::string output = "one\n; two";