mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +00:00
Adding map support to IsMember (#228)
* Adding first draft of mapping * IsMember now supports maps * Adding example, better Val combs, and cleanup * Reversing order of map, adding pair support * Check/Transform suppport for Validators * Adding enum tools from @phlptp, more tests
This commit is contained in:
parent
5e0bb1c8da
commit
17d05b000c
@ -261,6 +261,7 @@ Before parsing, you can set the following options:
|
||||
- `->description(str)`: Set/change the description.
|
||||
- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy).
|
||||
- `->check(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options.
|
||||
- `->transform(CLI::IsMember(...))`: Require an option be a member of a given set or map. Can change the parse. See below for options.
|
||||
- `->check(CLI::ExistingFile)`: Requires that the file exists if given.
|
||||
- `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
|
||||
- `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists.
|
||||
@ -283,7 +284,9 @@ of `IsMember`:
|
||||
- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
|
||||
- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
|
||||
- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
|
||||
- `CLI::IsMember(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the key.
|
||||
- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
|
||||
- Note that you can combine validators with `|`, and only the matched validation will modify the output in transform. The first `IsMember` is the only one that will print in help, though the error message will include all Validators.
|
||||
|
||||
On the command line, options can be given as:
|
||||
|
||||
|
@ -130,7 +130,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 {0,1,2}")
|
||||
"--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}")
|
||||
|
||||
add_cli_exe(modhelp modhelp.cpp)
|
||||
add_test(NAME modhelp COMMAND modhelp -a test -h)
|
||||
|
@ -1,26 +1,25 @@
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
enum class Level : int { High, Medium, Low };
|
||||
|
||||
std::istream &operator>>(std::istream &in, Level &level) {
|
||||
int i;
|
||||
in >> i;
|
||||
level = static_cast<Level>(i);
|
||||
return in;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &in, const Level &level) { return in << static_cast<int>(level); }
|
||||
|
||||
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;
|
||||
|
||||
app.add_option("-l,--level", level, "Level settings")
|
||||
->check(CLI::IsMember({Level::High, Level::Medium, Level::Low}))
|
||||
->type_name("enum/Level in {High=0, Medium=1, Low=2}");
|
||||
->required()
|
||||
->transform(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low}));
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
// CLI11's built in enum streaming can be used outside CLI11 like this:
|
||||
using namespace CLI::enums;
|
||||
std::cout << "Enum received: " << level << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -695,32 +695,32 @@ class App {
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore case (no default, static set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead")
|
||||
Option *add_set_ignore_case(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
std::set<std::string> options, ///< The set of possibilities
|
||||
std::string description = "") {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description));
|
||||
opt->check(IsMember{options, CLI::ignore_case});
|
||||
opt->transform(IsMember{options, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the
|
||||
/// set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead")
|
||||
Option *add_mutable_set_ignore_case(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
const std::set<std::string> &options, ///< The set of possibilities
|
||||
std::string description = "") {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description));
|
||||
opt->check(IsMember{&options, CLI::ignore_case});
|
||||
opt->transform(IsMember{&options, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore case (default, static set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead")
|
||||
Option *add_set_ignore_case(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
std::set<std::string> options, ///< The set of possibilities
|
||||
@ -728,13 +728,13 @@ class App {
|
||||
bool defaulted) {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description), defaulted);
|
||||
opt->check(IsMember{options, CLI::ignore_case});
|
||||
opt->transform(IsMember{options, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set)
|
||||
/// DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(...)) with a (shared) pointer instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(...)) with a (shared) pointer instead")
|
||||
Option *add_mutable_set_ignore_case(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
const std::set<std::string> &options, ///< The set of possibilities
|
||||
@ -742,37 +742,37 @@ class App {
|
||||
bool defaulted) {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description), defaulted);
|
||||
opt->check(IsMember{&options, CLI::ignore_case});
|
||||
opt->transform(IsMember{&options, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead")
|
||||
Option *add_set_ignore_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
std::set<std::string> options, ///< The set of possibilities
|
||||
std::string description = "") {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description));
|
||||
opt->check(IsMember{options, CLI::ignore_underscore});
|
||||
opt->transform(IsMember{options, CLI::ignore_underscore});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy
|
||||
/// the set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
Option *add_mutable_set_ignore_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
const std::set<std::string> &options, ///< The set of possibilities
|
||||
std::string description = "") {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description));
|
||||
opt->check(IsMember{options, CLI::ignore_underscore});
|
||||
opt->transform(IsMember{options, CLI::ignore_underscore});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore (default, static set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead")
|
||||
Option *add_set_ignore_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
std::set<std::string> options, ///< The set of possibilities
|
||||
@ -780,13 +780,13 @@ class App {
|
||||
bool defaulted) {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description), defaulted);
|
||||
opt->check(IsMember{options, CLI::ignore_underscore});
|
||||
opt->transform(IsMember{options, CLI::ignore_underscore});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the
|
||||
/// set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
Option *add_mutable_set_ignore_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
const std::set<std::string> &options, ///< The set of possibilities
|
||||
@ -794,38 +794,38 @@ class App {
|
||||
bool defaulted) {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description), defaulted);
|
||||
opt->check(IsMember{&options, CLI::ignore_underscore});
|
||||
opt->transform(IsMember{&options, CLI::ignore_underscore});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead")
|
||||
Option *add_set_ignore_case_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
std::set<std::string> options, ///< The set of possibilities
|
||||
std::string description = "") {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description));
|
||||
opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not
|
||||
/// destroy the set) DEPRECATED
|
||||
CLI11_DEPRECATED(
|
||||
"Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
Option *add_mutable_set_ignore_case_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
const std::set<std::string> &options, ///< The set of possibilities
|
||||
std::string description = "") {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description));
|
||||
opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED
|
||||
CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead")
|
||||
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead")
|
||||
Option *add_set_ignore_case_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
std::set<std::string> options, ///< The set of possibilities
|
||||
@ -833,14 +833,14 @@ class App {
|
||||
bool defaulted) {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description), defaulted);
|
||||
opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not
|
||||
/// destroy the set) DEPRECATED
|
||||
CLI11_DEPRECATED(
|
||||
"Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead")
|
||||
Option *add_mutable_set_ignore_case_underscore(std::string option_name,
|
||||
std::string &member, ///< The selected member of the set
|
||||
const std::set<std::string> &options, ///< The set of possibilities
|
||||
@ -848,7 +848,7 @@ class App {
|
||||
bool defaulted) {
|
||||
|
||||
Option *opt = add_option(option_name, member, std::move(description), defaulted);
|
||||
opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});
|
||||
return opt;
|
||||
}
|
||||
|
||||
|
@ -303,7 +303,12 @@ class Option : public OptionBase<Option> {
|
||||
|
||||
/// Adds a validator with a built in type name
|
||||
Option *check(const Validator &validator) {
|
||||
validators_.emplace_back(validator.func);
|
||||
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())
|
||||
@ -317,6 +322,16 @@ class Option : public OptionBase<Option> {
|
||||
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);
|
||||
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) {
|
||||
|
@ -13,6 +13,31 @@
|
||||
#include <vector>
|
||||
|
||||
namespace CLI {
|
||||
|
||||
/// Include the items in this namespace to get free conversion of enums to/from streams.
|
||||
/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
|
||||
namespace enums {
|
||||
|
||||
/// output streaming for enumerations
|
||||
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
std::ostream &operator<<(std::ostream &in, const T &item) {
|
||||
// make sure this is out of the detail namespace otherwise it won't be found when needed
|
||||
return in << static_cast<typename std::underlying_type<T>::type>(item);
|
||||
}
|
||||
|
||||
/// input streaming for enumerations
|
||||
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
std::istream &operator>>(std::istream &in, T &item) {
|
||||
typename std::underlying_type<T>::type i;
|
||||
in >> i;
|
||||
item = static_cast<T>(i);
|
||||
return in;
|
||||
}
|
||||
} // namespace enums
|
||||
|
||||
/// Export to CLI namespace
|
||||
using namespace enums;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
|
||||
|
@ -28,9 +28,17 @@ constexpr enabler dummy = {};
|
||||
/// We could check to see if C++14 is being used, but it does not hurt to redefine this
|
||||
/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h)
|
||||
/// It is not in the std namespace anyway, so no harm done.
|
||||
|
||||
template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
||||
/// A copy of std::void_t from C++17 (helper for C++11 and C++14)
|
||||
template <typename... Ts> struct make_void { using type = void; };
|
||||
|
||||
/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine
|
||||
template <typename... Ts> using void_t = typename make_void<Ts...>::type;
|
||||
|
||||
/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine
|
||||
template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type;
|
||||
|
||||
/// Check to see if something is a vector (fail check by default)
|
||||
template <typename T> struct is_vector : std::false_type {};
|
||||
|
||||
@ -54,6 +62,16 @@ template <typename T> struct is_copyable_ptr {
|
||||
static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
|
||||
};
|
||||
|
||||
/// This can be specialized to override the type deduction for IsMember.
|
||||
template <typename T> struct IsMemberType { using type = T; };
|
||||
|
||||
/// The main custom type needed here is const char * should be a string.
|
||||
template <> struct IsMemberType<const char *> { using type = std::string; };
|
||||
|
||||
namespace detail {
|
||||
|
||||
// These are utilites 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.
|
||||
template <typename T> struct element_type {
|
||||
@ -65,13 +83,34 @@ template <typename T> struct element_type {
|
||||
/// the container
|
||||
template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
|
||||
|
||||
/// This can be specialized to override the type deduction for IsMember.
|
||||
template <typename T> struct IsMemberType { using type = T; };
|
||||
/// Adaptor for map-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;
|
||||
|
||||
/// The main custom type needed here is const char * should be a string.
|
||||
template <> struct IsMemberType<const char *> { using type = std::string; };
|
||||
/// Get the first value (really just the underlying value)
|
||||
template <typename Q> static first_type first(Q &&value) { return value; }
|
||||
/// Get the second value (really just the underlying value)
|
||||
template <typename Q> static second_type second(Q &&value) { return value; }
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
/// Adaptor for map-like structure (true version, must have key_type and mapped_type).
|
||||
/// This wraps a mapped container in a few utilities access it in a general way.
|
||||
template <typename T>
|
||||
struct pair_adaptor<
|
||||
T,
|
||||
conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>>
|
||||
: std::true_type {
|
||||
using value_type = typename T::value_type;
|
||||
using first_type = typename std::remove_const<typename value_type::first_type>::type;
|
||||
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; }
|
||||
/// Get the second value (really just the underlying value)
|
||||
template <typename Q> static second_type second(Q &&value) { return value.second; }
|
||||
};
|
||||
|
||||
// Type name print
|
||||
|
||||
|
@ -57,42 +57,46 @@ class Validator {
|
||||
return func(value);
|
||||
};
|
||||
|
||||
/// Combining validators is a new validator
|
||||
/// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
|
||||
/// same.
|
||||
Validator operator&(const Validator &other) const {
|
||||
Validator newval;
|
||||
newval.tname = (tname == other.tname ? tname : "");
|
||||
newval.tname_function = tname_function;
|
||||
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & filename)> &f1 = func;
|
||||
const std::function<std::string(std::string & filename)> &f2 = other.func;
|
||||
|
||||
newval.func = [f1, f2](std::string &filename) {
|
||||
std::string s1 = f1(filename);
|
||||
std::string s2 = f2(filename);
|
||||
newval.func = [f1, f2](std::string &input) {
|
||||
std::string s1 = f1(input);
|
||||
std::string s2 = f2(input);
|
||||
if(!s1.empty() && !s2.empty())
|
||||
return s1 + " & " + s2;
|
||||
return s1 + " AND " + s2;
|
||||
else
|
||||
return s1 + s2;
|
||||
};
|
||||
return newval;
|
||||
}
|
||||
|
||||
/// Combining validators is a new validator
|
||||
/// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
|
||||
/// same.
|
||||
Validator operator|(const Validator &other) const {
|
||||
Validator newval;
|
||||
newval.tname = (tname == other.tname ? tname : "");
|
||||
newval.tname_function = tname_function;
|
||||
|
||||
// Give references (will make a copy in lambda function)
|
||||
const std::function<std::string(std::string & filename)> &f1 = func;
|
||||
const std::function<std::string(std::string & filename)> &f2 = other.func;
|
||||
const std::function<std::string(std::string &)> &f1 = func;
|
||||
const std::function<std::string(std::string &)> &f2 = other.func;
|
||||
|
||||
newval.func = [f1, f2](std::string &filename) {
|
||||
std::string s1 = f1(filename);
|
||||
std::string s2 = f2(filename);
|
||||
newval.func = [f1, f2](std::string &input) {
|
||||
std::string s1 = f1(input);
|
||||
std::string s2 = f2(input);
|
||||
if(s1.empty() || s2.empty())
|
||||
return std::string();
|
||||
else
|
||||
return s1 + " & " + s2;
|
||||
return s1 + " OR " + s2;
|
||||
};
|
||||
return newval;
|
||||
}
|
||||
@ -274,6 +278,7 @@ auto smart_deref(T value) -> decltype(*value) {
|
||||
template <typename T, enable_if_t<!is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> T smart_deref(T value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Verify items are in a set
|
||||
@ -287,18 +292,19 @@ class IsMember : public Validator {
|
||||
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// This checks to see if an item is in a set (empty function)
|
||||
template <typename T>
|
||||
explicit IsMember(T set)
|
||||
: IsMember(std::move(set),
|
||||
std::function<typename IsMemberType<typename element_value_type<T>::type>::type(
|
||||
typename IsMemberType<typename element_value_type<T>::type>::type)>()) {}
|
||||
template <typename T> explicit IsMember(T set) : IsMember(std::move(set), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit IsMember(T set, F filter_function) {
|
||||
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
using item_t = typename element_value_type<T>::type;
|
||||
using local_item_t = typename IsMemberType<item_t>::type;
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
@ -306,15 +312,19 @@ class IsMember : public Validator {
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
tname_function = [set]() {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<item_t>() << " in {" << detail::join(detail::smart_deref(set), ",") << "}";
|
||||
out << "{";
|
||||
int i = 0; // I don't like counters like this
|
||||
for(const auto &v : detail::smart_deref(set))
|
||||
out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v);
|
||||
out << "}";
|
||||
return out.str();
|
||||
};
|
||||
|
||||
// This is the function that validates
|
||||
// It stores a copy of the set pointer-like, so shared_ptr will stay alive
|
||||
func = [set, filter_fn](std::string &input) {
|
||||
for(const item_t &v : detail::smart_deref(set)) {
|
||||
local_item_t a = v;
|
||||
for(const auto &v : detail::smart_deref(set)) {
|
||||
local_item_t a = detail::pair_adaptor<element_t>::first(v);
|
||||
local_item_t b;
|
||||
if(!detail::lexical_cast(input, b))
|
||||
throw ValidationError(input); // name is added later
|
||||
@ -328,9 +338,10 @@ class IsMember : public Validator {
|
||||
if(a == b) {
|
||||
// Make sure the version in the input string is identical to the one in the set
|
||||
// Requires std::stringstream << be supported on T.
|
||||
if(filter_fn) {
|
||||
// If this is a map, output the map instead.
|
||||
if(filter_fn || detail::pair_adaptor<element_t>::value) {
|
||||
std::stringstream out;
|
||||
out << v;
|
||||
out << detail::pair_adaptor<element_t>::second(v);
|
||||
input = out.str();
|
||||
}
|
||||
|
||||
@ -340,11 +351,17 @@ class IsMember : public Validator {
|
||||
}
|
||||
|
||||
// If you reach this point, the result was not found
|
||||
return input + " not in {" + detail::join(detail::smart_deref(set), ",") + "}";
|
||||
std::stringstream out;
|
||||
out << input << " not in {";
|
||||
int i = 0; // I still don't like counters like this
|
||||
for(const auto &v : detail::smart_deref(set))
|
||||
out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v);
|
||||
out << "}";
|
||||
return out.str();
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
/// You can pass in as many filter functions as you like, they nest (string only currently)
|
||||
template <typename T, typename... Args>
|
||||
IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
|
||||
: IsMember(std::move(set),
|
||||
|
@ -237,6 +237,27 @@ TEST(THelp, ManualSetters) {
|
||||
EXPECT_THAT(help, HasSubstr("=14"));
|
||||
}
|
||||
|
||||
TEST(THelp, ManualSetterOverFunction) {
|
||||
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
int x = 1;
|
||||
|
||||
CLI::Option *op1 = app.add_option("--op1", x)->check(CLI::IsMember({1, 2}));
|
||||
CLI::Option *op2 = app.add_option("--op2", x)->transform(CLI::IsMember({1, 2}));
|
||||
op1->default_str("12");
|
||||
op1->type_name("BIGGLES");
|
||||
op2->type_name("QUIGGLES");
|
||||
EXPECT_EQ(x, 1);
|
||||
|
||||
std::string help = app.help();
|
||||
|
||||
EXPECT_THAT(help, HasSubstr("=12"));
|
||||
EXPECT_THAT(help, HasSubstr("BIGGLES"));
|
||||
EXPECT_THAT(help, HasSubstr("QUIGGLES"));
|
||||
EXPECT_THAT(help, Not(HasSubstr("1,2")));
|
||||
}
|
||||
|
||||
TEST(THelp, Subcom) {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
@ -789,6 +810,19 @@ TEST(THelp, CombinedValidatorsPathyText) {
|
||||
EXPECT_THAT(help, HasSubstr("PATH"));
|
||||
}
|
||||
|
||||
// Don't do this in real life, please (and transform does nothing here)
|
||||
TEST(THelp, CombinedValidatorsPathyTextAsTransform) {
|
||||
CLI::App app;
|
||||
|
||||
std::string filename;
|
||||
app.add_option("--f1", filename)->transform(CLI::ExistingPath | CLI::NonexistentPath);
|
||||
|
||||
// Combining validators with the same type string is OK
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, Not(HasSubstr("TEXT")));
|
||||
EXPECT_THAT(help, HasSubstr("PATH"));
|
||||
}
|
||||
|
||||
// #113 Part 2
|
||||
TEST(THelp, ChangingSet) {
|
||||
CLI::App app;
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "app_helper.hpp"
|
||||
#include <map>
|
||||
|
||||
static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
|
||||
static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
|
||||
@ -9,6 +10,82 @@ static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true,
|
||||
static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers");
|
||||
static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers");
|
||||
|
||||
static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs");
|
||||
static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs");
|
||||
static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs");
|
||||
|
||||
TEST_F(TApp, SimpleMaps) {
|
||||
int value;
|
||||
std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, 1);
|
||||
}
|
||||
|
||||
TEST_F(TApp, StringStringMap) {
|
||||
std::string value;
|
||||
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
|
||||
app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
args = {"-s", "a"};
|
||||
run();
|
||||
EXPECT_EQ(value, "b");
|
||||
|
||||
args = {"-s", "b"};
|
||||
run();
|
||||
EXPECT_EQ(value, "c");
|
||||
|
||||
args = {"-s", "c"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, StringStringMapNoModify) {
|
||||
std::string value;
|
||||
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
|
||||
app.add_option("-s,--set", value)->check(CLI::IsMember(map));
|
||||
args = {"-s", "a"};
|
||||
run();
|
||||
EXPECT_EQ(value, "a");
|
||||
|
||||
args = {"-s", "b"};
|
||||
run();
|
||||
EXPECT_EQ(value, "b");
|
||||
|
||||
args = {"-s", "c"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
enum SimpleEnum { SE_one = 1, SE_two = 2 };
|
||||
|
||||
TEST_F(TApp, EnumMap) {
|
||||
SimpleEnum value;
|
||||
std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}};
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, SE_one);
|
||||
}
|
||||
|
||||
enum class SimpleEnumC { one = 1, two = 2 };
|
||||
|
||||
TEST_F(TApp, EnumCMap) {
|
||||
SimpleEnumC value;
|
||||
std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}};
|
||||
auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map));
|
||||
args = {"-s", "one"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("-s"));
|
||||
EXPECT_EQ(1u, app.count("--set"));
|
||||
EXPECT_EQ(1u, opt->count());
|
||||
EXPECT_EQ(value, SimpleEnumC::one);
|
||||
}
|
||||
|
||||
TEST_F(TApp, SimpleSets) {
|
||||
std::string value;
|
||||
auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
|
||||
@ -51,7 +128,7 @@ TEST_F(TApp, SimiShortcutSets) {
|
||||
EXPECT_EQ(value, "one");
|
||||
|
||||
std::string value2;
|
||||
auto opt2 = app.add_option("--set2", value2)->check(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
|
||||
auto opt2 = app.add_option("--set2", value2)->transform(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
|
||||
args = {"--set2", "onE"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("--set2"));
|
||||
@ -60,7 +137,7 @@ TEST_F(TApp, SimiShortcutSets) {
|
||||
|
||||
std::string value3;
|
||||
auto opt3 = app.add_option("--set3", value3)
|
||||
->check(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
|
||||
->transform(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
|
||||
args = {"--set3", "onE"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("--set3"));
|
||||
@ -95,7 +172,7 @@ TEST_F(TApp, OtherTypeSets) {
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
std::vector<int> set2 = {-2, 3, 4};
|
||||
auto opt2 = app.add_option("--set2", value)->check(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
|
||||
auto opt2 = app.add_option("--set2", value)->transform(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
|
||||
args = {"--set2", "-3"};
|
||||
run();
|
||||
EXPECT_EQ(1u, app.count("--set2"));
|
||||
@ -189,7 +266,7 @@ TEST_F(TApp, InSetWithDefault) {
|
||||
TEST_F(TApp, InCaselessSetWithDefault) {
|
||||
|
||||
std::string choice = "one";
|
||||
app.add_option("-q,--quick", choice, "", true)->check(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case));
|
||||
app.add_option("-q,--quick", choice, "", true)->transform(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case));
|
||||
|
||||
run();
|
||||
EXPECT_EQ("one", choice);
|
||||
@ -263,7 +340,7 @@ TEST_F(TApp, FailMutableSet) {
|
||||
TEST_F(TApp, InSetIgnoreCase) {
|
||||
|
||||
std::string choice;
|
||||
app.add_option("-q,--quick", choice)->check(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case));
|
||||
app.add_option("-q,--quick", choice)->transform(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case));
|
||||
|
||||
args = {"--quick", "One"};
|
||||
run();
|
||||
@ -288,7 +365,7 @@ TEST_F(TApp, InSetIgnoreCaseMutableValue) {
|
||||
|
||||
std::set<std::string> options{"one", "Two", "THREE"};
|
||||
std::string choice;
|
||||
app.add_option("-q,--quick", choice)->check(CLI::IsMember(&options, CLI::ignore_case));
|
||||
app.add_option("-q,--quick", choice)->transform(CLI::IsMember(&options, CLI::ignore_case));
|
||||
|
||||
args = {"--quick", "One"};
|
||||
run();
|
||||
@ -311,7 +388,7 @@ TEST_F(TApp, InSetIgnoreCasePointer) {
|
||||
|
||||
std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
|
||||
std::string choice;
|
||||
app.add_option("-q,--quick", choice)->check(CLI::IsMember(*options, CLI::ignore_case));
|
||||
app.add_option("-q,--quick", choice)->transform(CLI::IsMember(*options, CLI::ignore_case));
|
||||
|
||||
args = {"--quick", "One"};
|
||||
run();
|
||||
@ -341,7 +418,7 @@ TEST_F(TApp, InSetIgnoreUnderscore) {
|
||||
|
||||
std::string choice;
|
||||
app.add_option("-q,--quick", choice)
|
||||
->check(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore));
|
||||
->transform(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore));
|
||||
|
||||
args = {"--quick", "option_one"};
|
||||
run();
|
||||
@ -366,7 +443,8 @@ TEST_F(TApp, InSetIgnoreCaseUnderscore) {
|
||||
|
||||
std::string choice;
|
||||
app.add_option("-q,--quick", choice)
|
||||
->check(CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore));
|
||||
->transform(
|
||||
CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore));
|
||||
|
||||
args = {"--quick", "option_one"};
|
||||
run();
|
||||
@ -423,8 +501,8 @@ TEST_F(TApp, AddRemoveSetItemsNoCase) {
|
||||
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
|
||||
|
||||
std::string type1, type2;
|
||||
app.add_option("--type1", type1)->check(CLI::IsMember(&items, CLI::ignore_case));
|
||||
app.add_option("--type2", type2, "", true)->check(CLI::IsMember(&items, CLI::ignore_case));
|
||||
app.add_option("--type1", type1)->transform(CLI::IsMember(&items, CLI::ignore_case));
|
||||
app.add_option("--type2", type2, "", true)->transform(CLI::IsMember(&items, CLI::ignore_case));
|
||||
|
||||
args = {"--type1", "TYPe1", "--type2", "TyPE2"};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user