Skip to content

Commit 9f81385

Browse files
phlptphenryiii
authored andcommitted
Option delimiter (#240)
* move delimiter controls to Option vs in the App callbacks
1 parent 7254ea7 commit 9f81385

File tree

5 files changed

+151
-104
lines changed

5 files changed

+151
-104
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ Before parsing, you can set the following options:
273273
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
274274
- `->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
275275
- `->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.
276+
- `->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
276277
- `->description(str)`: Set/change the description.
277278
- `->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).
278279
- `->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
338339

339340
- `results()`: retrieves a vector of strings with all the results in the order they were given.
340341
- `results(variable_to_bind_to)`: gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
341-
- `results(vector_type_variable,delimiter)`: gets the results to a vector type and uses a delimiter to further split the values
342-
- `Value=as<type>()`: returns the result or default value directly as the specified type if possible.
343-
- `Vector_value=as<type>(delimiter): same the results function with the delimiter but returns the value directly.
342+
- `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.
344343

345344
### Subcommands
346345

@@ -434,7 +433,7 @@ arguments, use `.config_to_str(default_also=false, prefix="", write_description=
434433

435434
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.
436435

437-
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:
436+
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:
438437

439438
```cpp
440439
app.option_defaults()->required();

include/CLI/App.hpp

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -435,28 +435,20 @@ class App {
435435
return opt;
436436
}
437437

438-
/// Add option for vectors (no default)
438+
/// Add option for vectors
439439
template <typename T>
440440
Option *add_option(std::string option_name,
441441
std::vector<T> &variable, ///< The variable vector to set
442-
std::string option_description = "",
443-
char delimiter = '\0') {
442+
std::string option_description = "") {
444443

445-
CLI::callback_t fun = [&variable, delimiter](CLI::results_t res) {
444+
CLI::callback_t fun = [&variable](CLI::results_t res) {
446445
bool retval = true;
447446
variable.clear();
447+
variable.reserve(res.size());
448448
for(const auto &elem : res) {
449-
if((delimiter != '\0') && (elem.find_first_of(delimiter) != std::string::npos)) {
450-
for(const auto &var : CLI::detail::split(elem, delimiter)) {
451-
if(!var.empty()) {
452-
variable.emplace_back();
453-
retval &= detail::lexical_cast(var, variable.back());
454-
}
455-
}
456-
} else {
457-
variable.emplace_back();
458-
retval &= detail::lexical_cast(elem, variable.back());
459-
}
449+
450+
variable.emplace_back();
451+
retval &= detail::lexical_cast(elem, variable.back());
460452
}
461453
return (!variable.empty()) && retval;
462454
};
@@ -466,35 +458,28 @@ class App {
466458
return opt;
467459
}
468460

469-
/// Add option for vectors
461+
/// Add option for vectors with defaulted argument
470462
template <typename T>
471463
Option *add_option(std::string option_name,
472464
std::vector<T> &variable, ///< The variable vector to set
473465
std::string option_description,
474-
bool defaulted,
475-
char delimiter = '\0') {
466+
bool defaulted) {
476467

477-
CLI::callback_t fun = [&variable, delimiter](CLI::results_t res) {
468+
CLI::callback_t fun = [&variable](CLI::results_t res) {
478469
bool retval = true;
479470
variable.clear();
471+
variable.reserve(res.size());
480472
for(const auto &elem : res) {
481-
if((delimiter != '\0') && (elem.find_first_of(delimiter) != std::string::npos)) {
482-
for(const auto &var : CLI::detail::split(elem, delimiter)) {
483-
if(!var.empty()) {
484-
variable.emplace_back();
485-
retval &= detail::lexical_cast(var, variable.back());
486-
}
487-
}
488-
} else {
489-
variable.emplace_back();
490-
retval &= detail::lexical_cast(elem, variable.back());
491-
}
473+
474+
variable.emplace_back();
475+
retval &= detail::lexical_cast(elem, variable.back());
492476
}
493477
return (!variable.empty()) && retval;
494478
};
495479

496480
Option *opt = add_option(option_name, fun, option_description, defaulted);
497481
opt->type_name(detail::type_name<T>())->type_size(-1);
482+
498483
if(defaulted)
499484
opt->default_str("[" + detail::join(variable) + "]");
500485
return opt;
@@ -504,25 +489,15 @@ class App {
504489
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
505490
Option *add_option_function(std::string option_name,
506491
const std::function<bool(const T &)> &func, ///< the callback to execute
507-
std::string option_description = "",
508-
char delimiter = '\0') {
492+
std::string option_description = "") {
509493

510-
CLI::callback_t fun = [func, delimiter](CLI::results_t res) {
494+
CLI::callback_t fun = [func](CLI::results_t res) {
511495
T values;
512496
bool retval = true;
513497
values.reserve(res.size());
514498
for(const auto &elem : res) {
515-
if((delimiter != '\0') && (elem.find_first_of(delimiter) != std::string::npos)) {
516-
for(const auto &var : CLI::detail::split(elem, delimiter)) {
517-
if(!var.empty()) {
518-
values.emplace_back();
519-
retval &= detail::lexical_cast(var, values.back());
520-
}
521-
}
522-
} else {
523-
values.emplace_back();
524-
retval &= detail::lexical_cast(elem, values.back());
525-
}
499+
values.emplace_back();
500+
retval &= detail::lexical_cast(elem, values.back());
526501
}
527502
if(retval) {
528503
return func(values);
@@ -2032,27 +2007,30 @@ class App {
20322007
// Make sure we always eat the minimum for unlimited vectors
20332008
int collected = 0;
20342009
// deal with flag like things
2010+
int count = 0;
20352011
if(num == 0) {
20362012
auto res = op->get_flag_value(arg_name, value);
20372013
op->add_result(res);
20382014
parse_order_.push_back(op.get());
20392015
}
20402016
// --this=value
20412017
else if(!value.empty()) {
2018+
op->add_result(value, count);
2019+
parse_order_.push_back(op.get());
2020+
collected += count;
20422021
// If exact number expected
20432022
if(num > 0)
2044-
num--;
2045-
op->add_result(value);
2046-
parse_order_.push_back(op.get());
2047-
collected += 1;
2023+
num = (num >= count) ? num - count : 0;
2024+
20482025
// -Trest
20492026
} else if(!rest.empty()) {
2050-
if(num > 0)
2051-
num--;
2052-
op->add_result(rest);
2027+
op->add_result(rest, count);
20532028
parse_order_.push_back(op.get());
20542029
rest = "";
2055-
collected += 1;
2030+
collected += count;
2031+
// If exact number expected
2032+
if(num > 0)
2033+
num = (num >= count) ? num - count : 0;
20562034
}
20572035

20582036
// Unlimited vector parser
@@ -2065,10 +2043,10 @@ class App {
20652043
if(_count_remaining_positionals() > 0)
20662044
break;
20672045
}
2068-
op->add_result(args.back());
2046+
op->add_result(args.back(), count);
20692047
parse_order_.push_back(op.get());
20702048
args.pop_back();
2071-
collected++;
2049+
collected += count;
20722050
}
20732051

20742052
// Allow -- to end an unlimited list and "eat" it
@@ -2077,11 +2055,11 @@ class App {
20772055

20782056
} else {
20792057
while(num > 0 && !args.empty()) {
2080-
num--;
20812058
std::string current_ = args.back();
20822059
args.pop_back();
2083-
op->add_result(current_);
2060+
op->add_result(current_, count);
20842061
parse_order_.push_back(op.get());
2062+
num -= count;
20852063
}
20862064

20872065
if(num > 0) {

include/CLI/Option.hpp

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ template <typename CRTP> class OptionBase {
5252
bool configurable_{true};
5353
/// Disable overriding flag values with '=value'
5454
bool disable_flag_override_{false};
55-
55+
/// Specify a delimiter character for vector arguments
56+
char delimiter_{'\0'};
5657
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
5758
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
5859

@@ -64,6 +65,7 @@ template <typename CRTP> class OptionBase {
6465
other->ignore_underscore(ignore_underscore_);
6566
other->configurable(configurable_);
6667
other->disable_flag_override(disable_flag_override_);
68+
other->delimiter(delimiter_);
6769
other->multi_option_policy(multi_option_policy_);
6870
}
6971

@@ -106,6 +108,7 @@ template <typename CRTP> class OptionBase {
106108
/// The status of configurable
107109
bool get_disable_flag_override() const { return disable_flag_override_; }
108110

111+
char get_delimiter() const { return delimiter_; }
109112
/// The status of the multi option policy
110113
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
111114

@@ -137,6 +140,12 @@ template <typename CRTP> class OptionBase {
137140
configurable_ = value;
138141
return static_cast<CRTP *>(this);
139142
}
143+
144+
/// Allow in a configuration file
145+
CRTP *delimiter(char value = '\0') {
146+
delimiter_ = value;
147+
return static_cast<CRTP *>(this);
148+
}
140149
};
141150

142151
/// This is a version of OptionBase that only supports setting values,
@@ -165,11 +174,17 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
165174
return this;
166175
}
167176

168-
/// Ignore underscores in the option name
177+
/// Disable overriding flag values with an '=<value>' segment
169178
OptionDefaults *disable_flag_override(bool value = true) {
170179
disable_flag_override_ = value;
171180
return this;
172181
}
182+
183+
/// set a delimiter character to split up single arguments to treat as multiple inputs
184+
OptionDefaults *delimiter(char value = '\0') {
185+
delimiter_ = value;
186+
return this;
187+
}
173188
};
174189

175190
class Option : public OptionBase<Option> {
@@ -793,19 +808,23 @@ class Option : public OptionBase<Option> {
793808

794809
/// Puts a result at the end
795810
Option *add_result(std::string s) {
796-
results_.push_back(std::move(s));
811+
_add_result(std::move(s));
812+
callback_run_ = false;
813+
return this;
814+
}
815+
816+
/// Puts a result at the end and get a count of the number of arguments actually added
817+
Option *add_result(std::string s, int &count) {
818+
count = _add_result(std::move(s));
797819
callback_run_ = false;
798820
return this;
799821
}
800822

801823
/// Puts a result at the end
802824
Option *add_result(std::vector<std::string> s) {
803-
if(results_.empty()) {
804-
results_ = std::move(s);
805-
} else {
806-
results_.insert(results_.end(), s.begin(), s.end());
825+
for(auto &str : s) {
826+
_add_result(std::move(str));
807827
}
808-
809828
callback_run_ = false;
810829
return this;
811830
}
@@ -850,22 +869,13 @@ class Option : public OptionBase<Option> {
850869
}
851870
}
852871
/// get the results as a vector of a particular type
853-
template <typename T> void results(std::vector<T> &output, char delim = '\0') const {
872+
template <typename T> void results(std::vector<T> &output) const {
854873
output.clear();
855874
bool retval = true;
856875

857876
for(const auto &elem : results_) {
858-
if(delim != '\0') {
859-
for(const auto &var : CLI::detail::split(elem, delim)) {
860-
if(!var.empty()) {
861-
output.emplace_back();
862-
retval &= detail::lexical_cast(var, output.back());
863-
}
864-
}
865-
} else {
866-
output.emplace_back();
867-
retval &= detail::lexical_cast(elem, output.back());
868-
}
877+
output.emplace_back();
878+
retval &= detail::lexical_cast(elem, output.back());
869879
}
870880

871881
if(!retval) {
@@ -874,20 +884,12 @@ class Option : public OptionBase<Option> {
874884
}
875885

876886
/// return the results as a particular type
877-
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> T as() const {
887+
template <typename T> T as() const {
878888
T output;
879889
results(output);
880890
return output;
881891
}
882892

883-
/// get the results as a vector of a particular type
884-
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
885-
T as(char delim = '\0') const {
886-
T output;
887-
results(output, delim);
888-
return output;
889-
}
890-
891893
/// See if the callback has been run already
892894
bool get_callback_run() const { return callback_run_; }
893895

@@ -935,6 +937,28 @@ class Option : public OptionBase<Option> {
935937

936938
/// Get the typename for this option
937939
std::string get_type_name() const { return type_name_(); }
940+
941+
private:
942+
int _add_result(std::string &&result) {
943+
int count = 0;
944+
if(delimiter_ == '\0') {
945+
results_.push_back(std::move(result));
946+
++count;
947+
} else {
948+
if((result.find_first_of(delimiter_) != std::string::npos)) {
949+
for(const auto &var : CLI::detail::split(result, delimiter_)) {
950+
if(!var.empty()) {
951+
results_.push_back(var);
952+
++count;
953+
}
954+
}
955+
} else {
956+
results_.push_back(std::move(result));
957+
++count;
958+
}
959+
}
960+
return count;
961+
}
938962
};
939963

940964
} // namespace CLI

0 commit comments

Comments
 (0)