Skip to content

Commit

Permalink
Adding map support to IsMember (#228)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
henryiii authored Feb 20, 2019
1 parent 5e0bb1c commit 17d05b0
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 80 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 10 additions & 11 deletions examples/enum.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
48 changes: 24 additions & 24 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,160 +695,160 @@ 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
std::string description,
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
std::string description,
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
std::string description,
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
std::string description,
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
std::string description,
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
std::string description,
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;
}

Expand Down
17 changes: 16 additions & 1 deletion include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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) {
Expand Down
25 changes: 25 additions & 0 deletions include/CLI/StringTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 17d05b0

Please sign in to comment.