Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adding map support to IsMember #228

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

Filter by extension

Filter by extension

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