Commit 6c645b55a117ad5035aa628c47bafd24a05b2a63

Authored by Philip Top
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
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&lt;std::string&gt; args) { @@ -27,10 +27,10 @@ inline std::string ini_join(std::vector&lt;std::string&gt; 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&lt;std::string&gt; split(const std::string &amp;s, char delim) { @@ -46,7 +46,7 @@ inline std::vector&lt;std::string&gt; split(const std::string &amp;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 &lt;typename T&gt; std::string join(const T &amp;v, std::string delim = &quot;,&quot;) { @@ -70,6 +70,21 @@ template &lt;typename T&gt; std::string join(const T &amp;v, std::string delim = &quot;,&quot;) {
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 &lt;typename T, enable_if_t&lt;is_vector&lt;T&gt;::value, detail::enabler&gt; = detail @@ -140,9 +141,16 @@ template &lt;typename T, enable_if_t&lt;is_vector&lt;T&gt;::value, detail::enabler&gt; = 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 &amp;output) { @@ -229,10 +237,22 @@ bool lexical_cast(std::string input, T &amp;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";