Skip to content

Commit

Permalink
Add better enum support in the library (#233)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
phlptp authored and henryiii committed Feb 20, 2019
1 parent 571fb07 commit 6c645b5
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<type>(option_name,
function <void(const type &value)>, // int, float, vector, or string-like
function <void(const type &value)>, // int, float, enum, vector, or string-like
help_string="")

app.add_complex(... // Special case: support for complex numbers
Expand Down
4 changes: 1 addition & 3 deletions examples/enum.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
#include <CLI/CLI.hpp>
#include <map>
#include <sstream>

enum class Level : int { High, Medium, Low };

int main(int argc, char **argv) {
CLI::App app;

std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}};

Level level;
std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}};

app.add_option("-l,--level", level, "Level settings")
->required()
Expand Down
6 changes: 3 additions & 3 deletions include/CLI/ConfigFwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ inline std::string ini_join(std::vector<std::string> args) {
auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(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();
Expand Down
17 changes: 16 additions & 1 deletion include/CLI/StringTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> 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);
Expand All @@ -70,6 +70,21 @@ template <typename T> std::string join(const T &v, std::string delim = ",") {
return s.str();
}

/// Simple function to join a string from processed elements
template <typename T,
typename Callable,
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::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 <typename T> std::string rjoin(const T &v, std::string delim = ",") {
std::ostringstream s;
Expand Down
24 changes: 22 additions & 2 deletions include/CLI/TypeTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <exception>
#include <memory>
#include <string>
Expand Down Expand Up @@ -140,9 +141,16 @@ template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail
constexpr const char *type_name() {
return "VECTOR";
}
/// Print name for enumeration types
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return "ENUM";
}

/// Print for all other types
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value &&
!std::is_enum<T>::value,
detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return "TEXT";
Expand Down Expand Up @@ -229,10 +237,22 @@ bool lexical_cast(std::string input, T &output) {
return true;
}

/// enumerations
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
typename std::underlying_type<T>::type val;
bool retval = detail::lexical_cast(input, val);
if(!retval) {
return false;
}
output = static_cast<T>(val);
return true;
}

/// Non-string parsable
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value,
!std::is_assignable<T &, std::string>::value && !std::is_enum<T>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
std::istringstream is;
Expand Down
23 changes: 23 additions & 0 deletions tests/HelpersTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,10 @@ TEST(Types, TypeName) {

std::string text2_name = CLI::detail::type_name<char *>();
EXPECT_EQ("TEXT", text2_name);

enum class test { test1, test2, test3 };
std::string enum_name = CLI::detail::type_name<test>();
EXPECT_EQ("ENUM", enum_name);
}

TEST(Types, OverflowSmall) {
Expand Down Expand Up @@ -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";
Expand Down

0 comments on commit 6c645b5

Please sign in to comment.