1
0
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:
Henry Schreiner 2019-02-20 17:17:51 +01:00 committed by GitHub
parent 5e0bb1c8da
commit 17d05b000c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 290 additions and 80 deletions

View File

@ -261,6 +261,7 @@ Before parsing, you can set the following options:
- `->description(str)`: Set/change the description. - `->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). - `->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. - `->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::ExistingFile)`: Requires that the file exists if given.
- `->check(CLI::ExistingDirectory)`: Requires that the directory exists. - `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
- `->check(CLI::ExistingPath)`: Requires that the path (file or 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"})`: Select from exact match to choices.
- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too. - `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::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. - `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: On the command line, options can be given as:

View File

@ -130,7 +130,7 @@ add_cli_exe(enum enum.cpp)
add_test(NAME enum_pass COMMAND enum -l 1) add_test(NAME enum_pass COMMAND enum -l 1)
add_test(NAME enum_fail COMMAND enum -l 4) add_test(NAME enum_fail COMMAND enum -l 4)
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION 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_cli_exe(modhelp modhelp.cpp)
add_test(NAME modhelp COMMAND modhelp -a test -h) add_test(NAME modhelp COMMAND modhelp -a test -h)

View File

@ -1,26 +1,25 @@
#include <CLI/CLI.hpp> #include <CLI/CLI.hpp>
#include <map>
#include <sstream> #include <sstream>
enum class Level : int { High, Medium, Low }; 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) { int main(int argc, char **argv) {
CLI::App app; CLI::App app;
std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}};
Level level; Level level;
app.add_option("-l,--level", level, "Level settings") app.add_option("-l,--level", level, "Level settings")
->check(CLI::IsMember({Level::High, Level::Medium, Level::Low})) ->required()
->type_name("enum/Level in {High=0, Medium=1, Low=2}"); ->transform(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low}));
CLI11_PARSE(app, argc, argv); 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; return 0;
} }

View File

@ -695,32 +695,32 @@ class App {
} }
/// Add set of options, string only, ignore case (no default, static set) DEPRECATED /// 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, Option *add_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities std::set<std::string> options, ///< The set of possibilities
std::string description = "") { std::string description = "") {
Option *opt = add_option(option_name, member, std::move(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; return opt;
} }
/// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the /// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the
/// set) DEPRECATED /// 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, Option *add_mutable_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") { std::string description = "") {
Option *opt = add_option(option_name, member, std::move(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; return opt;
} }
/// Add set of options, string only, ignore case (default, static set) DEPRECATED /// 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, Option *add_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities std::set<std::string> options, ///< The set of possibilities
@ -728,13 +728,13 @@ class App {
bool defaulted) { bool defaulted) {
Option *opt = add_option(option_name, member, std::move(description), 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; return opt;
} }
/// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set) /// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set)
/// DEPRECATED /// 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, Option *add_mutable_set_ignore_case(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities const std::set<std::string> &options, ///< The set of possibilities
@ -742,37 +742,37 @@ class App {
bool defaulted) { bool defaulted) {
Option *opt = add_option(option_name, member, std::move(description), 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; return opt;
} }
/// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED /// 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, Option *add_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities std::set<std::string> options, ///< The set of possibilities
std::string description = "") { std::string description = "") {
Option *opt = add_option(option_name, member, std::move(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; return opt;
} }
/// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy /// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy
/// the set) DEPRECATED /// 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, Option *add_mutable_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") { std::string description = "") {
Option *opt = add_option(option_name, member, std::move(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; return opt;
} }
/// Add set of options, string only, ignore underscore (default, static set) DEPRECATED /// 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, Option *add_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities std::set<std::string> options, ///< The set of possibilities
@ -780,13 +780,13 @@ class App {
bool defaulted) { bool defaulted) {
Option *opt = add_option(option_name, member, std::move(description), 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; return opt;
} }
/// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the /// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the
/// set) DEPRECATED /// 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, Option *add_mutable_set_ignore_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities const std::set<std::string> &options, ///< The set of possibilities
@ -794,38 +794,38 @@ class App {
bool defaulted) { bool defaulted) {
Option *opt = add_option(option_name, member, std::move(description), 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; return opt;
} }
/// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED /// 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, Option *add_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities std::set<std::string> options, ///< The set of possibilities
std::string description = "") { std::string description = "") {
Option *opt = add_option(option_name, member, std::move(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; return opt;
} }
/// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not /// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not
/// destroy the set) DEPRECATED /// destroy the set) DEPRECATED
CLI11_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, Option *add_mutable_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") { std::string description = "") {
Option *opt = add_option(option_name, member, std::move(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; return opt;
} }
/// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED /// 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, Option *add_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities std::set<std::string> options, ///< The set of possibilities
@ -833,14 +833,14 @@ class App {
bool defaulted) { bool defaulted) {
Option *opt = add_option(option_name, member, std::move(description), 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; return opt;
} }
/// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not /// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not
/// destroy the set) DEPRECATED /// destroy the set) DEPRECATED
CLI11_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, Option *add_mutable_set_ignore_case_underscore(std::string option_name,
std::string &member, ///< The selected member of the set std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities const std::set<std::string> &options, ///< The set of possibilities
@ -848,7 +848,7 @@ class App {
bool defaulted) { bool defaulted) {
Option *opt = add_option(option_name, member, std::move(description), 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; return opt;
} }

View File

@ -303,7 +303,12 @@ class Option : public OptionBase<Option> {
/// Adds a validator with a built in type name /// Adds a validator with a built in type name
Option *check(const Validator &validator) { 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) if(validator.tname_function)
type_name_fn(validator.tname_function); type_name_fn(validator.tname_function);
else if(!validator.tname.empty()) else if(!validator.tname.empty())
@ -317,6 +322,16 @@ class Option : public OptionBase<Option> {
return this; 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 /// Adds a validator-like function that can change result
Option *transform(std::function<std::string(std::string)> func) { Option *transform(std::function<std::string(std::string)> func) {
validators_.emplace_back([func](std::string &inout) { validators_.emplace_back([func](std::string &inout) {

View File

@ -13,6 +13,31 @@
#include <vector> #include <vector>
namespace CLI { 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 { namespace detail {
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c

View File

@ -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 /// 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) /// (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. /// 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; 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) /// Check to see if something is a vector (fail check by default)
template <typename T> struct is_vector : std::false_type {}; 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; 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 /// 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. /// pointer_traits<T> be valid.
template <typename T> struct element_type { template <typename T> struct element_type {
@ -65,13 +83,34 @@ template <typename T> struct element_type {
/// the container /// the container
template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; }; 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. /// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing.
template <typename T> struct IsMemberType { using type = T; }; 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. /// Get the first value (really just the underlying value)
template <> struct IsMemberType<const char *> { using type = std::string; }; 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 // Type name print

View File

@ -57,42 +57,46 @@ class Validator {
return func(value); 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 operator&(const Validator &other) const {
Validator newval; Validator newval;
newval.tname = (tname == other.tname ? tname : ""); newval.tname = (tname == other.tname ? tname : "");
newval.tname_function = tname_function;
// Give references (will make a copy in lambda 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)> &f1 = func;
const std::function<std::string(std::string & filename)> &f2 = other.func; const std::function<std::string(std::string & filename)> &f2 = other.func;
newval.func = [f1, f2](std::string &filename) { newval.func = [f1, f2](std::string &input) {
std::string s1 = f1(filename); std::string s1 = f1(input);
std::string s2 = f2(filename); std::string s2 = f2(input);
if(!s1.empty() && !s2.empty()) if(!s1.empty() && !s2.empty())
return s1 + " & " + s2; return s1 + " AND " + s2;
else else
return s1 + s2; return s1 + s2;
}; };
return newval; 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 operator|(const Validator &other) const {
Validator newval; Validator newval;
newval.tname = (tname == other.tname ? tname : ""); newval.tname = (tname == other.tname ? tname : "");
newval.tname_function = tname_function;
// Give references (will make a copy in lambda 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 &)> &f1 = func;
const std::function<std::string(std::string & filename)> &f2 = other.func; const std::function<std::string(std::string &)> &f2 = other.func;
newval.func = [f1, f2](std::string &filename) { newval.func = [f1, f2](std::string &input) {
std::string s1 = f1(filename); std::string s1 = f1(input);
std::string s2 = f2(filename); std::string s2 = f2(input);
if(s1.empty() || s2.empty()) if(s1.empty() || s2.empty())
return std::string(); return std::string();
else else
return s1 + " & " + s2; return s1 + " OR " + s2;
}; };
return newval; 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) { template <typename T, enable_if_t<!is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> T smart_deref(T value) {
return value; return value;
} }
} // namespace detail } // namespace detail
/// Verify items are in a set /// Verify items are in a set
@ -287,18 +292,19 @@ class IsMember : public Validator {
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {} : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
/// This checks to see if an item is in a set (empty function) /// This checks to see if an item is in a set (empty function)
template <typename T> template <typename T> explicit IsMember(T set) : IsMember(std::move(set), nullptr) {}
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)>()) {}
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter /// 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. /// both sides of the comparison before computing the comparison.
template <typename T, typename F> explicit IsMember(T set, F filter_function) { 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 // Get the type of the contained item - requires a container have ::value_type
using item_t = typename element_value_type<T>::type; // if the type does not have first_type and second_type, these are both value_type
using local_item_t = typename IsMemberType<item_t>::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 // 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; 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 // This is the type name for help, it will take the current version of the set contents
tname_function = [set]() { tname_function = [set]() {
std::stringstream out; 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(); return out.str();
}; };
// This is the function that validates // This is the function that validates
// It stores a copy of the set pointer-like, so shared_ptr will stay alive // It stores a copy of the set pointer-like, so shared_ptr will stay alive
func = [set, filter_fn](std::string &input) { func = [set, filter_fn](std::string &input) {
for(const item_t &v : detail::smart_deref(set)) { for(const auto &v : detail::smart_deref(set)) {
local_item_t a = v; local_item_t a = detail::pair_adaptor<element_t>::first(v);
local_item_t b; local_item_t b;
if(!detail::lexical_cast(input, b)) if(!detail::lexical_cast(input, b))
throw ValidationError(input); // name is added later throw ValidationError(input); // name is added later
@ -328,9 +338,10 @@ class IsMember : public Validator {
if(a == b) { if(a == b) {
// Make sure the version in the input string is identical to the one in the set // Make sure the version in the input string is identical to the one in the set
// Requires std::stringstream << be supported on T. // 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; std::stringstream out;
out << v; out << detail::pair_adaptor<element_t>::second(v);
input = out.str(); input = out.str();
} }
@ -340,11 +351,17 @@ class IsMember : public Validator {
} }
// If you reach this point, the result was not found // 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> template <typename T, typename... Args>
IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
: IsMember(std::move(set), : IsMember(std::move(set),

View File

@ -237,6 +237,27 @@ TEST(THelp, ManualSetters) {
EXPECT_THAT(help, HasSubstr("=14")); 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) { TEST(THelp, Subcom) {
CLI::App app{"My prog"}; CLI::App app{"My prog"};
@ -789,6 +810,19 @@ TEST(THelp, CombinedValidatorsPathyText) {
EXPECT_THAT(help, HasSubstr("PATH")); 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 // #113 Part 2
TEST(THelp, ChangingSet) { TEST(THelp, ChangingSet) {
CLI::App app; CLI::App app;

View File

@ -1,4 +1,5 @@
#include "app_helper.hpp" #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<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"); 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 == 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::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) { TEST_F(TApp, SimpleSets) {
std::string value; std::string value;
auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})}); 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"); EXPECT_EQ(value, "one");
std::string value2; 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"}; args = {"--set2", "onE"};
run(); run();
EXPECT_EQ(1u, app.count("--set2")); EXPECT_EQ(1u, app.count("--set2"));
@ -60,7 +137,7 @@ TEST_F(TApp, SimiShortcutSets) {
std::string value3; std::string value3;
auto opt3 = app.add_option("--set3", 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"}; args = {"--set3", "onE"};
run(); run();
EXPECT_EQ(1u, app.count("--set3")); EXPECT_EQ(1u, app.count("--set3"));
@ -95,7 +172,7 @@ TEST_F(TApp, OtherTypeSets) {
EXPECT_THROW(run(), CLI::ValidationError); EXPECT_THROW(run(), CLI::ValidationError);
std::vector<int> set2 = {-2, 3, 4}; 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"}; args = {"--set2", "-3"};
run(); run();
EXPECT_EQ(1u, app.count("--set2")); EXPECT_EQ(1u, app.count("--set2"));
@ -189,7 +266,7 @@ TEST_F(TApp, InSetWithDefault) {
TEST_F(TApp, InCaselessSetWithDefault) { TEST_F(TApp, InCaselessSetWithDefault) {
std::string choice = "one"; 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(); run();
EXPECT_EQ("one", choice); EXPECT_EQ("one", choice);
@ -263,7 +340,7 @@ TEST_F(TApp, FailMutableSet) {
TEST_F(TApp, InSetIgnoreCase) { TEST_F(TApp, InSetIgnoreCase) {
std::string choice; 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"}; args = {"--quick", "One"};
run(); run();
@ -288,7 +365,7 @@ TEST_F(TApp, InSetIgnoreCaseMutableValue) {
std::set<std::string> options{"one", "Two", "THREE"}; std::set<std::string> options{"one", "Two", "THREE"};
std::string choice; 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"}; args = {"--quick", "One"};
run(); run();
@ -311,7 +388,7 @@ TEST_F(TApp, InSetIgnoreCasePointer) {
std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"}; std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
std::string choice; 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"}; args = {"--quick", "One"};
run(); run();
@ -341,7 +418,7 @@ TEST_F(TApp, InSetIgnoreUnderscore) {
std::string choice; std::string choice;
app.add_option("-q,--quick", 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"}; args = {"--quick", "option_one"};
run(); run();
@ -366,7 +443,8 @@ TEST_F(TApp, InSetIgnoreCaseUnderscore) {
std::string choice; std::string choice;
app.add_option("-q,--quick", 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"}; args = {"--quick", "option_one"};
run(); run();
@ -423,8 +501,8 @@ TEST_F(TApp, AddRemoveSetItemsNoCase) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"}; std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2; std::string type1, type2;
app.add_option("--type1", type1)->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)->check(CLI::IsMember(&items, CLI::ignore_case)); app.add_option("--type2", type2, "", true)->transform(CLI::IsMember(&items, CLI::ignore_case));
args = {"--type1", "TYPe1", "--type2", "TyPE2"}; args = {"--type1", "TYPe1", "--type2", "TyPE2"};