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 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&lt;std::string&gt; 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&lt;std::string&gt; split(const std::string &amp;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 &lt;typename T&gt; std::string join(const T &amp;v, std::string delim = &quot;,&quot;) {
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 &lt;typename T, enable_if_t&lt;is_vector&lt;T&gt;::value, detail::enabler&gt; = 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 &amp;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";
... ...