Commit 9f81385bcd14e6dcd741fae6396713582a332f56

Authored by Philip Top
Committed by Henry Schreiner
1 parent 7254ea79

Option delimiter (#240)

* move delimiter controls to Option vs in the App callbacks
README.md
... ... @@ -273,6 +273,7 @@ Before parsing, you can set the following options:
273 273 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
274 274 - `->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
275 275 - `->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
276 277 - `->description(str)`: Set/change the description.
277 278 - `->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).
278 279 - `->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
338 339  
339 340 - `results()`: retrieves a vector of strings with all the results in the order they were given.
340 341 - `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.
344 343  
345 344 ### Subcommands
346 345  
... ... @@ -434,7 +433,7 @@ arguments, use `.config_to_str(default_also=false, prefix=&quot;&quot;, write_description=
434 433  
435 434 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.
436 435  
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:
438 437  
439 438 ```cpp
440 439 app.option_defaults()->required();
... ...
include/CLI/App.hpp
... ... @@ -435,28 +435,20 @@ class App {
435 435 return opt;
436 436 }
437 437  
438   - /// Add option for vectors (no default)
  438 + /// Add option for vectors
439 439 template <typename T>
440 440 Option *add_option(std::string option_name,
441 441 std::vector<T> &variable, ///< The variable vector to set
442   - std::string option_description = "",
443   - char delimiter = '\0') {
  442 + std::string option_description = "") {
444 443  
445   - CLI::callback_t fun = [&variable, delimiter](CLI::results_t res) {
  444 + CLI::callback_t fun = [&variable](CLI::results_t res) {
446 445 bool retval = true;
447 446 variable.clear();
  447 + variable.reserve(res.size());
448 448 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());
460 452 }
461 453 return (!variable.empty()) && retval;
462 454 };
... ... @@ -466,35 +458,28 @@ class App {
466 458 return opt;
467 459 }
468 460  
469   - /// Add option for vectors
  461 + /// Add option for vectors with defaulted argument
470 462 template <typename T>
471 463 Option *add_option(std::string option_name,
472 464 std::vector<T> &variable, ///< The variable vector to set
473 465 std::string option_description,
474   - bool defaulted,
475   - char delimiter = '\0') {
  466 + bool defaulted) {
476 467  
477   - CLI::callback_t fun = [&variable, delimiter](CLI::results_t res) {
  468 + CLI::callback_t fun = [&variable](CLI::results_t res) {
478 469 bool retval = true;
479 470 variable.clear();
  471 + variable.reserve(res.size());
480 472 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());
492 476 }
493 477 return (!variable.empty()) && retval;
494 478 };
495 479  
496 480 Option *opt = add_option(option_name, fun, option_description, defaulted);
497 481 opt->type_name(detail::type_name<T>())->type_size(-1);
  482 +
498 483 if(defaulted)
499 484 opt->default_str("[" + detail::join(variable) + "]");
500 485 return opt;
... ... @@ -504,25 +489,15 @@ class App {
504 489 template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
505 490 Option *add_option_function(std::string option_name,
506 491 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 = "") {
509 493  
510   - CLI::callback_t fun = [func, delimiter](CLI::results_t res) {
  494 + CLI::callback_t fun = [func](CLI::results_t res) {
511 495 T values;
512 496 bool retval = true;
513 497 values.reserve(res.size());
514 498 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());
526 501 }
527 502 if(retval) {
528 503 return func(values);
... ... @@ -2032,6 +2007,7 @@ class App {
2032 2007 // Make sure we always eat the minimum for unlimited vectors
2033 2008 int collected = 0;
2034 2009 // deal with flag like things
  2010 + int count = 0;
2035 2011 if(num == 0) {
2036 2012 auto res = op->get_flag_value(arg_name, value);
2037 2013 op->add_result(res);
... ... @@ -2039,20 +2015,22 @@ class App {
2039 2015 }
2040 2016 // --this=value
2041 2017 else if(!value.empty()) {
  2018 + op->add_result(value, count);
  2019 + parse_order_.push_back(op.get());
  2020 + collected += count;
2042 2021 // If exact number expected
2043 2022 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 +
2048 2025 // -Trest
2049 2026 } else if(!rest.empty()) {
2050   - if(num > 0)
2051   - num--;
2052   - op->add_result(rest);
  2027 + op->add_result(rest, count);
2053 2028 parse_order_.push_back(op.get());
2054 2029 rest = "";
2055   - collected += 1;
  2030 + collected += count;
  2031 + // If exact number expected
  2032 + if(num > 0)
  2033 + num = (num >= count) ? num - count : 0;
2056 2034 }
2057 2035  
2058 2036 // Unlimited vector parser
... ... @@ -2065,10 +2043,10 @@ class App {
2065 2043 if(_count_remaining_positionals() > 0)
2066 2044 break;
2067 2045 }
2068   - op->add_result(args.back());
  2046 + op->add_result(args.back(), count);
2069 2047 parse_order_.push_back(op.get());
2070 2048 args.pop_back();
2071   - collected++;
  2049 + collected += count;
2072 2050 }
2073 2051  
2074 2052 // Allow -- to end an unlimited list and "eat" it
... ... @@ -2077,11 +2055,11 @@ class App {
2077 2055  
2078 2056 } else {
2079 2057 while(num > 0 && !args.empty()) {
2080   - num--;
2081 2058 std::string current_ = args.back();
2082 2059 args.pop_back();
2083   - op->add_result(current_);
  2060 + op->add_result(current_, count);
2084 2061 parse_order_.push_back(op.get());
  2062 + num -= count;
2085 2063 }
2086 2064  
2087 2065 if(num > 0) {
... ...
include/CLI/Option.hpp
... ... @@ -52,7 +52,8 @@ template &lt;typename CRTP&gt; class OptionBase {
52 52 bool configurable_{true};
53 53 /// Disable overriding flag values with '=value'
54 54 bool disable_flag_override_{false};
55   -
  55 + /// Specify a delimiter character for vector arguments
  56 + char delimiter_{'\0'};
56 57 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
57 58 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
58 59  
... ... @@ -64,6 +65,7 @@ template &lt;typename CRTP&gt; class OptionBase {
64 65 other->ignore_underscore(ignore_underscore_);
65 66 other->configurable(configurable_);
66 67 other->disable_flag_override(disable_flag_override_);
  68 + other->delimiter(delimiter_);
67 69 other->multi_option_policy(multi_option_policy_);
68 70 }
69 71  
... ... @@ -106,6 +108,7 @@ template &lt;typename CRTP&gt; class OptionBase {
106 108 /// The status of configurable
107 109 bool get_disable_flag_override() const { return disable_flag_override_; }
108 110  
  111 + char get_delimiter() const { return delimiter_; }
109 112 /// The status of the multi option policy
110 113 MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
111 114  
... ... @@ -137,6 +140,12 @@ template &lt;typename CRTP&gt; class OptionBase {
137 140 configurable_ = value;
138 141 return static_cast<CRTP *>(this);
139 142 }
  143 +
  144 + /// Allow in a configuration file
  145 + CRTP *delimiter(char value = '\0') {
  146 + delimiter_ = value;
  147 + return static_cast<CRTP *>(this);
  148 + }
140 149 };
141 150  
142 151 /// This is a version of OptionBase that only supports setting values,
... ... @@ -165,11 +174,17 @@ class OptionDefaults : public OptionBase&lt;OptionDefaults&gt; {
165 174 return this;
166 175 }
167 176  
168   - /// Ignore underscores in the option name
  177 + /// Disable overriding flag values with an '=<value>' segment
169 178 OptionDefaults *disable_flag_override(bool value = true) {
170 179 disable_flag_override_ = value;
171 180 return this;
172 181 }
  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 + }
173 188 };
174 189  
175 190 class Option : public OptionBase<Option> {
... ... @@ -793,19 +808,23 @@ class Option : public OptionBase&lt;Option&gt; {
793 808  
794 809 /// Puts a result at the end
795 810 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));
797 819 callback_run_ = false;
798 820 return this;
799 821 }
800 822  
801 823 /// Puts a result at the end
802 824 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));
807 827 }
808   -
809 828 callback_run_ = false;
810 829 return this;
811 830 }
... ... @@ -850,22 +869,13 @@ class Option : public OptionBase&lt;Option&gt; {
850 869 }
851 870 }
852 871 /// 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 {
854 873 output.clear();
855 874 bool retval = true;
856 875  
857 876 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());
869 879 }
870 880  
871 881 if(!retval) {
... ... @@ -874,20 +884,12 @@ class Option : public OptionBase&lt;Option&gt; {
874 884 }
875 885  
876 886 /// 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 {
878 888 T output;
879 889 results(output);
880 890 return output;
881 891 }
882 892  
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   -
891 893 /// See if the callback has been run already
892 894 bool get_callback_run() const { return callback_run_; }
893 895  
... ... @@ -935,6 +937,28 @@ class Option : public OptionBase&lt;Option&gt; {
935 937  
936 938 /// Get the typename for this option
937 939 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 + }
938 962 };
939 963  
940 964 } // namespace CLI
... ...
tests/AppTest.cpp
... ... @@ -1956,17 +1956,17 @@ TEST_F(TApp, CustomUserSepParse) {
1956 1956  
1957 1957 std::vector<int> vals = {1, 2, 3};
1958 1958 args = {"--idx", "1,2,3"};
1959   - auto opt = app.add_option("--idx", vals, "", ',');
  1959 + auto opt = app.add_option("--idx", vals)->delimiter(',');
1960 1960 run();
1961 1961 EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
1962 1962 std::vector<int> vals2;
1963 1963 // check that the results vector gets the results in the same way
1964   - opt->results(vals2, ',');
  1964 + opt->results(vals2);
1965 1965 EXPECT_EQ(vals2, vals);
1966 1966  
1967 1967 app.remove_option(opt);
1968 1968  
1969   - app.add_option("--idx", vals, "", true, ',');
  1969 + app.add_option("--idx", vals, "", true)->delimiter(',');
1970 1970 run();
1971 1971 EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
1972 1972 }
... ... @@ -1979,8 +1979,7 @@ TEST_F(TApp, DefaultUserSepParse) {
1979 1979 auto opt = app.add_option("--idx", vals, "");
1980 1980 run();
1981 1981 EXPECT_EQ(vals, std::vector<std::string>({"1 2 3", "4 5 6"}));
1982   - app.remove_option(opt);
1983   - app.add_option("--idx", vals, "", true);
  1982 + opt->delimiter(',');
1984 1983 run();
1985 1984 EXPECT_EQ(vals, std::vector<std::string>({"1 2 3", "4 5 6"}));
1986 1985 }
... ... @@ -1989,7 +1988,7 @@ TEST_F(TApp, DefaultUserSepParse) {
1989 1988 TEST_F(TApp, BadUserSepParse) {
1990 1989  
1991 1990 std::vector<int> vals;
1992   - app.add_option("--idx", vals, "");
  1991 + app.add_option("--idx", vals);
1993 1992  
1994 1993 args = {"--idx", "1,2,3"};
1995 1994  
... ... @@ -2001,13 +2000,13 @@ TEST_F(TApp, CustomUserSepParse2) {
2001 2000  
2002 2001 std::vector<int> vals = {1, 2, 3};
2003 2002 args = {"--idx", "1,2,"};
2004   - auto opt = app.add_option("--idx", vals, "", ',');
  2003 + auto opt = app.add_option("--idx", vals)->delimiter(',');
2005 2004 run();
2006 2005 EXPECT_EQ(vals, std::vector<int>({1, 2}));
2007 2006  
2008 2007 app.remove_option(opt);
2009 2008  
2010   - app.add_option("--idx", vals, "", true, ',');
  2009 + app.add_option("--idx", vals, "", true)->delimiter(',');
2011 2010 run();
2012 2011 EXPECT_EQ(vals, std::vector<int>({1, 2}));
2013 2012 }
... ... @@ -2020,13 +2019,28 @@ TEST_F(TApp, CustomUserSepParseFunction) {
2020 2019 [&vals](std::vector<int> v) {
2021 2020 vals = std::move(v);
2022 2021 return true;
2023   - },
2024   - "",
2025   - ',');
  2022 + })
  2023 + ->delimiter(',');
2026 2024 run();
2027 2025 EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
2028 2026 }
2029 2027  
  2028 +// delimiter removal
  2029 +TEST_F(TApp, CustomUserSepParseToggle) {
  2030 +
  2031 + std::vector<std::string> vals;
  2032 + args = {"--idx", "1,2,3"};
  2033 + auto opt = app.add_option("--idx", vals)->delimiter(',');
  2034 + run();
  2035 + EXPECT_EQ(vals, std::vector<std::string>({"1", "2", "3"}));
  2036 + opt->delimiter('\0');
  2037 + run();
  2038 + EXPECT_EQ(vals, std::vector<std::string>({"1,2,3"}));
  2039 + opt->delimiter(',');
  2040 + run();
  2041 + EXPECT_EQ(vals, std::vector<std::string>({"1", "2", "3"}));
  2042 +}
  2043 +
2030 2044 // #209
2031 2045 TEST_F(TApp, CustomUserSepParse3) {
2032 2046  
... ... @@ -2035,12 +2049,12 @@ TEST_F(TApp, CustomUserSepParse3) {
2035 2049 "1",
2036 2050 ","
2037 2051 "2"};
2038   - auto opt = app.add_option("--idx", vals, "", ',');
  2052 + auto opt = app.add_option("--idx", vals)->delimiter(',');
2039 2053 run();
2040 2054 EXPECT_EQ(vals, std::vector<int>({1, 2}));
2041 2055 app.remove_option(opt);
2042 2056  
2043   - app.add_option("--idx", vals, "", false, ',');
  2057 + app.add_option("--idx", vals, "", false)->delimiter(',');
2044 2058 run();
2045 2059 EXPECT_EQ(vals, std::vector<int>({1, 2}));
2046 2060 }
... ... @@ -2050,13 +2064,13 @@ TEST_F(TApp, CustomUserSepParse4) {
2050 2064  
2051 2065 std::vector<int> vals;
2052 2066 args = {"--idx", "1, 2"};
2053   - auto opt = app.add_option("--idx", vals, "", ',');
  2067 + auto opt = app.add_option("--idx", vals)->delimiter(',');
2054 2068 run();
2055 2069 EXPECT_EQ(vals, std::vector<int>({1, 2}));
2056 2070  
2057 2071 app.remove_option(opt);
2058 2072  
2059   - app.add_option("--idx", vals, "", true, ',');
  2073 + app.add_option("--idx", vals, "", true)->delimiter(',');
2060 2074 run();
2061 2075 EXPECT_EQ(vals, std::vector<int>({1, 2}));
2062 2076 }
... ...
tests/NewParseTest.cpp
... ... @@ -77,6 +77,38 @@ TEST_F(TApp, BuiltinComplex) {
77 77 EXPECT_DOUBLE_EQ(3, comp.imag());
78 78 }
79 79  
  80 +TEST_F(TApp, BuiltinComplexWithDelimiter) {
  81 + cx comp{1, 2};
  82 + app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
  83 +
  84 + args = {"-c", "4+3i"};
  85 +
  86 + std::string help = app.help();
  87 + EXPECT_THAT(help, HasSubstr("1"));
  88 + EXPECT_THAT(help, HasSubstr("2"));
  89 + EXPECT_THAT(help, HasSubstr("COMPLEX"));
  90 +
  91 + EXPECT_DOUBLE_EQ(1, comp.real());
  92 + EXPECT_DOUBLE_EQ(2, comp.imag());
  93 +
  94 + run();
  95 +
  96 + EXPECT_DOUBLE_EQ(4, comp.real());
  97 + EXPECT_DOUBLE_EQ(3, comp.imag());
  98 +
  99 + args = {"-c", "5+-3i"};
  100 + run();
  101 +
  102 + EXPECT_DOUBLE_EQ(5, comp.real());
  103 + EXPECT_DOUBLE_EQ(-3, comp.imag());
  104 +
  105 + args = {"-c", "6", "-4i"};
  106 + run();
  107 +
  108 + EXPECT_DOUBLE_EQ(6, comp.real());
  109 + EXPECT_DOUBLE_EQ(-4, comp.imag());
  110 +}
  111 +
80 112 TEST_F(TApp, BuiltinComplexIgnoreI) {
81 113 cx comp{1, 2};
82 114 app.add_complex("-c,--complex", comp);
... ...