1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-30 12:43:52 +00:00

Option delimiter (#240)

* move delimiter controls to Option vs in the App callbacks
This commit is contained in:
Philip Top 2019-02-27 09:04:21 -08:00 committed by Henry Schreiner
parent 7254ea7916
commit 9f81385bcd
5 changed files with 151 additions and 104 deletions

View File

@ -273,6 +273,7 @@ Before parsing, you can set the following options:
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character - `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
- `->disable_flag_override()`: from the command line long form flag option can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. - `->disable_flag_override()`: from the command line long form flag option can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options.
- `->delimiter(char)`: allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value
- `->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.
@ -338,9 +339,7 @@ In most cases the fastest and easiest way is to return the results through a cal
- `results()`: retrieves a vector of strings with all the results in the order they were given. - `results()`: retrieves a vector of strings with all the results in the order they were given.
- `results(variable_to_bind_to)`: gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable. - `results(variable_to_bind_to)`: gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
- `results(vector_type_variable,delimiter)`: gets the results to a vector type and uses a delimiter to further split the values - `Value=as<type>()`: returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place.
- `Value=as<type>()`: returns the result or default value directly as the specified type if possible.
- `Vector_value=as<type>(delimiter): same the results function with the delimiter but returns the value directly.
### Subcommands ### Subcommands
@ -434,7 +433,7 @@ arguments, use `.config_to_str(default_also=false, prefix="", write_description=
Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
Options have defaults for `group`, `required`, `disable_flag_override`,`multi_option_policy`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example: Options have defaults for `group`, `required`, `disable_flag_override`,`multi_option_policy`, `ignore_underscore`,`delimiter`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
```cpp ```cpp
app.option_defaults()->required(); app.option_defaults()->required();

View File

@ -435,29 +435,21 @@ class App {
return opt; return opt;
} }
/// Add option for vectors (no default) /// Add option for vectors
template <typename T> template <typename T>
Option *add_option(std::string option_name, Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set std::vector<T> &variable, ///< The variable vector to set
std::string option_description = "", std::string option_description = "") {
char delimiter = '\0') {
CLI::callback_t fun = [&variable, delimiter](CLI::results_t res) { CLI::callback_t fun = [&variable](CLI::results_t res) {
bool retval = true; bool retval = true;
variable.clear(); variable.clear();
variable.reserve(res.size());
for(const auto &elem : res) { for(const auto &elem : res) {
if((delimiter != '\0') && (elem.find_first_of(delimiter) != std::string::npos)) {
for(const auto &var : CLI::detail::split(elem, delimiter)) {
if(!var.empty()) {
variable.emplace_back();
retval &= detail::lexical_cast(var, variable.back());
}
}
} else {
variable.emplace_back(); variable.emplace_back();
retval &= detail::lexical_cast(elem, variable.back()); retval &= detail::lexical_cast(elem, variable.back());
} }
}
return (!variable.empty()) && retval; return (!variable.empty()) && retval;
}; };
@ -466,35 +458,28 @@ class App {
return opt; return opt;
} }
/// Add option for vectors /// Add option for vectors with defaulted argument
template <typename T> template <typename T>
Option *add_option(std::string option_name, Option *add_option(std::string option_name,
std::vector<T> &variable, ///< The variable vector to set std::vector<T> &variable, ///< The variable vector to set
std::string option_description, std::string option_description,
bool defaulted, bool defaulted) {
char delimiter = '\0') {
CLI::callback_t fun = [&variable, delimiter](CLI::results_t res) { CLI::callback_t fun = [&variable](CLI::results_t res) {
bool retval = true; bool retval = true;
variable.clear(); variable.clear();
variable.reserve(res.size());
for(const auto &elem : res) { for(const auto &elem : res) {
if((delimiter != '\0') && (elem.find_first_of(delimiter) != std::string::npos)) {
for(const auto &var : CLI::detail::split(elem, delimiter)) {
if(!var.empty()) {
variable.emplace_back();
retval &= detail::lexical_cast(var, variable.back());
}
}
} else {
variable.emplace_back(); variable.emplace_back();
retval &= detail::lexical_cast(elem, variable.back()); retval &= detail::lexical_cast(elem, variable.back());
} }
}
return (!variable.empty()) && retval; return (!variable.empty()) && retval;
}; };
Option *opt = add_option(option_name, fun, option_description, defaulted); Option *opt = add_option(option_name, fun, option_description, defaulted);
opt->type_name(detail::type_name<T>())->type_size(-1); opt->type_name(detail::type_name<T>())->type_size(-1);
if(defaulted) if(defaulted)
opt->default_str("[" + detail::join(variable) + "]"); opt->default_str("[" + detail::join(variable) + "]");
return opt; return opt;
@ -504,26 +489,16 @@ class App {
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
Option *add_option_function(std::string option_name, Option *add_option_function(std::string option_name,
const std::function<bool(const T &)> &func, ///< the callback to execute const std::function<bool(const T &)> &func, ///< the callback to execute
std::string option_description = "", std::string option_description = "") {
char delimiter = '\0') {
CLI::callback_t fun = [func, delimiter](CLI::results_t res) { CLI::callback_t fun = [func](CLI::results_t res) {
T values; T values;
bool retval = true; bool retval = true;
values.reserve(res.size()); values.reserve(res.size());
for(const auto &elem : res) { for(const auto &elem : res) {
if((delimiter != '\0') && (elem.find_first_of(delimiter) != std::string::npos)) {
for(const auto &var : CLI::detail::split(elem, delimiter)) {
if(!var.empty()) {
values.emplace_back();
retval &= detail::lexical_cast(var, values.back());
}
}
} else {
values.emplace_back(); values.emplace_back();
retval &= detail::lexical_cast(elem, values.back()); retval &= detail::lexical_cast(elem, values.back());
} }
}
if(retval) { if(retval) {
return func(values); return func(values);
} }
@ -2032,6 +2007,7 @@ class App {
// Make sure we always eat the minimum for unlimited vectors // Make sure we always eat the minimum for unlimited vectors
int collected = 0; int collected = 0;
// deal with flag like things // deal with flag like things
int count = 0;
if(num == 0) { if(num == 0) {
auto res = op->get_flag_value(arg_name, value); auto res = op->get_flag_value(arg_name, value);
op->add_result(res); op->add_result(res);
@ -2039,20 +2015,22 @@ class App {
} }
// --this=value // --this=value
else if(!value.empty()) { else if(!value.empty()) {
op->add_result(value, count);
parse_order_.push_back(op.get());
collected += count;
// If exact number expected // If exact number expected
if(num > 0) if(num > 0)
num--; num = (num >= count) ? num - count : 0;
op->add_result(value);
parse_order_.push_back(op.get());
collected += 1;
// -Trest // -Trest
} else if(!rest.empty()) { } else if(!rest.empty()) {
if(num > 0) op->add_result(rest, count);
num--;
op->add_result(rest);
parse_order_.push_back(op.get()); parse_order_.push_back(op.get());
rest = ""; rest = "";
collected += 1; collected += count;
// If exact number expected
if(num > 0)
num = (num >= count) ? num - count : 0;
} }
// Unlimited vector parser // Unlimited vector parser
@ -2065,10 +2043,10 @@ class App {
if(_count_remaining_positionals() > 0) if(_count_remaining_positionals() > 0)
break; break;
} }
op->add_result(args.back()); op->add_result(args.back(), count);
parse_order_.push_back(op.get()); parse_order_.push_back(op.get());
args.pop_back(); args.pop_back();
collected++; collected += count;
} }
// Allow -- to end an unlimited list and "eat" it // Allow -- to end an unlimited list and "eat" it
@ -2077,11 +2055,11 @@ class App {
} else { } else {
while(num > 0 && !args.empty()) { while(num > 0 && !args.empty()) {
num--;
std::string current_ = args.back(); std::string current_ = args.back();
args.pop_back(); args.pop_back();
op->add_result(current_); op->add_result(current_, count);
parse_order_.push_back(op.get()); parse_order_.push_back(op.get());
num -= count;
} }
if(num > 0) { if(num > 0) {

View File

@ -52,7 +52,8 @@ template <typename CRTP> class OptionBase {
bool configurable_{true}; bool configurable_{true};
/// Disable overriding flag values with '=value' /// Disable overriding flag values with '=value'
bool disable_flag_override_{false}; bool disable_flag_override_{false};
/// Specify a delimiter character for vector arguments
char delimiter_{'\0'};
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
@ -64,6 +65,7 @@ template <typename CRTP> class OptionBase {
other->ignore_underscore(ignore_underscore_); other->ignore_underscore(ignore_underscore_);
other->configurable(configurable_); other->configurable(configurable_);
other->disable_flag_override(disable_flag_override_); other->disable_flag_override(disable_flag_override_);
other->delimiter(delimiter_);
other->multi_option_policy(multi_option_policy_); other->multi_option_policy(multi_option_policy_);
} }
@ -106,6 +108,7 @@ template <typename CRTP> class OptionBase {
/// The status of configurable /// The status of configurable
bool get_disable_flag_override() const { return disable_flag_override_; } bool get_disable_flag_override() const { return disable_flag_override_; }
char get_delimiter() const { return delimiter_; }
/// The status of the multi option policy /// The status of the multi option policy
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
@ -137,6 +140,12 @@ template <typename CRTP> class OptionBase {
configurable_ = value; configurable_ = value;
return static_cast<CRTP *>(this); return static_cast<CRTP *>(this);
} }
/// Allow in a configuration file
CRTP *delimiter(char value = '\0') {
delimiter_ = value;
return static_cast<CRTP *>(this);
}
}; };
/// This is a version of OptionBase that only supports setting values, /// This is a version of OptionBase that only supports setting values,
@ -165,11 +174,17 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
return this; return this;
} }
/// Ignore underscores in the option name /// Disable overriding flag values with an '=<value>' segment
OptionDefaults *disable_flag_override(bool value = true) { OptionDefaults *disable_flag_override(bool value = true) {
disable_flag_override_ = value; disable_flag_override_ = value;
return this; return this;
} }
/// set a delimiter character to split up single arguments to treat as multiple inputs
OptionDefaults *delimiter(char value = '\0') {
delimiter_ = value;
return this;
}
}; };
class Option : public OptionBase<Option> { class Option : public OptionBase<Option> {
@ -793,19 +808,23 @@ class Option : public OptionBase<Option> {
/// Puts a result at the end /// Puts a result at the end
Option *add_result(std::string s) { Option *add_result(std::string s) {
results_.push_back(std::move(s)); _add_result(std::move(s));
callback_run_ = false;
return this;
}
/// Puts a result at the end and get a count of the number of arguments actually added
Option *add_result(std::string s, int &count) {
count = _add_result(std::move(s));
callback_run_ = false; callback_run_ = false;
return this; return this;
} }
/// Puts a result at the end /// Puts a result at the end
Option *add_result(std::vector<std::string> s) { Option *add_result(std::vector<std::string> s) {
if(results_.empty()) { for(auto &str : s) {
results_ = std::move(s); _add_result(std::move(str));
} else {
results_.insert(results_.end(), s.begin(), s.end());
} }
callback_run_ = false; callback_run_ = false;
return this; return this;
} }
@ -850,23 +869,14 @@ class Option : public OptionBase<Option> {
} }
} }
/// get the results as a vector of a particular type /// get the results as a vector of a particular type
template <typename T> void results(std::vector<T> &output, char delim = '\0') const { template <typename T> void results(std::vector<T> &output) const {
output.clear(); output.clear();
bool retval = true; bool retval = true;
for(const auto &elem : results_) { for(const auto &elem : results_) {
if(delim != '\0') {
for(const auto &var : CLI::detail::split(elem, delim)) {
if(!var.empty()) {
output.emplace_back();
retval &= detail::lexical_cast(var, output.back());
}
}
} else {
output.emplace_back(); output.emplace_back();
retval &= detail::lexical_cast(elem, output.back()); retval &= detail::lexical_cast(elem, output.back());
} }
}
if(!retval) { if(!retval) {
throw ConversionError(get_name(), results_); throw ConversionError(get_name(), results_);
@ -874,20 +884,12 @@ class Option : public OptionBase<Option> {
} }
/// return the results as a particular type /// return the results as a particular type
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> T as() const { template <typename T> T as() const {
T output; T output;
results(output); results(output);
return output; return output;
} }
/// get the results as a vector of a particular type
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
T as(char delim = '\0') const {
T output;
results(output, delim);
return output;
}
/// See if the callback has been run already /// See if the callback has been run already
bool get_callback_run() const { return callback_run_; } bool get_callback_run() const { return callback_run_; }
@ -935,6 +937,28 @@ class Option : public OptionBase<Option> {
/// Get the typename for this option /// Get the typename for this option
std::string get_type_name() const { return type_name_(); } std::string get_type_name() const { return type_name_(); }
private:
int _add_result(std::string &&result) {
int count = 0;
if(delimiter_ == '\0') {
results_.push_back(std::move(result));
++count;
} else {
if((result.find_first_of(delimiter_) != std::string::npos)) {
for(const auto &var : CLI::detail::split(result, delimiter_)) {
if(!var.empty()) {
results_.push_back(var);
++count;
}
}
} else {
results_.push_back(std::move(result));
++count;
}
}
return count;
}
}; };
} // namespace CLI } // namespace CLI

View File

@ -1956,17 +1956,17 @@ TEST_F(TApp, CustomUserSepParse) {
std::vector<int> vals = {1, 2, 3}; std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,3"}; args = {"--idx", "1,2,3"};
auto opt = app.add_option("--idx", vals, "", ','); auto opt = app.add_option("--idx", vals)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
std::vector<int> vals2; std::vector<int> vals2;
// check that the results vector gets the results in the same way // check that the results vector gets the results in the same way
opt->results(vals2, ','); opt->results(vals2);
EXPECT_EQ(vals2, vals); EXPECT_EQ(vals2, vals);
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", true, ','); app.add_option("--idx", vals, "", true)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
} }
@ -1979,8 +1979,7 @@ TEST_F(TApp, DefaultUserSepParse) {
auto opt = app.add_option("--idx", vals, ""); auto opt = app.add_option("--idx", vals, "");
run(); run();
EXPECT_EQ(vals, std::vector<std::string>({"1 2 3", "4 5 6"})); EXPECT_EQ(vals, std::vector<std::string>({"1 2 3", "4 5 6"}));
app.remove_option(opt); opt->delimiter(',');
app.add_option("--idx", vals, "", true);
run(); run();
EXPECT_EQ(vals, std::vector<std::string>({"1 2 3", "4 5 6"})); EXPECT_EQ(vals, std::vector<std::string>({"1 2 3", "4 5 6"}));
} }
@ -1989,7 +1988,7 @@ TEST_F(TApp, DefaultUserSepParse) {
TEST_F(TApp, BadUserSepParse) { TEST_F(TApp, BadUserSepParse) {
std::vector<int> vals; std::vector<int> vals;
app.add_option("--idx", vals, ""); app.add_option("--idx", vals);
args = {"--idx", "1,2,3"}; args = {"--idx", "1,2,3"};
@ -2001,13 +2000,13 @@ TEST_F(TApp, CustomUserSepParse2) {
std::vector<int> vals = {1, 2, 3}; std::vector<int> vals = {1, 2, 3};
args = {"--idx", "1,2,"}; args = {"--idx", "1,2,"};
auto opt = app.add_option("--idx", vals, "", ','); auto opt = app.add_option("--idx", vals)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", true, ','); app.add_option("--idx", vals, "", true)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
} }
@ -2020,13 +2019,28 @@ TEST_F(TApp, CustomUserSepParseFunction) {
[&vals](std::vector<int> v) { [&vals](std::vector<int> v) {
vals = std::move(v); vals = std::move(v);
return true; return true;
}, })
"", ->delimiter(',');
',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
} }
// delimiter removal
TEST_F(TApp, CustomUserSepParseToggle) {
std::vector<std::string> vals;
args = {"--idx", "1,2,3"};
auto opt = app.add_option("--idx", vals)->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<std::string>({"1", "2", "3"}));
opt->delimiter('\0');
run();
EXPECT_EQ(vals, std::vector<std::string>({"1,2,3"}));
opt->delimiter(',');
run();
EXPECT_EQ(vals, std::vector<std::string>({"1", "2", "3"}));
}
// #209 // #209
TEST_F(TApp, CustomUserSepParse3) { TEST_F(TApp, CustomUserSepParse3) {
@ -2035,12 +2049,12 @@ TEST_F(TApp, CustomUserSepParse3) {
"1", "1",
"," ","
"2"}; "2"};
auto opt = app.add_option("--idx", vals, "", ','); auto opt = app.add_option("--idx", vals)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", false, ','); app.add_option("--idx", vals, "", false)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
} }
@ -2050,13 +2064,13 @@ TEST_F(TApp, CustomUserSepParse4) {
std::vector<int> vals; std::vector<int> vals;
args = {"--idx", "1, 2"}; args = {"--idx", "1, 2"};
auto opt = app.add_option("--idx", vals, "", ','); auto opt = app.add_option("--idx", vals)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
app.remove_option(opt); app.remove_option(opt);
app.add_option("--idx", vals, "", true, ','); app.add_option("--idx", vals, "", true)->delimiter(',');
run(); run();
EXPECT_EQ(vals, std::vector<int>({1, 2})); EXPECT_EQ(vals, std::vector<int>({1, 2}));
} }

View File

@ -77,6 +77,38 @@ TEST_F(TApp, BuiltinComplex) {
EXPECT_DOUBLE_EQ(3, comp.imag()); EXPECT_DOUBLE_EQ(3, comp.imag());
} }
TEST_F(TApp, BuiltinComplexWithDelimiter) {
cx comp{1, 2};
app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
args = {"-c", "4+3i"};
std::string help = app.help();
EXPECT_THAT(help, HasSubstr("1"));
EXPECT_THAT(help, HasSubstr("2"));
EXPECT_THAT(help, HasSubstr("COMPLEX"));
EXPECT_DOUBLE_EQ(1, comp.real());
EXPECT_DOUBLE_EQ(2, comp.imag());
run();
EXPECT_DOUBLE_EQ(4, comp.real());
EXPECT_DOUBLE_EQ(3, comp.imag());
args = {"-c", "5+-3i"};
run();
EXPECT_DOUBLE_EQ(5, comp.real());
EXPECT_DOUBLE_EQ(-3, comp.imag());
args = {"-c", "6", "-4i"};
run();
EXPECT_DOUBLE_EQ(6, comp.real());
EXPECT_DOUBLE_EQ(-4, comp.imag());
}
TEST_F(TApp, BuiltinComplexIgnoreI) { TEST_F(TApp, BuiltinComplexIgnoreI) {
cx comp{1, 2}; cx comp{1, 2};
app.add_complex("-c,--complex", comp); app.add_complex("-c,--complex", comp);