Commit 6c645b55a117ad5035aa628c47bafd24a05b2a63
Committed by
Henry Schreiner
1 parent
571fb07c
Add better enum support in the library (#233)
* add some notes about enums in the readme add some helpers tests for enumerations Add better enum support in the library * fix Helpers Test for Enums
Showing
6 changed files
with
67 additions
and
11 deletions
README.md
| @@ -178,12 +178,12 @@ While all options internally are the same type, there are several ways to add an | @@ -178,12 +178,12 @@ While all options internally are the same type, there are several ways to add an | ||
| 178 | 178 | ||
| 179 | ```cpp | 179 | ```cpp |
| 180 | app.add_option(option_name, | 180 | app.add_option(option_name, |
| 181 | - variable_to_bind_to, // bool, int, float, vector, or string-like | 181 | + variable_to_bind_to, // bool, int, float, vector, enum, or string-like |
| 182 | help_string="", | 182 | help_string="", |
| 183 | default=false) | 183 | default=false) |
| 184 | 184 | ||
| 185 | app.add_option_function<type>(option_name, | 185 | app.add_option_function<type>(option_name, |
| 186 | - function <void(const type &value)>, // int, float, vector, or string-like | 186 | + function <void(const type &value)>, // int, float, enum, vector, or string-like |
| 187 | help_string="") | 187 | help_string="") |
| 188 | 188 | ||
| 189 | app.add_complex(... // Special case: support for complex numbers | 189 | app.add_complex(... // Special case: support for complex numbers |
examples/enum.cpp
| 1 | #include <CLI/CLI.hpp> | 1 | #include <CLI/CLI.hpp> |
| 2 | #include <map> | 2 | #include <map> |
| 3 | -#include <sstream> | ||
| 4 | 3 | ||
| 5 | enum class Level : int { High, Medium, Low }; | 4 | enum class Level : int { High, Medium, Low }; |
| 6 | 5 | ||
| 7 | int main(int argc, char **argv) { | 6 | int main(int argc, char **argv) { |
| 8 | CLI::App app; | 7 | CLI::App app; |
| 9 | 8 | ||
| 10 | - std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; | ||
| 11 | - | ||
| 12 | Level level; | 9 | Level level; |
| 10 | + std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; | ||
| 13 | 11 | ||
| 14 | app.add_option("-l,--level", level, "Level settings") | 12 | app.add_option("-l,--level", level, "Level settings") |
| 15 | ->required() | 13 | ->required() |
include/CLI/ConfigFwd.hpp
| @@ -27,10 +27,10 @@ inline std::string ini_join(std::vector<std::string> args) { | @@ -27,10 +27,10 @@ inline std::string ini_join(std::vector<std::string> args) { | ||
| 27 | auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); }); | 27 | auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); }); |
| 28 | if(it == arg.end()) | 28 | if(it == arg.end()) |
| 29 | s << arg; | 29 | s << arg; |
| 30 | - else if(arg.find(R"(")") == std::string::npos) | ||
| 31 | - s << R"(")" << arg << R"(")"; | 30 | + else if(arg.find_first_of('\"') == std::string::npos) |
| 31 | + s << '\"' << arg << '\"'; | ||
| 32 | else | 32 | else |
| 33 | - s << R"(')" << arg << R"(')"; | 33 | + s << '\'' << arg << '\''; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | return s.str(); | 36 | return s.str(); |
include/CLI/StringTools.hpp
| @@ -46,7 +46,7 @@ inline std::vector<std::string> split(const std::string &s, char delim) { | @@ -46,7 +46,7 @@ inline std::vector<std::string> split(const std::string &s, char delim) { | ||
| 46 | std::vector<std::string> elems; | 46 | std::vector<std::string> elems; |
| 47 | // Check to see if empty string, give consistent result | 47 | // Check to see if empty string, give consistent result |
| 48 | if(s.empty()) | 48 | if(s.empty()) |
| 49 | - elems.emplace_back(""); | 49 | + elems.emplace_back(); |
| 50 | else { | 50 | else { |
| 51 | std::stringstream ss; | 51 | std::stringstream ss; |
| 52 | ss.str(s); | 52 | ss.str(s); |
| @@ -70,6 +70,21 @@ template <typename T> std::string join(const T &v, std::string delim = ",") { | @@ -70,6 +70,21 @@ template <typename T> std::string join(const T &v, std::string delim = ",") { | ||
| 70 | return s.str(); | 70 | return s.str(); |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | +/// Simple function to join a string from processed elements | ||
| 74 | +template <typename T, | ||
| 75 | + typename Callable, | ||
| 76 | + typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type> | ||
| 77 | +std::string join(const T &v, Callable func, std::string delim = ",") { | ||
| 78 | + std::ostringstream s; | ||
| 79 | + size_t start = 0; | ||
| 80 | + for(const auto &i : v) { | ||
| 81 | + if(start++ > 0) | ||
| 82 | + s << delim; | ||
| 83 | + s << func(i); | ||
| 84 | + } | ||
| 85 | + return s.str(); | ||
| 86 | +} | ||
| 87 | + | ||
| 73 | /// Join a string in reverse order | 88 | /// Join a string in reverse order |
| 74 | template <typename T> std::string rjoin(const T &v, std::string delim = ",") { | 89 | template <typename T> std::string rjoin(const T &v, std::string delim = ",") { |
| 75 | std::ostringstream s; | 90 | std::ostringstream s; |
include/CLI/TypeTools.hpp
| @@ -3,6 +3,7 @@ | @@ -3,6 +3,7 @@ | ||
| 3 | // Distributed under the 3-Clause BSD License. See accompanying | 3 | // Distributed under the 3-Clause BSD License. See accompanying |
| 4 | // file LICENSE or https://github.com/CLIUtils/CLI11 for details. | 4 | // file LICENSE or https://github.com/CLIUtils/CLI11 for details. |
| 5 | 5 | ||
| 6 | +#include "StringTools.hpp" | ||
| 6 | #include <exception> | 7 | #include <exception> |
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <string> | 9 | #include <string> |
| @@ -140,9 +141,16 @@ template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail | @@ -140,9 +141,16 @@ template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail | ||
| 140 | constexpr const char *type_name() { | 141 | constexpr const char *type_name() { |
| 141 | return "VECTOR"; | 142 | return "VECTOR"; |
| 142 | } | 143 | } |
| 144 | +/// Print name for enumeration types | ||
| 145 | +template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | ||
| 146 | +constexpr const char *type_name() { | ||
| 147 | + return "ENUM"; | ||
| 148 | +} | ||
| 143 | 149 | ||
| 150 | +/// Print for all other types | ||
| 144 | template <typename T, | 151 | template <typename T, |
| 145 | - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value, | 152 | + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value && |
| 153 | + !std::is_enum<T>::value, | ||
| 146 | detail::enabler> = detail::dummy> | 154 | detail::enabler> = detail::dummy> |
| 147 | constexpr const char *type_name() { | 155 | constexpr const char *type_name() { |
| 148 | return "TEXT"; | 156 | return "TEXT"; |
| @@ -229,10 +237,22 @@ bool lexical_cast(std::string input, T &output) { | @@ -229,10 +237,22 @@ bool lexical_cast(std::string input, T &output) { | ||
| 229 | return true; | 237 | return true; |
| 230 | } | 238 | } |
| 231 | 239 | ||
| 240 | +/// enumerations | ||
| 241 | +template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | ||
| 242 | +bool lexical_cast(std::string input, T &output) { | ||
| 243 | + typename std::underlying_type<T>::type val; | ||
| 244 | + bool retval = detail::lexical_cast(input, val); | ||
| 245 | + if(!retval) { | ||
| 246 | + return false; | ||
| 247 | + } | ||
| 248 | + output = static_cast<T>(val); | ||
| 249 | + return true; | ||
| 250 | +} | ||
| 251 | + | ||
| 232 | /// Non-string parsable | 252 | /// Non-string parsable |
| 233 | template <typename T, | 253 | template <typename T, |
| 234 | enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | 254 | enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 235 | - !std::is_assignable<T &, std::string>::value, | 255 | + !std::is_assignable<T &, std::string>::value && !std::is_enum<T>::value, |
| 236 | detail::enabler> = detail::dummy> | 256 | detail::enabler> = detail::dummy> |
| 237 | bool lexical_cast(std::string input, T &output) { | 257 | bool lexical_cast(std::string input, T &output) { |
| 238 | std::istringstream is; | 258 | std::istringstream is; |
tests/HelpersTest.cpp
| @@ -527,6 +527,10 @@ TEST(Types, TypeName) { | @@ -527,6 +527,10 @@ TEST(Types, TypeName) { | ||
| 527 | 527 | ||
| 528 | std::string text2_name = CLI::detail::type_name<char *>(); | 528 | std::string text2_name = CLI::detail::type_name<char *>(); |
| 529 | EXPECT_EQ("TEXT", text2_name); | 529 | EXPECT_EQ("TEXT", text2_name); |
| 530 | + | ||
| 531 | + enum class test { test1, test2, test3 }; | ||
| 532 | + std::string enum_name = CLI::detail::type_name<test>(); | ||
| 533 | + EXPECT_EQ("ENUM", enum_name); | ||
| 530 | } | 534 | } |
| 531 | 535 | ||
| 532 | TEST(Types, OverflowSmall) { | 536 | TEST(Types, OverflowSmall) { |
| @@ -617,6 +621,25 @@ TEST(Types, LexicalCastParsable) { | @@ -617,6 +621,25 @@ TEST(Types, LexicalCastParsable) { | ||
| 617 | EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); | 621 | EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); |
| 618 | } | 622 | } |
| 619 | 623 | ||
| 624 | +TEST(Types, LexicalCastEnum) { | ||
| 625 | + enum t1 : char { v1 = 5, v3 = 7, v5 = -9 }; | ||
| 626 | + | ||
| 627 | + t1 output; | ||
| 628 | + EXPECT_TRUE(CLI::detail::lexical_cast("-9", output)); | ||
| 629 | + EXPECT_EQ(output, v5); | ||
| 630 | + | ||
| 631 | + EXPECT_FALSE(CLI::detail::lexical_cast("invalid", output)); | ||
| 632 | + enum class t2 : uint64_t { enum1 = 65, enum2 = 45667, enum3 = 9999999999999 }; | ||
| 633 | + t2 output2; | ||
| 634 | + EXPECT_TRUE(CLI::detail::lexical_cast("65", output2)); | ||
| 635 | + EXPECT_EQ(output2, t2::enum1); | ||
| 636 | + | ||
| 637 | + EXPECT_FALSE(CLI::detail::lexical_cast("invalid", output2)); | ||
| 638 | + | ||
| 639 | + EXPECT_TRUE(CLI::detail::lexical_cast("9999999999999", output2)); | ||
| 640 | + EXPECT_EQ(output2, t2::enum3); | ||
| 641 | +} | ||
| 642 | + | ||
| 620 | TEST(FixNewLines, BasicCheck) { | 643 | TEST(FixNewLines, BasicCheck) { |
| 621 | std::string input = "one\ntwo"; | 644 | std::string input = "one\ntwo"; |
| 622 | std::string output = "one\n; two"; | 645 | std::string output = "one\n; two"; |