Skip to content

Add better enum support in the library #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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