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 | 178 | |
| 179 | 179 | ```cpp |
| 180 | 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 | 182 | help_string="", |
| 183 | 183 | default=false) |
| 184 | 184 | |
| 185 | 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 | 187 | help_string="") |
| 188 | 188 | |
| 189 | 189 | app.add_complex(... // Special case: support for complex numbers | ... | ... |
examples/enum.cpp
| 1 | 1 | #include <CLI/CLI.hpp> |
| 2 | 2 | #include <map> |
| 3 | -#include <sstream> | |
| 4 | 3 | |
| 5 | 4 | enum class Level : int { High, Medium, Low }; |
| 6 | 5 | |
| 7 | 6 | int main(int argc, char **argv) { |
| 8 | 7 | CLI::App app; |
| 9 | 8 | |
| 10 | - std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; | |
| 11 | - | |
| 12 | 9 | Level level; |
| 10 | + std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; | |
| 13 | 11 | |
| 14 | 12 | app.add_option("-l,--level", level, "Level settings") |
| 15 | 13 | ->required() | ... | ... |
include/CLI/ConfigFwd.hpp
| ... | ... | @@ -27,10 +27,10 @@ inline std::string ini_join(std::vector<std::string> args) { |
| 27 | 27 | auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); }); |
| 28 | 28 | if(it == arg.end()) |
| 29 | 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 | 32 | else |
| 33 | - s << R"(')" << arg << R"(')"; | |
| 33 | + s << '\'' << arg << '\''; | |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | 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 | 46 | std::vector<std::string> elems; |
| 47 | 47 | // Check to see if empty string, give consistent result |
| 48 | 48 | if(s.empty()) |
| 49 | - elems.emplace_back(""); | |
| 49 | + elems.emplace_back(); | |
| 50 | 50 | else { |
| 51 | 51 | std::stringstream ss; |
| 52 | 52 | ss.str(s); |
| ... | ... | @@ -70,6 +70,21 @@ template <typename T> std::string join(const T &v, std::string delim = ",") { |
| 70 | 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 | 88 | /// Join a string in reverse order |
| 74 | 89 | template <typename T> std::string rjoin(const T &v, std::string delim = ",") { |
| 75 | 90 | std::ostringstream s; | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -3,6 +3,7 @@ |
| 3 | 3 | // Distributed under the 3-Clause BSD License. See accompanying |
| 4 | 4 | // file LICENSE or https://github.com/CLIUtils/CLI11 for details. |
| 5 | 5 | |
| 6 | +#include "StringTools.hpp" | |
| 6 | 7 | #include <exception> |
| 7 | 8 | #include <memory> |
| 8 | 9 | #include <string> |
| ... | ... | @@ -140,9 +141,16 @@ template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail |
| 140 | 141 | constexpr const char *type_name() { |
| 141 | 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 | 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 | 154 | detail::enabler> = detail::dummy> |
| 147 | 155 | constexpr const char *type_name() { |
| 148 | 156 | return "TEXT"; |
| ... | ... | @@ -229,10 +237,22 @@ bool lexical_cast(std::string input, T &output) { |
| 229 | 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 | 252 | /// Non-string parsable |
| 233 | 253 | template <typename T, |
| 234 | 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 | 256 | detail::enabler> = detail::dummy> |
| 237 | 257 | bool lexical_cast(std::string input, T &output) { |
| 238 | 258 | std::istringstream is; | ... | ... |
tests/HelpersTest.cpp
| ... | ... | @@ -527,6 +527,10 @@ TEST(Types, TypeName) { |
| 527 | 527 | |
| 528 | 528 | std::string text2_name = CLI::detail::type_name<char *>(); |
| 529 | 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 | 536 | TEST(Types, OverflowSmall) { |
| ... | ... | @@ -617,6 +621,25 @@ TEST(Types, LexicalCastParsable) { |
| 617 | 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 | 643 | TEST(FixNewLines, BasicCheck) { |
| 621 | 644 | std::string input = "one\ntwo"; |
| 622 | 645 | std::string output = "one\n; two"; | ... | ... |