Skip to content

add transformer and checkedTransformer #239

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 5 commits into from
Mar 2, 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
157 changes: 124 additions & 33 deletions README.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION
add_cli_exe(validators validators.cpp)
add_test(NAME validators_help COMMAND validators --help)
set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION
" -f,--file FILE File name"
" -v,--value INT in [3 - 6] Value in range")
" -f,--file TEXT:FILE[\\r\\n\\t ]+File name"
" -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range")
add_test(NAME validators_file COMMAND validators --file nonex.xxx)
set_property(TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION
"--file: File does not exist: nonex.xxx"
Expand Down Expand Up @@ -155,7 +155,7 @@ add_cli_exe(enum enum.cpp)
add_test(NAME enum_pass COMMAND enum -l 1)
add_test(NAME enum_fail COMMAND enum -l 4)
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
"--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}")
"--level: Check 4 value in {" "FAILED")

add_cli_exe(digit_args digit_args.cpp)
add_test(NAME digit_args COMMAND digit_args -h)
Expand Down
10 changes: 6 additions & 4 deletions examples/enum.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
#include <CLI/CLI.hpp>
#include <map>

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

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

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

// specify string->value mappings
std::vector<std::pair<std::string, Level>> map{
{"high", Level::High}, {"medium", Level::Medium}, {"low", Level::Low}};
// checked Transform does the translation and checks the results are either in one of the strings or one of the
// translations already
app.add_option("-l,--level", level, "Level settings")
->required()
->transform(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low}));
->transform(CLI::CheckedTransformer(map, CLI::ignore_case));

CLI11_PARSE(app, argc, argv);

Expand Down
100 changes: 61 additions & 39 deletions include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class Option : public OptionBase<Option> {
int expected_{1};

/// A list of validators to run on each value parsed
std::vector<std::function<std::string(std::string &)>> validators_;
std::vector<Validator> validators_;

/// A list of options that are required with this option
std::set<Option *> needs_;
Expand Down Expand Up @@ -331,59 +331,70 @@ class Option : public OptionBase<Option> {
return this;
}

/// Adds a validator with a built in type name
Option *check(const Validator &validator) {
std::function<std::string(std::string &)> func = validator.func;
validators_.emplace_back([func](const std::string &value) {
/// Throw away changes to the string value
std::string ignore_changes_value = value;
return func(ignore_changes_value);
});
if(validator.tname_function)
type_name_fn(validator.tname_function);
else if(!validator.tname.empty())
type_name(validator.tname);
/// Adds a Validator with a built in type name
Option *check(Validator validator, std::string validator_name = "") {
validator.non_modifying();
validators_.push_back(std::move(validator));
if(!validator_name.empty())
validators_.front().name(validator_name);
return this;
}

/// Adds a validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
Option *check(std::function<std::string(const std::string &)> validator) {
validators_.emplace_back(validator);
/// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
Option *check(std::function<std::string(const std::string &)> validator,
std::string validator_description = "",
std::string validator_name = "") {
validators_.emplace_back(validator, std::move(validator_description), std::move(validator_name));
validators_.back().non_modifying();
return this;
}

/// Adds a transforming validator with a built in type name
Option *transform(const Validator &validator) {
validators_.emplace_back(validator.func);
if(validator.tname_function)
type_name_fn(validator.tname_function);
else if(!validator.tname.empty())
type_name(validator.tname);
Option *transform(Validator validator, std::string validator_name = "") {
validators_.insert(validators_.begin(), std::move(validator));
if(!validator_name.empty())
validators_.front().name(validator_name);
return this;
}

/// Adds a validator-like function that can change result
Option *transform(std::function<std::string(std::string)> func) {
validators_.emplace_back([func](std::string &inout) {
try {
inout = func(inout);
} catch(const ValidationError &e) {
return std::string(e.what());
}
return std::string();
});
Option *transform(std::function<std::string(std::string)> func,
std::string transform_description = "",
std::string transform_name = "") {
validators_.insert(validators_.begin(),
Validator(
[func](std::string &val) {
val = func(val);
return std::string{};
},
std::move(transform_description),
std::move(transform_name)));

return this;
}

/// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
Option *each(std::function<void(std::string)> func) {
validators_.emplace_back([func](std::string &inout) {
func(inout);
return std::string();
});
validators_.emplace_back(
[func](std::string &inout) {
func(inout);
return std::string{};
},
std::string{});
return this;
}

/// Get a named Validator
Validator *get_validator(const std::string &validator_name = "") {
for(auto &validator : validators_) {
if(validator_name == validator.get_name()) {
return &validator;
}
}
if((validator_name.empty()) && (!validators_.empty())) {
return &(validators_.front());
}
throw OptionNotFound(std::string("Validator ") + validator_name + " Not Found");
}
/// Sets required options
Option *needs(Option *opt) {
auto tup = needs_.insert(opt);
Expand Down Expand Up @@ -663,7 +674,7 @@ class Option : public OptionBase<Option> {
try {
err_msg = vali(result);
} catch(const ValidationError &err) {
throw ValidationError(err.what(), get_name());
throw ValidationError(get_name(), err.what());
}

if(!err_msg.empty())
Expand Down Expand Up @@ -938,8 +949,19 @@ class Option : public OptionBase<Option> {
return this;
}

/// Get the typename for this option
std::string get_type_name() const { return type_name_(); }
/// Get the full typename for this option
std::string get_type_name() const {
std::string full_type_name = type_name_();
if(!validators_.empty()) {
for(auto &validator : validators_) {
std::string vtype = validator.get_description();
if(!vtype.empty()) {
full_type_name += ":" + vtype;
}
}
}
return full_type_name;
}

private:
int _add_result(std::string &&result) {
Expand Down
33 changes: 23 additions & 10 deletions include/CLI/StringTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,27 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
}
return elems;
}
/// simple utility to convert various types to a string
template <typename T> inline std::string as_string(const T &v) {
std::ostringstream s;
s << v;
return s.str();
}
// if the data type is already a string just forward it
template <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>
inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) {
return std::forward<T>(v);
}

/// Simple function to join a string
template <typename T> std::string join(const T &v, std::string delim = ",") {
std::ostringstream s;
size_t start = 0;
for(const auto &i : v) {
if(start++ > 0)
s << delim;
s << i;
auto beg = std::begin(v);
auto end = std::end(v);
if(beg != end)
s << *beg++;
while(beg != end) {
s << delim << *beg++;
}
return s.str();
}
Expand All @@ -76,11 +88,12 @@ template <typename T,
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);
auto beg = std::begin(v);
auto end = std::end(v);
if(beg != end)
s << func(*beg++);
while(beg != end) {
s << delim << func(*beg++);
}
return s.str();
}
Expand Down
33 changes: 22 additions & 11 deletions include/CLI/TypeTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ template <typename T> struct is_shared_ptr : std::false_type {};
/// Check to see if something is a shared pointer (True if really a shared pointer)
template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};

/// Check to see if something is a shared pointer (True if really a shared pointer)
template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {};

/// Check to see if something is copyable pointer
template <typename T> struct is_copyable_ptr {
static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
Expand All @@ -71,7 +74,7 @@ template <> struct IsMemberType<const char *> { using type = std::string; };

namespace detail {

// These are utilites for IsMember
// These are utilities for IsMember

/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
/// pointer_traits<T> be valid.
Expand All @@ -84,16 +87,20 @@ template <typename T> struct element_type {
/// the container
template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };

/// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing.
/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
using value_type = typename T::value_type;
using first_type = typename std::remove_const<value_type>::type;
using second_type = typename std::remove_const<value_type>::type;

/// Get the first value (really just the underlying value)
template <typename Q> static first_type first(Q &&value) { return value; }
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
return std::forward<Q>(pair_value);
}
/// Get the second value (really just the underlying value)
template <typename Q> static second_type second(Q &&value) { return value; }
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
return std::forward<Q>(pair_value);
}
};

/// Adaptor for map-like structure (true version, must have key_type and mapped_type).
Expand All @@ -108,9 +115,13 @@ struct pair_adaptor<
using second_type = typename std::remove_const<typename value_type::second_type>::type;

/// Get the first value (really just the underlying value)
template <typename Q> static first_type first(Q &&value) { return value.first; }
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) {
return std::get<0>(std::forward<Q>(pair_value));
}
/// Get the second value (really just the underlying value)
template <typename Q> static second_type second(Q &&value) { return value.second; }
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) {
return std::get<1>(std::forward<Q>(pair_value));
}
};

// Type name print
Expand Down Expand Up @@ -158,7 +169,7 @@ constexpr const char *type_name() {

// Lexical cast

/// convert a flag into an integer value typically binary flags
/// Convert a flag into an integer value typically binary flags
inline int64_t to_flag_value(std::string val) {
static const std::string trueString("true");
static const std::string falseString("false");
Expand Down Expand Up @@ -247,7 +258,7 @@ bool lexical_cast(std::string input, T &output) {
}
}

/// boolean values
/// Boolean values
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
try {
Expand Down Expand Up @@ -283,7 +294,7 @@ bool lexical_cast(std::string input, T &output) {
return true;
}

/// enumerations
/// 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;
Expand All @@ -308,7 +319,7 @@ bool lexical_cast(std::string input, T &output) {
return !is.fail() && !is.rdbuf()->in_avail();
}

/// sum a vector of flag representations
/// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
/// common true and false strings then uses stoll to convert the rest for summing
Expand All @@ -322,7 +333,7 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
output = (count > 0) ? static_cast<T>(count) : T{0};
}

/// sum a vector of flag representations
/// Sum a vector of flag representations
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by
/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
/// common true and false strings then uses stoll to convert the rest for summing
Expand Down
Loading