Commit 418b7175f5223aa28ab65744c76132fd05fd8f39

Authored by Philip Top
Committed by Henry Schreiner
1 parent 41d3c967

Type size refactor (#325)

* add expanded type_size specification

* add some more checks for type_size_max

* continued work on getting type sizes more flexible

* make some more tweaks to option to split up validate and reduce sections

* git rid of exceptions on the type_size functions exceptions,  allow any number to be entered for the min and max and don't make a distinction between flags and other types.

* add expected count

* add the allow extra args flag in an option

* start working in allow_extra_args

* write some stuff in the book,  and continue working on the failing test cases

* fix a few more of the helpers tests

* a few more test cases running

* all tests pass, fixing calls in ini files

* get vector<pair> working and all tests passing

* change callback to use reference to remove allocation and copy operation

* add support and test for vector<vector<X>>

* change Validators_ to validators_ for consistency

* fix linux warnings and errors by reording some templates and adding some typename keywords

* add support for std::vector<X> as the cross conversion type so optional<std::vector<X>> is supported using the full template of add_option.

* a few more test cases to take care of some coverage gaps

* add missing parenthesis

* add some more tests for coverage gaps

* add test for flag like option

* add transform test for `as<X>` function and make it pass through the defaults

* add a few more tests and have vector default string interpreted correctly.

* add test for defaulted integer,  and route default string for defaulted value which would otherwise be empty

* some code cleanup and comments and few more test coverage gap tests

* add more tests and fix a few bugs on the type size and different code paths

* remove path in results by fixing the clear of options so they go back to parsing state.

* get coverage back to 100%

* clang_tidy, and codacy fixes

* reorder the lexical_conversion definitions

* update some formatting

* update whitespace on book chapter
book/chapters/options.md
@@ -72,23 +72,26 @@ When you call `add_option`, you get a pointer to the added option. You can use t @@ -72,23 +72,26 @@ When you call `add_option`, you get a pointer to the added option. You can use t
72 72
73 | Modifier | Description | 73 | Modifier | Description |
74 |----------|-------------| 74 |----------|-------------|
75 -| `->required()`| The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. |  
76 -| `->expected(N)`| Take `N` values instead of as many as possible, only for vector args.|  
77 -| `->needs(opt)`| This option requires another option to also be present, opt is an `Option` pointer.|  
78 -| `->excludes(opt)`| This option cannot be given with `opt` present, opt is an `Option` pointer.|  
79 -| `->envname(name)`| Gets the value from the environment if present and not passed on the command line.|  
80 -| `->group(name)`| The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print.|  
81 -| `->ignore_case()`| Ignore the case on the command line (also works on subcommands, does not affect arguments).|  
82 -| `->ignore_underscore()`| Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7).|  
83 -| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy if 1 argument expected but this was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, and `Join` are also available. See the next three lines for shortcuts to set this more easily. |  
84 -| `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`.| 75 +| `->required()` | The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. |
  76 +| `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. |
  77 +| `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. |
  78 +| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. |
  79 +| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. |
  80 +| `->envname(name)` | Gets the value from the environment if present and not passed on the command line. |
  81 +| `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. |
  82 +| `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). |
  83 +| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7). |
  84 +| `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
  85 +| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next three lines for shortcuts to set this more easily. |
  86 +| `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. |
85 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | 87 | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
86 -| `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines to join all arguments into a single string output. |  
87 -| `->check(CLI::ExistingFile)`| Requires that the file exists if given.|  
88 -| `->check(CLI::ExistingDirectory)`| Requires that the directory exists.|  
89 -| `->check(CLI::NonexistentPath)`| Requires that the path does not exist.|  
90 -| `->check(CLI::Range(min,max))`| Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.|  
91 -| `->each(void(std::string))` | Run a function on each parsed value, *in order*.| 88 +| `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. |
  89 +| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. |
  90 +| `->check(CLI::ExistingFile)` | Requires that the file exists if given. |
  91 +| `->check(CLI::ExistingDirectory)` | Requires that the directory exists. |
  92 +| `->check(CLI::NonexistentPath)` | Requires that the path does not exist. |
  93 +| `->check(CLI::Range(min,max))` | Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. |
  94 +| `->each(void(std::string))` | Run a function on each parsed value, *in order*. |
92 95
93 The `->check(...)` modifiers adds a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed. 96 The `->check(...)` modifiers adds a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
94 97
@@ -101,7 +104,7 @@ CLI::Option* opt = app.add_flag(&quot;--opt&quot;); @@ -101,7 +104,7 @@ CLI::Option* opt = app.add_flag(&quot;--opt&quot;);
101 104
102 CLI11_PARSE(app, argv, argc); 105 CLI11_PARSE(app, argv, argc);
103 106
104 -if(*opt) 107 +if(* opt)
105 std::cout << "Flag recieved " << opt->count() << " times." << std::endl; 108 std::cout << "Flag recieved " << opt->count() << " times." << std::endl;
106 ``` 109 ```
107 110
@@ -109,10 +112,10 @@ if(*opt) @@ -109,10 +112,10 @@ if(*opt)
109 112
110 One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are four settings that can be set and inherited: 113 One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are four settings that can be set and inherited:
111 114
112 -* `group`: The group name starts as "Options"  
113 -* `required`: If the option must be given. Defaults to `false`. Is ignored for flags.  
114 -* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag.  
115 -* `ignore_case`: Allow any mixture of cases for the option or flag name 115 +* `group`: The group name starts as "Options"
  116 +* `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
  117 +* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag.
  118 +* `ignore_case`: Allow any mixture of cases for the option or flag name
116 119
117 An example of usage: 120 An example of usage:
118 121
@@ -148,36 +151,68 @@ std::complex&lt;float&gt; val; @@ -148,36 +151,68 @@ std::complex&lt;float&gt; val;
148 app.add_complex("--cplx", val); 151 app.add_complex("--cplx", val);
149 ``` 152 ```
150 153
151 -### Optionals (New in CLI11 1.5) 154 +### Windows style options (New in CLI11 1.7)
  155 +
  156 +You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line:
  157 +
  158 +* `/a` (flag)
  159 +* `/f filename` (option)
  160 +* `/long` (long flag)
  161 +* `/file filename` (space)
  162 +* `/file:filename` (colon)
  163 +
  164 +Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manor as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined.
152 165
153 -If you have a compiler with `__has_include`, you can use `std::optional`, `std::experimental::optional`, and `boost::optional` in `add_option`. You can manually enforce support for one of these by defining the corresponding macro before including CLI11 (or in your build system). For example: 166 +## Parse configuration
154 167
  168 +How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse.
  169 +
  170 +### examples
  171 +How options manage this is best illustrated through some examples
155 ```cpp 172 ```cpp
156 -#define CLI11_BOOST_OPTIONAL  
157 -#include <CLI/CLI.hpp> 173 +std::string val;
  174 +app.add_option("--opt",val,"description");
  175 +```
  176 +creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size of 1. meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types.
  177 +
  178 +now for example
  179 +```cpp
  180 +std::pair<int, std::string> val;
  181 +app.add_option("--opt",val,"description");
  182 +```
158 183
159 -... 184 +In this case the typesize is automatically detected to be 2 instead of 1, so the parsing would expect 2 arguments associated with the option.
160 185
161 -boost::optional<int> x;  
162 -app.add_option("-x", x); 186 +```cpp
  187 +std::vector<int> val;
  188 +app.add_option("--opt",val,"description");
  189 +```
163 190
164 -CLI11_PARSE(app, argc, argv); 191 +detects a type size of 1, since the underlying element type is a single string, so the minimum number of strings is 1. But since it is a vector the expected number can be very big. The default for a vector is (1<<30), and the allow_extra_args is set to true. This means that at least 1 argument is expected to follow the option, but arbitrary numbers of arguments may follow. These are checked if they have the form of an option but if not they are added to the argument.
165 192
166 -if(x)  
167 - std::cout << *x << std::endl; 193 +```cpp
  194 +std::vector<std::tuple<int, double, std::string>> val;
  195 +app.add_option("--opt",val,"description");
168 ``` 196 ```
  197 +gets into the complicated cases where the type size is now 3. and the expected max is set to a large number and `allow_extra_args` is set to true. In this case at least 3 arguments are required to follow the option, and subsequent groups must come in groups of three, otherwise an error will result.
169 198
170 -### Windows style options (New in CLI11 1.7) 199 +```cpp
  200 +bool val;
  201 +app.add_flag("--opt",val,"description");
  202 +```
171 203
172 -You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line: 204 +Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed.
173 205
174 -* `/a` (flag)  
175 -* `/f filename` (option)  
176 -* `/long` (long flag)  
177 -* `/file filename` (space)  
178 -* `/file:filename` (colon) 206 +### Customization
179 207
180 -Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manor as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined. 208 +The `type_size(N)`, `type_size(Nmin, Nmax)`, `expected(N)`, `expected(Nmin,Nmax)`, and `allow_extra_args()` can be used to customize an option. For example
  209 +
  210 +```cpp
  211 +std::string val;
  212 +auto opt=app.add_flag("--opt{vvv}",val,"description");
  213 +opt->expected(0,1);
  214 +```
  215 +will create a hybrid option, that can exist on its own in which case the value "vvv" is used or if a value is given that value will be used.
181 216
182 [^1]: For example, enums are not printable to `std::cout`. 217 [^1]: For example, enums are not printable to `std::cout`.
183 [^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values. 218 [^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values.
include/CLI/App.hpp
@@ -554,27 +554,29 @@ class App { @@ -554,27 +554,29 @@ class App {
554 // LCOV_EXCL_END 554 // LCOV_EXCL_END
555 } 555 }
556 556
557 - /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)  
558 -  
559 - template <typename T, typename XC = T, enable_if_t<!std::is_const<XC>::value, detail::enabler> = detail::dummy> 557 + /// Add option for assigning to a variable
  558 + template <typename AssignTo,
  559 + typename ConvertTo = AssignTo,
  560 + enable_if_t<!std::is_const<ConvertTo>::value, detail::enabler> = detail::dummy>
560 Option *add_option(std::string option_name, 561 Option *add_option(std::string option_name,
561 - T &variable, ///< The variable to set 562 + AssignTo &variable, ///< The variable to set
562 std::string option_description = "", 563 std::string option_description = "",
563 bool defaulted = false) { 564 bool defaulted = false) {
564 565
565 - auto fun = [&variable](CLI::results_t res) { // comment for spacing  
566 - return detail::lexical_conversion<T, XC>(res, variable); 566 + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing
  567 + return detail::lexical_conversion<AssignTo, ConvertTo>(res, variable);
567 }; 568 };
568 569
569 Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { 570 Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
570 - return CLI::detail::checked_to_string<T, XC>(variable); 571 + return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable);
571 }); 572 });
572 - opt->type_name(detail::type_name<XC>());  
573 - // these must be actual variable since (std::max) sometimes is defined in terms of references and references 573 + opt->type_name(detail::type_name<ConvertTo>());
  574 + // these must be actual variables since (std::max) sometimes is defined in terms of references and references
574 // to structs used in the evaluation can be temporary so that would cause issues. 575 // to structs used in the evaluation can be temporary so that would cause issues.
575 - auto Tcount = detail::type_count<T>::value;  
576 - auto XCcount = detail::type_count<XC>::value; 576 + auto Tcount = detail::type_count<AssignTo>::value;
  577 + auto XCcount = detail::type_count<ConvertTo>::value;
577 opt->type_size((std::max)(Tcount, XCcount)); 578 opt->type_size((std::max)(Tcount, XCcount));
  579 + opt->expected(detail::expected_count<ConvertTo>::value);
578 return opt; 580 return opt;
579 } 581 }
580 582
@@ -584,7 +586,7 @@ class App { @@ -584,7 +586,7 @@ class App {
584 const std::function<void(const T &)> &func, ///< the callback to execute 586 const std::function<void(const T &)> &func, ///< the callback to execute
585 std::string option_description = "") { 587 std::string option_description = "") {
586 588
587 - auto fun = [func](CLI::results_t res) { 589 + auto fun = [func](const CLI::results_t &res) {
588 T variable; 590 T variable;
589 bool result = detail::lexical_conversion<T, T>(res, variable); 591 bool result = detail::lexical_conversion<T, T>(res, variable);
590 if(result) { 592 if(result) {
@@ -596,6 +598,7 @@ class App { @@ -596,6 +598,7 @@ class App {
596 Option *opt = add_option(option_name, std::move(fun), option_description, false); 598 Option *opt = add_option(option_name, std::move(fun), option_description, false);
597 opt->type_name(detail::type_name<T>()); 599 opt->type_name(detail::type_name<T>());
598 opt->type_size(detail::type_count<T>::value); 600 opt->type_size(detail::type_count<T>::value);
  601 + opt->expected(detail::expected_count<T>::value);
599 return opt; 602 return opt;
600 } 603 }
601 604
@@ -667,8 +670,9 @@ class App { @@ -667,8 +670,9 @@ class App {
667 remove_option(opt); 670 remove_option(opt);
668 throw IncorrectConstruction::PositionalFlag(pos_name); 671 throw IncorrectConstruction::PositionalFlag(pos_name);
669 } 672 }
670 -  
671 - opt->type_size(0); 673 + opt->multi_option_policy(MultiOptionPolicy::TakeLast);
  674 + opt->expected(0);
  675 + opt->required(false);
672 return opt; 676 return opt;
673 } 677 }
674 678
@@ -694,7 +698,7 @@ class App { @@ -694,7 +698,7 @@ class App {
694 T &flag_count, ///< A variable holding the count 698 T &flag_count, ///< A variable holding the count
695 std::string flag_description = "") { 699 std::string flag_description = "") {
696 flag_count = 0; 700 flag_count = 0;
697 - CLI::callback_t fun = [&flag_count](CLI::results_t res) { 701 + CLI::callback_t fun = [&flag_count](const CLI::results_t &res) {
698 try { 702 try {
699 detail::sum_flag_vector(res, flag_count); 703 detail::sum_flag_vector(res, flag_count);
700 } catch(const std::invalid_argument &) { 704 } catch(const std::invalid_argument &) {
@@ -702,7 +706,8 @@ class App { @@ -702,7 +706,8 @@ class App {
702 } 706 }
703 return true; 707 return true;
704 }; 708 };
705 - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); 709 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
  710 + ->multi_option_policy(MultiOptionPolicy::TakeAll);
706 } 711 }
707 712
708 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes 713 /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
@@ -716,15 +721,10 @@ class App { @@ -716,15 +721,10 @@ class App {
716 T &flag_result, ///< A variable holding true if passed 721 T &flag_result, ///< A variable holding true if passed
717 std::string flag_description = "") { 722 std::string flag_description = "") {
718 723
719 - CLI::callback_t fun = [&flag_result](CLI::results_t res) {  
720 - if(res.size() != 1) {  
721 - return false;  
722 - } 724 + CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
723 return CLI::detail::lexical_cast(res[0], flag_result); 725 return CLI::detail::lexical_cast(res[0], flag_result);
724 }; 726 };
725 - Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));  
726 - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);  
727 - return opt; 727 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
728 } 728 }
729 729
730 /// Vector version to capture multiple flags. 730 /// Vector version to capture multiple flags.
@@ -733,7 +733,7 @@ class App { @@ -733,7 +733,7 @@ class App {
733 Option *add_flag(std::string flag_name, 733 Option *add_flag(std::string flag_name,
734 std::vector<T> &flag_results, ///< A vector of values with the flag results 734 std::vector<T> &flag_results, ///< A vector of values with the flag results
735 std::string flag_description = "") { 735 std::string flag_description = "") {
736 - CLI::callback_t fun = [&flag_results](CLI::results_t res) { 736 + CLI::callback_t fun = [&flag_results](const CLI::results_t &res) {
737 bool retval = true; 737 bool retval = true;
738 for(const auto &elem : res) { 738 for(const auto &elem : res) {
739 flag_results.emplace_back(); 739 flag_results.emplace_back();
@@ -741,7 +741,8 @@ class App { @@ -741,7 +741,8 @@ class App {
741 } 741 }
742 return retval; 742 return retval;
743 }; 743 };
744 - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); 744 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
  745 + ->multi_option_policy(MultiOptionPolicy::TakeAll);
745 } 746 }
746 747
747 /// Add option for callback that is triggered with a true flag and takes no arguments 748 /// Add option for callback that is triggered with a true flag and takes no arguments
@@ -749,19 +750,14 @@ class App { @@ -749,19 +750,14 @@ class App {
749 std::function<void(void)> function, ///< A function to call, void(void) 750 std::function<void(void)> function, ///< A function to call, void(void)
750 std::string flag_description = "") { 751 std::string flag_description = "") {
751 752
752 - CLI::callback_t fun = [function](CLI::results_t res) {  
753 - if(res.size() != 1) {  
754 - return false;  
755 - } 753 + CLI::callback_t fun = [function](const CLI::results_t &res) {
756 bool trigger; 754 bool trigger;
757 auto result = CLI::detail::lexical_cast(res[0], trigger); 755 auto result = CLI::detail::lexical_cast(res[0], trigger);
758 if(trigger) 756 if(trigger)
759 function(); 757 function();
760 return result; 758 return result;
761 }; 759 };
762 - Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));  
763 - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);  
764 - return opt; 760 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
765 } 761 }
766 762
767 /// Add option for callback with an integer value 763 /// Add option for callback with an integer value
@@ -769,13 +765,14 @@ class App { @@ -769,13 +765,14 @@ class App {
769 std::function<void(int64_t)> function, ///< A function to call, void(int) 765 std::function<void(int64_t)> function, ///< A function to call, void(int)
770 std::string flag_description = "") { 766 std::string flag_description = "") {
771 767
772 - CLI::callback_t fun = [function](CLI::results_t res) { 768 + CLI::callback_t fun = [function](const CLI::results_t &res) {
773 int64_t flag_count = 0; 769 int64_t flag_count = 0;
774 detail::sum_flag_vector(res, flag_count); 770 detail::sum_flag_vector(res, flag_count);
775 function(flag_count); 771 function(flag_count);
776 return true; 772 return true;
777 }; 773 };
778 - return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); 774 + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
  775 + ->multi_option_policy(MultiOptionPolicy::TakeAll);
779 } 776 }
780 777
781 #ifdef CLI11_CPP14 778 #ifdef CLI11_CPP14
@@ -996,34 +993,52 @@ class App { @@ -996,34 +993,52 @@ class App {
996 } 993 }
997 994
998 /// Add a complex number 995 /// Add a complex number
999 - template <typename T> 996 + template <typename T, typename XC = double>
1000 Option *add_complex(std::string option_name, 997 Option *add_complex(std::string option_name,
1001 T &variable, 998 T &variable,
1002 std::string option_description = "", 999 std::string option_description = "",
1003 bool defaulted = false, 1000 bool defaulted = false,
1004 std::string label = "COMPLEX") { 1001 std::string label = "COMPLEX") {
1005 1002
1006 - std::string simple_name = CLI::detail::split(option_name, ',').at(0);  
1007 - CLI::callback_t fun = [&variable, simple_name, label](results_t res) {  
1008 - if(res[1].back() == 'i')  
1009 - res[1].pop_back();  
1010 - double x, y;  
1011 - bool worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(res[1], y); 1003 + CLI::callback_t fun = [&variable](const results_t &res) {
  1004 + XC x, y;
  1005 + bool worked;
  1006 + if(res.size() >= 2 && !res[1].empty()) {
  1007 + auto str1 = res[1];
  1008 + if(str1.back() == 'i' || str1.back() == 'j')
  1009 + str1.pop_back();
  1010 + worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(str1, y);
  1011 + } else {
  1012 + auto str1 = res.front();
  1013 + auto nloc = str1.find_last_of('-');
  1014 + if(nloc != std::string::npos && nloc > 0) {
  1015 + worked = detail::lexical_cast(str1.substr(0, nloc), x);
  1016 + str1 = str1.substr(nloc);
  1017 + if(str1.back() == 'i' || str1.back() == 'j')
  1018 + str1.pop_back();
  1019 + worked = worked && detail::lexical_cast(str1, y);
  1020 + } else {
  1021 + if(str1.back() == 'i' || str1.back() == 'j') {
  1022 + str1.pop_back();
  1023 + worked = detail::lexical_cast(str1, y);
  1024 + x = XC{0};
  1025 + } else {
  1026 + worked = detail::lexical_cast(str1, x);
  1027 + y = XC{0};
  1028 + }
  1029 + }
  1030 + }
1012 if(worked) 1031 if(worked)
1013 - variable = T(x, y); 1032 + variable = T{x, y};
1014 return worked; 1033 return worked;
1015 }; 1034 };
1016 1035
1017 - auto default_function = [&variable]() {  
1018 - std::stringstream out;  
1019 - out << variable;  
1020 - return out.str();  
1021 - }; 1036 + auto default_function = [&variable]() { return CLI::detail::checked_to_string<T, T>(variable); };
1022 1037
1023 CLI::Option *opt = 1038 CLI::Option *opt =
1024 add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function); 1039 add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function);
1025 1040
1026 - opt->type_name(label)->type_size(2); 1041 + opt->type_name(label)->type_size(1, 2)->delimiter('+');
1027 return opt; 1042 return opt;
1028 } 1043 }
1029 1044
@@ -1922,14 +1937,22 @@ class App { @@ -1922,14 +1937,22 @@ class App {
1922 protected: 1937 protected:
1923 /// Check the options to make sure there are no conflicts. 1938 /// Check the options to make sure there are no conflicts.
1924 /// 1939 ///
1925 - /// Currently checks to see if multiple positionals exist with -1 args and checks if the min and max options are  
1926 - /// feasible 1940 + /// Currently checks to see if multiple positionals exist with unlimited args and checks if the min and max options
  1941 + /// are feasible
1927 void _validate() const { 1942 void _validate() const {
  1943 + // count the number of positional only args
1928 auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { 1944 auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
1929 - return opt->get_items_expected() < 0 && opt->get_positional(); 1945 + return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional();
1930 }); 1946 });
1931 - if(pcount > 1)  
1932 - throw InvalidError(name_); 1947 + if(pcount > 1) {
  1948 + auto pcount_req = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
  1949 + return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional() &&
  1950 + opt->get_required();
  1951 + });
  1952 + if(pcount - pcount_req > 1) {
  1953 + throw InvalidError(name_);
  1954 + }
  1955 + }
1933 1956
1934 size_t nameless_subs{0}; 1957 size_t nameless_subs{0};
1935 for(const App_p &app : subcommands_) { 1958 for(const App_p &app : subcommands_) {
@@ -2199,15 +2222,9 @@ class App { @@ -2199,15 +2222,9 @@ class App {
2199 if(opt->count() != 0) { 2222 if(opt->count() != 0) {
2200 ++used_options; 2223 ++used_options;
2201 } 2224 }
2202 - // Required or partially filled  
2203 - if(opt->get_required() || opt->count() != 0) {  
2204 - // Make sure enough -N arguments parsed (+N is already handled in parsing function)  
2205 - if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected()))  
2206 - throw ArgumentMismatch::AtLeast(opt->get_name(), -opt->get_items_expected());  
2207 -  
2208 - // Required but empty  
2209 - if(opt->get_required() && opt->count() == 0)  
2210 - throw RequiredError(opt->get_name()); 2225 + // Required but empty
  2226 + if(opt->get_required() && opt->count() == 0) {
  2227 + throw RequiredError(opt->get_name());
2211 } 2228 }
2212 // Requires 2229 // Requires
2213 for(const Option *opt_req : opt->needs_) 2230 for(const Option *opt_req : opt->needs_)
@@ -2408,7 +2425,7 @@ class App { @@ -2408,7 +2425,7 @@ class App {
2408 2425
2409 if(op->empty()) { 2426 if(op->empty()) {
2410 // Flag parsing 2427 // Flag parsing
2411 - if(op->get_type_size() == 0) { 2428 + if(op->get_expected_min() == 0) {
2412 auto res = config_formatter_->to_flag(item); 2429 auto res = config_formatter_->to_flag(item);
2413 res = op->get_flag_value(item.name, res); 2430 res = op->get_flag_value(item.name, res);
2414 2431
@@ -2459,7 +2476,6 @@ class App { @@ -2459,7 +2476,6 @@ class App {
2459 positional_only = true; 2476 positional_only = true;
2460 } 2477 }
2461 break; 2478 break;
2462 -  
2463 // LCOV_EXCL_START 2479 // LCOV_EXCL_START
2464 default: 2480 default:
2465 throw HorribleError("unrecognized classifier (you should not see this!)"); 2481 throw HorribleError("unrecognized classifier (you should not see this!)");
@@ -2473,10 +2489,9 @@ class App { @@ -2473,10 +2489,9 @@ class App {
2473 size_t retval = 0; 2489 size_t retval = 0;
2474 for(const Option_p &opt : options_) { 2490 for(const Option_p &opt : options_) {
2475 if(opt->get_positional() && (!required_only || opt->get_required())) { 2491 if(opt->get_positional() && (!required_only || opt->get_required())) {
2476 - if(opt->get_items_expected() > 0 && static_cast<int>(opt->count()) < opt->get_items_expected()) {  
2477 - retval += static_cast<size_t>(opt->get_items_expected()) - opt->count();  
2478 - } else if(opt->get_required() && opt->get_items_expected() < 0 && opt->count() == 0ul) {  
2479 - retval += 1; 2492 + if(opt->get_items_expected_min() > 0 &&
  2493 + static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
  2494 + retval += static_cast<size_t>(opt->get_items_expected_min()) - opt->count();
2480 } 2495 }
2481 } 2496 }
2482 } 2497 }
@@ -2486,9 +2501,9 @@ class App { @@ -2486,9 +2501,9 @@ class App {
2486 /// Count the required remaining positional arguments 2501 /// Count the required remaining positional arguments
2487 bool _has_remaining_positionals() const { 2502 bool _has_remaining_positionals() const {
2488 for(const Option_p &opt : options_) 2503 for(const Option_p &opt : options_)
2489 - if(opt->get_positional() &&  
2490 - ((opt->get_items_expected() < 0) || ((static_cast<int>(opt->count()) < opt->get_items_expected())))) 2504 + if(opt->get_positional() && ((static_cast<int>(opt->count()) < opt->get_items_expected_min()))) {
2491 return true; 2505 return true;
  2506 + }
2492 2507
2493 return false; 2508 return false;
2494 } 2509 }
@@ -2507,8 +2522,7 @@ class App { @@ -2507,8 +2522,7 @@ class App {
2507 if(arg_rem <= remreq) { 2522 if(arg_rem <= remreq) {
2508 for(const Option_p &opt : options_) { 2523 for(const Option_p &opt : options_) {
2509 if(opt->get_positional() && opt->required_) { 2524 if(opt->get_positional() && opt->required_) {
2510 - if(static_cast<int>(opt->count()) < opt->get_items_expected() ||  
2511 - (opt->get_items_expected() < 0 && opt->count() == 0lu)) { 2525 + if(static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
2512 if(validate_positionals_) { 2526 if(validate_positionals_) {
2513 std::string pos = positional; 2527 std::string pos = positional;
2514 pos = opt->_validate(pos, 0); 2528 pos = opt->_validate(pos, 0);
@@ -2528,7 +2542,7 @@ class App { @@ -2528,7 +2542,7 @@ class App {
2528 for(const Option_p &opt : options_) { 2542 for(const Option_p &opt : options_) {
2529 // Eat options, one by one, until done 2543 // Eat options, one by one, until done
2530 if(opt->get_positional() && 2544 if(opt->get_positional() &&
2531 - (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { 2545 + (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
2532 if(validate_positionals_) { 2546 if(validate_positionals_) {
2533 std::string pos = positional; 2547 std::string pos = positional;
2534 pos = opt->_validate(pos, 0); 2548 pos = opt->_validate(pos, 0);
@@ -2714,13 +2728,14 @@ class App { @@ -2714,13 +2728,14 @@ class App {
2714 // Get a reference to the pointer to make syntax bearable 2728 // Get a reference to the pointer to make syntax bearable
2715 Option_p &op = *op_ptr; 2729 Option_p &op = *op_ptr;
2716 2730
2717 - int num = op->get_items_expected(); 2731 + int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
  2732 + int max_num = op->get_items_expected_max();
2718 2733
2719 // Make sure we always eat the minimum for unlimited vectors 2734 // Make sure we always eat the minimum for unlimited vectors
2720 - int collected = 0;  
2721 - int result_count = 0;  
2722 - // deal with flag like things  
2723 - if(num == 0) { 2735 + int collected = 0; // total number of arguments collected
  2736 + int result_count = 0; // local variable for number of results in a single arg string
  2737 + // deal with purely flag like things
  2738 + if(max_num == 0) {
2724 auto res = op->get_flag_value(arg_name, value); 2739 auto res = op->get_flag_value(arg_name, value);
2725 op->add_result(res); 2740 op->add_result(res);
2726 parse_order_.push_back(op.get()); 2741 parse_order_.push_back(op.get());
@@ -2730,31 +2745,37 @@ class App { @@ -2730,31 +2745,37 @@ class App {
2730 op->add_result(value, result_count); 2745 op->add_result(value, result_count);
2731 parse_order_.push_back(op.get()); 2746 parse_order_.push_back(op.get());
2732 collected += result_count; 2747 collected += result_count;
2733 - // If exact number expected  
2734 - if(num > 0)  
2735 - num = (num >= result_count) ? num - result_count : 0;  
2736 -  
2737 // -Trest 2748 // -Trest
2738 } else if(!rest.empty()) { 2749 } else if(!rest.empty()) {
2739 op->add_result(rest, result_count); 2750 op->add_result(rest, result_count);
2740 parse_order_.push_back(op.get()); 2751 parse_order_.push_back(op.get());
2741 rest = ""; 2752 rest = "";
2742 collected += result_count; 2753 collected += result_count;
2743 - // If exact number expected  
2744 - if(num > 0)  
2745 - num = (num >= result_count) ? num - result_count : 0;  
2746 } 2754 }
2747 2755
2748 - // Unlimited vector parser  
2749 - if(num < 0) {  
2750 - while(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) {  
2751 - if(collected >= -num) {  
2752 - // We could break here for allow extras, but we don't 2756 + // gather the minimum number of arguments
  2757 + while(min_num > collected && !args.empty()) {
  2758 + std::string current_ = args.back();
  2759 + args.pop_back();
  2760 + op->add_result(current_, result_count);
  2761 + parse_order_.push_back(op.get());
  2762 + collected += result_count;
  2763 + }
  2764 +
  2765 + if(min_num > collected) { // if we have run out of arguments and the minimum was not met
  2766 + throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name());
  2767 + }
2753 2768
2754 - // If any positionals remain, don't keep eating  
2755 - if(_count_remaining_positionals() > 0)  
2756 - break; 2769 + if(max_num > collected || op->get_allow_extra_args()) { // we allow optional arguments
  2770 + auto remreqpos = _count_remaining_positionals(true);
  2771 + // we have met the minimum now optionally check up to the maximum
  2772 + while((collected < max_num || op->get_allow_extra_args()) && !args.empty() &&
  2773 + _recognize(args.back(), false) == detail::Classifier::NONE) {
  2774 + // If any required positionals remain, don't keep eating
  2775 + if(remreqpos >= args.size()) {
  2776 + break;
2757 } 2777 }
  2778 +
2758 op->add_result(args.back(), result_count); 2779 op->add_result(args.back(), result_count);
2759 parse_order_.push_back(op.get()); 2780 parse_order_.push_back(op.get());
2760 args.pop_back(); 2781 args.pop_back();
@@ -2764,19 +2785,17 @@ class App { @@ -2764,19 +2785,17 @@ class App {
2764 // Allow -- to end an unlimited list and "eat" it 2785 // Allow -- to end an unlimited list and "eat" it
2765 if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK) 2786 if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK)
2766 args.pop_back(); 2787 args.pop_back();
2767 -  
2768 - } else {  
2769 - while(num > 0 && !args.empty()) {  
2770 - std::string current_ = args.back();  
2771 - args.pop_back();  
2772 - op->add_result(current_, result_count); 2788 + // optional flag that didn't receive anything now get the default value
  2789 + if(min_num == 0 && max_num > 0 && collected == 0) {
  2790 + auto res = op->get_flag_value(arg_name, std::string{});
  2791 + op->add_result(res);
2773 parse_order_.push_back(op.get()); 2792 parse_order_.push_back(op.get());
2774 - num -= result_count;  
2775 } 2793 }
  2794 + }
2776 2795
2777 - if(num > 0) {  
2778 - throw ArgumentMismatch::TypedAtLeast(op->get_name(), num, op->get_type_name());  
2779 - } 2796 + // if we only partially completed a type then add an empty string for later processing
  2797 + if(min_num > 0 && op->get_type_size_max() != min_num && collected % op->get_type_size_max() != 0) {
  2798 + op->add_result(std::string{});
2780 } 2799 }
2781 2800
2782 if(!rest.empty()) { 2801 if(!rest.empty()) {
include/CLI/Config.hpp
@@ -25,7 +25,7 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description, @@ -25,7 +25,7 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description,
25 std::string value; 25 std::string value;
26 26
27 // Non-flags 27 // Non-flags
28 - if(opt->get_type_size() != 0) { 28 + if(opt->get_expected_min() != 0) {
29 29
30 // If the option was found on command line 30 // If the option was found on command line
31 if(opt->count() > 0) 31 if(opt->count() > 0)
@@ -56,7 +56,7 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description, @@ -56,7 +56,7 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description,
56 } 56 }
57 57
58 // Don't try to quote anything that is not size 1 58 // Don't try to quote anything that is not size 1
59 - if(opt->get_items_expected() != 1) 59 + if(opt->get_items_expected_max() != 1)
60 out << name << "=" << value << std::endl; 60 out << name << "=" << value << std::endl;
61 else 61 else
62 out << name << "=" << detail::add_quotes_if_needed(value) << std::endl; 62 out << name << "=" << detail::add_quotes_if_needed(value) << std::endl;
include/CLI/Error.hpp
@@ -246,8 +246,13 @@ class ArgumentMismatch : public ParseError { @@ -246,8 +246,13 @@ class ArgumentMismatch : public ParseError {
246 ", got " + std::to_string(recieved)), 246 ", got " + std::to_string(recieved)),
247 ExitCodes::ArgumentMismatch) {} 247 ExitCodes::ArgumentMismatch) {}
248 248
249 - static ArgumentMismatch AtLeast(std::string name, int num) {  
250 - return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); 249 + static ArgumentMismatch AtLeast(std::string name, int num, size_t received) {
  250 + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
  251 + std::to_string(received));
  252 + }
  253 + static ArgumentMismatch AtMost(std::string name, int num, size_t received) {
  254 + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
  255 + std::to_string(received));
251 } 256 }
252 static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { 257 static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
253 return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); 258 return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
include/CLI/Formatter.hpp
@@ -234,10 +234,11 @@ inline std::string Formatter::make_option_opts(const Option *opt) const { @@ -234,10 +234,11 @@ inline std::string Formatter::make_option_opts(const Option *opt) const {
234 out << " " << get_label(opt->get_type_name()); 234 out << " " << get_label(opt->get_type_name());
235 if(!opt->get_default_str().empty()) 235 if(!opt->get_default_str().empty())
236 out << "=" << opt->get_default_str(); 236 out << "=" << opt->get_default_str();
237 - if(opt->get_expected() > 1)  
238 - out << " x " << opt->get_expected();  
239 - if(opt->get_expected() == -1) 237 + if(opt->get_expected_max() == detail::expected_max_vector_size)
240 out << " ..."; 238 out << " ...";
  239 + else if(opt->get_expected_min() > 1)
  240 + out << " x " << opt->get_expected();
  241 +
241 if(opt->get_required()) 242 if(opt->get_required())
242 out << " " << get_label("REQUIRED"); 243 out << " " << get_label("REQUIRED");
243 } 244 }
@@ -262,11 +263,11 @@ inline std::string Formatter::make_option_usage(const Option *opt) const { @@ -262,11 +263,11 @@ inline std::string Formatter::make_option_usage(const Option *opt) const {
262 // Note that these are positionals usages 263 // Note that these are positionals usages
263 std::stringstream out; 264 std::stringstream out;
264 out << make_option_name(opt, true); 265 out << make_option_name(opt, true);
265 -  
266 - if(opt->get_expected() > 1)  
267 - out << "(" << std::to_string(opt->get_expected()) << "x)";  
268 - else if(opt->get_expected() < 0) 266 + if(opt->get_expected_max() >= detail::expected_max_vector_size)
269 out << "..."; 267 out << "...";
  268 + else if(opt->get_expected_max() > 1)
  269 + out << "(" << opt->get_expected() << "x)";
  270 +
270 return opt->get_required() ? out.str() : "[" + out.str() + "]"; 271 return opt->get_required() ? out.str() : "[" + out.str() + "]";
271 } 272 }
272 273
include/CLI/Option.hpp
@@ -21,14 +21,21 @@ @@ -21,14 +21,21 @@
21 namespace CLI { 21 namespace CLI {
22 22
23 using results_t = std::vector<std::string>; 23 using results_t = std::vector<std::string>;
24 -using callback_t = std::function<bool(results_t)>; 24 +/// callback function definition
  25 +using callback_t = std::function<bool(const results_t &)>;
25 26
26 class Option; 27 class Option;
27 class App; 28 class App;
28 29
29 using Option_p = std::unique_ptr<Option>; 30 using Option_p = std::unique_ptr<Option>;
30 -  
31 -enum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join }; 31 +/// Enumeration of the multiOption Policy selection
  32 +enum class MultiOptionPolicy : char {
  33 + Throw, //!< Throw an error if any extra arguments were given
  34 + TakeLast, //!< take only the last Expected number of arguments
  35 + TakeFirst, //!< take only the first Expected number of arguments
  36 + Join, //!< merge all the arguments together into a single string via the delimiter character default('\n')
  37 + TakeAll //!< just get all the passed argument regardless
  38 +};
32 39
33 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way 40 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
34 /// to share parts of the class; an OptionDefaults can copy to an Option. 41 /// to share parts of the class; an OptionDefaults can copy to an Option.
@@ -60,7 +67,7 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -60,7 +67,7 @@ template &lt;typename CRTP&gt; class OptionBase {
60 /// Automatically capture default value 67 /// Automatically capture default value
61 bool always_capture_default_{false}; 68 bool always_capture_default_{false};
62 69
63 - /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) 70 + /// Policy for handling multiple arguments beyond the expected Max
64 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; 71 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
65 72
66 /// Copy the contents to another similar class (one based on OptionBase) 73 /// Copy the contents to another similar class (one based on OptionBase)
@@ -119,7 +126,7 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -119,7 +126,7 @@ template &lt;typename CRTP&gt; class OptionBase {
119 /// The status of configurable 126 /// The status of configurable
120 bool get_disable_flag_override() const { return disable_flag_override_; } 127 bool get_disable_flag_override() const { return disable_flag_override_; }
121 128
122 - /// Get the current delimeter char 129 + /// Get the current delimiter char
123 char get_delimiter() const { return delimiter_; } 130 char get_delimiter() const { return delimiter_; }
124 131
125 /// Return true if this will automatically capture the default value for help printing 132 /// Return true if this will automatically capture the default value for help printing
@@ -144,13 +151,28 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -144,13 +151,28 @@ template &lt;typename CRTP&gt; class OptionBase {
144 return self; 151 return self;
145 } 152 }
146 153
147 - /// Set the multi option policy to take last 154 + /// Set the multi option policy to take all arguments
  155 + CRTP *take_all() {
  156 + auto self = static_cast<CRTP *>(this);
  157 + self->multi_option_policy(MultiOptionPolicy::TakeAll);
  158 + return self;
  159 + }
  160 +
  161 + /// Set the multi option policy to join
148 CRTP *join() { 162 CRTP *join() {
149 auto self = static_cast<CRTP *>(this); 163 auto self = static_cast<CRTP *>(this);
150 self->multi_option_policy(MultiOptionPolicy::Join); 164 self->multi_option_policy(MultiOptionPolicy::Join);
151 return self; 165 return self;
152 } 166 }
153 167
  168 + /// Set the multi option policy to join with a specific delimiter
  169 + CRTP *join(char delim) {
  170 + auto self = static_cast<CRTP *>(this);
  171 + self->delimiter_ = delim;
  172 + self->multi_option_policy(MultiOptionPolicy::Join);
  173 + return self;
  174 + }
  175 +
154 /// Allow in a configuration file 176 /// Allow in a configuration file
155 CRTP *configurable(bool value = true) { 177 CRTP *configurable(bool value = true) {
156 configurable_ = value; 178 configurable_ = value;
@@ -213,7 +235,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -213,7 +235,7 @@ class Option : public OptionBase&lt;Option&gt; {
213 /// A list of the short names (`-a`) without the leading dashes 235 /// A list of the short names (`-a`) without the leading dashes
214 std::vector<std::string> snames_; 236 std::vector<std::string> snames_;
215 237
216 - /// A list of the long names (`--a`) without the leading dashes 238 + /// A list of the long names (`--long`) without the leading dashes
217 std::vector<std::string> lnames_; 239 std::vector<std::string> lnames_;
218 240
219 /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of 241 /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
@@ -251,15 +273,18 @@ class Option : public OptionBase&lt;Option&gt; { @@ -251,15 +273,18 @@ class Option : public OptionBase&lt;Option&gt; {
251 /// @name Configuration 273 /// @name Configuration
252 ///@{ 274 ///@{
253 275
254 - /// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option,  
255 - /// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean  
256 - /// vector of pairs.  
257 - int type_size_{1}; 276 + /// The number of arguments that make up one option. max is the nominal type size, min is the minimum number of
  277 + /// strings
  278 + int type_size_max_{1};
  279 + /// The minimum number of arguments an option should be expecting
  280 + int type_size_min_{1};
258 281
259 - /// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values.  
260 - int expected_{1}; 282 + /// The minimum number of expected values
  283 + int expected_min_{1};
  284 + /// The maximum number of expected values
  285 + int expected_max_{1};
261 286
262 - /// A list of validators to run on each value parsed 287 + /// A list of Validators to run on each value parsed
263 std::vector<Validator> validators_; 288 std::vector<Validator> validators_;
264 289
265 /// A list of options that are required with this option 290 /// A list of options that are required with this option
@@ -282,19 +307,27 @@ class Option : public OptionBase&lt;Option&gt; { @@ -282,19 +307,27 @@ class Option : public OptionBase&lt;Option&gt; {
282 /// @name Parsing results 307 /// @name Parsing results
283 ///@{ 308 ///@{
284 309
285 - /// Results of parsing 310 + /// complete Results of parsing
286 results_t results_; 311 results_t results_;
287 - 312 + /// results after reduction
  313 + results_t proc_results_;
  314 + /// enumeration for the option state machine
  315 + enum class option_state {
  316 + parsing = 0, //!< The option is currently collecting parsed results
  317 + validated = 2, //!< the results have been validated
  318 + reduced = 4, //!< a subset of results has been generated
  319 + callback_run = 6, //!< the callback has been executed
  320 + };
288 /// Whether the callback has run (needed for INI parsing) 321 /// Whether the callback has run (needed for INI parsing)
289 - bool callback_run_{false};  
290 - 322 + option_state current_option_state_{option_state::parsing};
  323 + /// Specify that extra args beyond type_size_max should be allowed
  324 + bool allow_extra_args_{false};
  325 + /// Specify that the option should act like a flag vs regular option
  326 + bool flag_like_{false};
291 ///@} 327 ///@}
292 328
293 /// Making an option by hand is not defined, it must be made by the App class 329 /// Making an option by hand is not defined, it must be made by the App class
294 - Option(std::string option_name,  
295 - std::string option_description,  
296 - std::function<bool(results_t)> callback,  
297 - App *parent) 330 + Option(std::string option_name, std::string option_description, callback_t callback, App *parent)
298 : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) { 331 : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
299 std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name)); 332 std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
300 } 333 }
@@ -313,38 +346,64 @@ class Option : public OptionBase&lt;Option&gt; { @@ -313,38 +346,64 @@ class Option : public OptionBase&lt;Option&gt; {
313 operator bool() const { return !empty(); } 346 operator bool() const { return !empty(); }
314 347
315 /// Clear the parsed results (mostly for testing) 348 /// Clear the parsed results (mostly for testing)
316 - void clear() { results_.clear(); } 349 + void clear() {
  350 + results_.clear();
  351 + current_option_state_ = option_state::parsing;
  352 + }
317 353
318 ///@} 354 ///@}
319 /// @name Setting options 355 /// @name Setting options
320 ///@{ 356 ///@{
321 357
322 - /// Set the number of expected arguments (Flags don't use this) 358 + /// Set the number of expected arguments
323 Option *expected(int value) { 359 Option *expected(int value) {
  360 + if(value < 0) {
  361 + expected_min_ = -value;
  362 + if(expected_max_ < expected_min_) {
  363 + expected_max_ = expected_min_;
  364 + }
  365 + allow_extra_args_ = true;
  366 + flag_like_ = false;
  367 + } else if(value == detail::expected_max_vector_size) {
  368 + expected_min_ = 1;
  369 + expected_max_ = detail::expected_max_vector_size;
  370 + allow_extra_args_ = true;
  371 + flag_like_ = false;
  372 + } else {
  373 + expected_min_ = value;
  374 + expected_max_ = value;
  375 + flag_like_ = (expected_min_ == 0);
  376 + }
  377 + return this;
  378 + }
324 379
325 - // Break if this is a flag  
326 - if(type_size_ == 0)  
327 - throw IncorrectConstruction::SetFlag(get_name(true, true));  
328 -  
329 - // Setting 0 is not allowed  
330 - if(value == 0)  
331 - throw IncorrectConstruction::Set0Opt(get_name());  
332 -  
333 - // No change is okay, quit now  
334 - if(expected_ == value)  
335 - return this;  
336 -  
337 - // Type must be a vector  
338 - if(type_size_ >= 0)  
339 - throw IncorrectConstruction::ChangeNotVector(get_name()); 380 + /// Set the range of expected arguments
  381 + Option *expected(int value_min, int value_max) {
  382 + if(value_min < 0) {
  383 + value_min = -value_min;
  384 + }
340 385
341 - // TODO: Can support multioption for non-1 values (except for join)  
342 - if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw)  
343 - throw IncorrectConstruction::AfterMultiOpt(get_name()); 386 + if(value_max < 0) {
  387 + value_max = detail::expected_max_vector_size;
  388 + }
  389 + if(value_max < value_min) {
  390 + expected_min_ = value_max;
  391 + expected_max_ = value_min;
  392 + } else {
  393 + expected_max_ = value_max;
  394 + expected_min_ = value_min;
  395 + }
344 396
345 - expected_ = value;  
346 return this; 397 return this;
347 } 398 }
  399 + /// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included
  400 + /// with each instance
  401 + Option *allow_extra_args(bool value = true) {
  402 + allow_extra_args_ = value;
  403 + return this;
  404 + }
  405 + /// Get the current value of allow extra args
  406 + bool get_allow_extra_args() const { return allow_extra_args_; }
348 407
349 /// Adds a Validator with a built in type name 408 /// Adds a Validator with a built in type name
350 Option *check(Validator validator, std::string validator_name = "") { 409 Option *check(Validator validator, std::string validator_name = "") {
@@ -356,23 +415,23 @@ class Option : public OptionBase&lt;Option&gt; { @@ -356,23 +415,23 @@ class Option : public OptionBase&lt;Option&gt; {
356 } 415 }
357 416
358 /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay). 417 /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
359 - Option *check(std::function<std::string(const std::string &)> validator,  
360 - std::string validator_description = "",  
361 - std::string validator_name = "") {  
362 - validators_.emplace_back(validator, std::move(validator_description), std::move(validator_name)); 418 + Option *check(std::function<std::string(const std::string &)> Validator,
  419 + std::string Validator_description = "",
  420 + std::string Validator_name = "") {
  421 + validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
363 validators_.back().non_modifying(); 422 validators_.back().non_modifying();
364 return this; 423 return this;
365 } 424 }
366 425
367 - /// Adds a transforming validator with a built in type name  
368 - Option *transform(Validator validator, std::string validator_name = "") {  
369 - validators_.insert(validators_.begin(), std::move(validator));  
370 - if(!validator_name.empty())  
371 - validators_.front().name(validator_name); 426 + /// Adds a transforming Validator with a built in type name
  427 + Option *transform(Validator Validator, std::string Validator_name = "") {
  428 + validators_.insert(validators_.begin(), std::move(Validator));
  429 + if(!Validator_name.empty())
  430 + validators_.front().name(Validator_name);
372 return this; 431 return this;
373 } 432 }
374 433
375 - /// Adds a validator-like function that can change result 434 + /// Adds a Validator-like function that can change result
376 Option *transform(std::function<std::string(std::string)> func, 435 Option *transform(std::function<std::string(std::string)> func,
377 std::string transform_description = "", 436 std::string transform_description = "",
378 std::string transform_name = "") { 437 std::string transform_name = "") {
@@ -399,16 +458,16 @@ class Option : public OptionBase&lt;Option&gt; { @@ -399,16 +458,16 @@ class Option : public OptionBase&lt;Option&gt; {
399 return this; 458 return this;
400 } 459 }
401 /// Get a named Validator 460 /// Get a named Validator
402 - Validator *get_validator(const std::string &validator_name = "") {  
403 - for(auto &validator : validators_) {  
404 - if(validator_name == validator.get_name()) {  
405 - return &validator; 461 + Validator *get_validator(const std::string &Validator_name = "") {
  462 + for(auto &Validator : validators_) {
  463 + if(Validator_name == Validator.get_name()) {
  464 + return &Validator;
406 } 465 }
407 } 466 }
408 - if((validator_name.empty()) && (!validators_.empty())) { 467 + if((Validator_name.empty()) && (!validators_.empty())) {
409 return &(validators_.front()); 468 return &(validators_.front());
410 } 469 }
411 - throw OptionNotFound(std::string{"Validator "} + validator_name + " Not Found"); 470 + throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
412 } 471 }
413 472
414 /// Get a Validator by index NOTE: this may not be the order of definition 473 /// Get a Validator by index NOTE: this may not be the order of definition
@@ -552,10 +611,15 @@ class Option : public OptionBase&lt;Option&gt; { @@ -552,10 +611,15 @@ class Option : public OptionBase&lt;Option&gt; {
552 611
553 /// Take the last argument if given multiple times (or another policy) 612 /// Take the last argument if given multiple times (or another policy)
554 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { 613 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
555 -  
556 - if(get_items_expected() < 0)  
557 - throw IncorrectConstruction::MultiOptionPolicy(get_name());  
558 - multi_option_policy_ = value; 614 + if(value != multi_option_policy_) {
  615 + if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
  616 + expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility
  617 + // with the previous behavior of expected_ with vectors
  618 + expected_max_ = expected_min_;
  619 + }
  620 + multi_option_policy_ = value;
  621 + current_option_state_ = option_state::parsing;
  622 + }
559 return this; 623 return this;
560 } 624 }
561 625
@@ -569,7 +633,12 @@ class Option : public OptionBase&lt;Option&gt; { @@ -569,7 +633,12 @@ class Option : public OptionBase&lt;Option&gt; {
569 ///@{ 633 ///@{
570 634
571 /// The number of arguments the option expects 635 /// The number of arguments the option expects
572 - int get_type_size() const { return type_size_; } 636 + int get_type_size() const { return type_size_min_; }
  637 +
  638 + /// The minimum number of arguments the option expects
  639 + int get_type_size_min() const { return type_size_min_; }
  640 + /// The maximum number of arguments the option expects
  641 + int get_type_size_max() const { return type_size_max_; }
573 642
574 /// The environment variable associated to this value 643 /// The environment variable associated to this value
575 std::string get_envname() const { return envname_; } 644 std::string get_envname() const { return envname_; }
@@ -600,28 +669,23 @@ class Option : public OptionBase&lt;Option&gt; { @@ -600,28 +669,23 @@ class Option : public OptionBase&lt;Option&gt; {
600 const std::vector<std::string> get_fnames() const { return fnames_; } 669 const std::vector<std::string> get_fnames() const { return fnames_; }
601 670
602 /// The number of times the option expects to be included 671 /// The number of times the option expects to be included
603 - int get_expected() const { return expected_; } 672 + int get_expected() const { return expected_min_; }
604 673
605 - /// \brief The total number of expected values (including the type)  
606 - /// This is positive if exactly this number is expected, and negative for at least N values  
607 - ///  
608 - /// v = fabs(size_type*expected)  
609 - /// !MultiOptionPolicy::Throw  
610 - /// | Expected < 0 | Expected == 0 | Expected > 0  
611 - /// Size < 0 | -v | 0 | -v  
612 - /// Size == 0 | 0 | 0 | 0  
613 - /// Size > 0 | -v | 0 | -v // Expected must be 1  
614 - ///  
615 - /// MultiOptionPolicy::Throw  
616 - /// | Expected < 0 | Expected == 0 | Expected > 0  
617 - /// Size < 0 | -v | 0 | v  
618 - /// Size == 0 | 0 | 0 | 0  
619 - /// Size > 0 | v | 0 | v // Expected must be 1  
620 - ///  
621 - int get_items_expected() const {  
622 - return std::abs(type_size_ * expected_) *  
623 - ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); 674 + /// The number of times the option expects to be included
  675 + int get_expected_min() const { return expected_min_; }
  676 + /// The max number of times the option expects to be included
  677 + int get_expected_max() const { return expected_max_; }
  678 +
  679 + /// The total min number of expected string values to be used
  680 + int get_items_expected_min() const { return type_size_min_ * expected_min_; }
  681 +
  682 + /// Get the maximum number of items expected to be returned and used for the callback
  683 + int get_items_expected_max() const {
  684 + int t = type_size_max_;
  685 + return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size;
624 } 686 }
  687 + /// The total min number of expected string values to be used
  688 + int get_items_expected() const { return get_items_expected_min(); }
625 689
626 /// True if the argument can be given directly 690 /// True if the argument can be given directly
627 bool get_positional() const { return pname_.length() > 0; } 691 bool get_positional() const { return pname_.length() > 0; }
@@ -711,66 +775,26 @@ class Option : public OptionBase&lt;Option&gt; { @@ -711,66 +775,26 @@ class Option : public OptionBase&lt;Option&gt; {
711 /// Process the callback 775 /// Process the callback
712 void run_callback() { 776 void run_callback() {
713 777
714 - callback_run_ = true;  
715 -  
716 - // Run the validators (can change the string)  
717 - if(!validators_.empty()) {  
718 - int index = 0;  
719 - // this is not available until multi_option_policy with type_size_>0 is enabled and functional  
720 - // if(type_size_ > 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {  
721 - // index = type_size_ - static_cast<int>(results_.size());  
722 - //}  
723 - if(type_size_ < 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { // for vector operations  
724 - index = expected_ - static_cast<int>(results_.size());  
725 - }  
726 - for(std::string &result : results_) {  
727 - auto err_msg = _validate(result, index);  
728 - ++index;  
729 - if(!err_msg.empty())  
730 - throw ValidationError(get_name(), err_msg);  
731 - } 778 + if(current_option_state_ == option_state::parsing) {
  779 + _validate_results(results_);
  780 + current_option_state_ = option_state::validated;
732 } 781 }
733 - if(!(callback_)) {  
734 - return;  
735 - }  
736 - bool local_result;  
737 782
738 - // Num items expected or length of vector, always at least 1  
739 - // Only valid for a trimming policy  
740 - int trim_size =  
741 - std::min<int>(std::max<int>(std::abs(get_items_expected()), 1), static_cast<int>(results_.size()));  
742 -  
743 - // Operation depends on the policy setting  
744 - if(multi_option_policy_ == MultiOptionPolicy::TakeLast) {  
745 - // Allow multi-option sizes (including 0)  
746 - results_t partial_result{results_.end() - trim_size, results_.end()};  
747 - local_result = !callback_(partial_result);  
748 -  
749 - } else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) {  
750 - results_t partial_result{results_.begin(), results_.begin() + trim_size};  
751 - local_result = !callback_(partial_result);  
752 -  
753 - } else if(multi_option_policy_ == MultiOptionPolicy::Join) {  
754 - results_t partial_result = {detail::join(results_, "\n")};  
755 - local_result = !callback_(partial_result);  
756 -  
757 - } else {  
758 - // Exact number required  
759 - if(get_items_expected() > 0) {  
760 - if(results_.size() != static_cast<size_t>(get_items_expected()))  
761 - throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());  
762 - // Variable length list  
763 - } else if(get_items_expected() < 0) {  
764 - // Require that this be a multiple of expected size and at least as many as expected  
765 - if(results_.size() < static_cast<size_t>(-get_items_expected()) ||  
766 - results_.size() % static_cast<size_t>(std::abs(get_type_size())) != 0u)  
767 - throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());  
768 - }  
769 - local_result = !callback_(results_); 783 + if(current_option_state_ < option_state::reduced) {
  784 + _reduce_results(proc_results_, results_);
  785 + current_option_state_ = option_state::reduced;
770 } 786 }
  787 + if(current_option_state_ >= option_state::reduced) {
  788 + current_option_state_ = option_state::callback_run;
  789 + if(!(callback_)) {
  790 + return;
  791 + }
  792 + const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
  793 + bool local_result = callback_(send_results);
771 794
772 - if(local_result)  
773 - throw ConversionError(get_name(), results_); 795 + if(!local_result)
  796 + throw ConversionError(get_name(), results_);
  797 + }
774 } 798 }
775 799
776 /// If options share any of the same names, find it 800 /// If options share any of the same names, find it
@@ -833,7 +857,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -833,7 +857,8 @@ class Option : public OptionBase&lt;Option&gt; {
833 return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0); 857 return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0);
834 } 858 }
835 859
836 - /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not disabled 860 + /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not
  861 + /// disabled
837 std::string get_flag_value(std::string name, std::string input_value) const { 862 std::string get_flag_value(std::string name, std::string input_value) const {
838 static const std::string trueString{"true"}; 863 static const std::string trueString{"true"};
839 static const std::string falseString{"false"}; 864 static const std::string falseString{"false"};
@@ -856,7 +881,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -856,7 +881,11 @@ class Option : public OptionBase&lt;Option&gt; {
856 } 881 }
857 auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_); 882 auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
858 if((input_value.empty()) || (input_value == emptyString)) { 883 if((input_value.empty()) || (input_value == emptyString)) {
859 - return (ind < 0) ? trueString : default_flag_values_[static_cast<size_t>(ind)].second; 884 + if(flag_like_) {
  885 + return (ind < 0) ? trueString : default_flag_values_[static_cast<size_t>(ind)].second;
  886 + } else {
  887 + return (ind < 0) ? default_str_ : default_flag_values_[static_cast<size_t>(ind)].second;
  888 + }
860 } 889 }
861 if(ind < 0) { 890 if(ind < 0) {
862 return input_value; 891 return input_value;
@@ -875,73 +904,78 @@ class Option : public OptionBase&lt;Option&gt; { @@ -875,73 +904,78 @@ class Option : public OptionBase&lt;Option&gt; {
875 904
876 /// Puts a result at the end 905 /// Puts a result at the end
877 Option *add_result(std::string s) { 906 Option *add_result(std::string s) {
878 - _add_result(std::move(s));  
879 - callback_run_ = false; 907 + _add_result(std::move(s), results_);
  908 + current_option_state_ = option_state::parsing;
880 return this; 909 return this;
881 } 910 }
882 911
883 /// Puts a result at the end and get a count of the number of arguments actually added 912 /// Puts a result at the end and get a count of the number of arguments actually added
884 Option *add_result(std::string s, int &results_added) { 913 Option *add_result(std::string s, int &results_added) {
885 - results_added = _add_result(std::move(s));  
886 - callback_run_ = false; 914 + results_added = _add_result(std::move(s), results_);
  915 + current_option_state_ = option_state::parsing;
887 return this; 916 return this;
888 } 917 }
889 918
890 /// Puts a result at the end 919 /// Puts a result at the end
891 Option *add_result(std::vector<std::string> s) { 920 Option *add_result(std::vector<std::string> s) {
892 for(auto &str : s) { 921 for(auto &str : s) {
893 - _add_result(std::move(str)); 922 + _add_result(std::move(str), results_);
894 } 923 }
895 - callback_run_ = false; 924 + current_option_state_ = option_state::parsing;
896 return this; 925 return this;
897 } 926 }
898 927
899 /// Get a copy of the results 928 /// Get a copy of the results
900 - std::vector<std::string> results() const { return results_; } 929 + results_t results() const { return results_; }
  930 +
  931 + /// Get a copy of the results
  932 + results_t reduced_results() const {
  933 + results_t res = proc_results_.empty() ? results_ : proc_results_;
  934 + if(current_option_state_ < option_state::reduced) {
  935 + if(current_option_state_ == option_state::parsing) {
  936 + res = results_;
  937 + _validate_results(res);
  938 + }
  939 + results_t extra;
  940 + _reduce_results(extra, res);
  941 + if(!extra.empty()) {
  942 + res = std::move(extra);
  943 + }
  944 + }
  945 + return res;
  946 + }
901 947
902 /// Get the results as a specified type 948 /// Get the results as a specified type
903 - template <typename T,  
904 - enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy> 949 + template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy>
905 void results(T &output) const { 950 void results(T &output) const {
906 bool retval; 951 bool retval;
907 - if(results_.empty()) {  
908 - retval = detail::lexical_cast(default_str_, output);  
909 - } else if(results_.size() == 1) {  
910 - retval = detail::lexical_cast(results_[0], output); 952 + if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
  953 + const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
  954 + retval = detail::lexical_conversion<T, T>(res, output);
911 } else { 955 } else {
912 - switch(multi_option_policy_) {  
913 - case MultiOptionPolicy::TakeFirst:  
914 - retval = detail::lexical_cast(results_.front(), output);  
915 - break;  
916 - case MultiOptionPolicy::TakeLast:  
917 - default:  
918 - retval = detail::lexical_cast(results_.back(), output);  
919 - break;  
920 - case MultiOptionPolicy::Throw:  
921 - throw ConversionError(get_name(), results_);  
922 - case MultiOptionPolicy::Join:  
923 - retval = detail::lexical_cast(detail::join(results_), output);  
924 - break; 956 + results_t res;
  957 + if(results_.empty()) {
  958 + if(!default_str_.empty()) {
  959 + //_add_results takes an rvalue only
  960 + _add_result(std::string(default_str_), res);
  961 + _validate_results(res);
  962 + results_t extra;
  963 + _reduce_results(extra, res);
  964 + if(!extra.empty()) {
  965 + res = std::move(extra);
  966 + }
  967 + } else {
  968 + res.emplace_back();
  969 + }
  970 + } else {
  971 + res = reduced_results();
925 } 972 }
  973 + retval = detail::lexical_conversion<T, T>(res, output);
926 } 974 }
927 if(!retval) { 975 if(!retval) {
928 throw ConversionError(get_name(), results_); 976 throw ConversionError(get_name(), results_);
929 } 977 }
930 } 978 }
931 - /// Get the results as a vector of the specified type  
932 - template <typename T> void results(std::vector<T> &output) const {  
933 - output.clear();  
934 - bool retval = true;  
935 -  
936 - for(const auto &elem : results_) {  
937 - output.emplace_back();  
938 - retval &= detail::lexical_cast(elem, output.back());  
939 - }  
940 -  
941 - if(!retval) {  
942 - throw ConversionError(get_name(), results_);  
943 - }  
944 - }  
945 979
946 /// Return the results as the specified type 980 /// Return the results as the specified type
947 template <typename T> T as() const { 981 template <typename T> T as() const {
@@ -951,7 +985,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -951,7 +985,7 @@ class Option : public OptionBase&lt;Option&gt; {
951 } 985 }
952 986
953 /// See if the callback has been run already 987 /// See if the callback has been run already
954 - bool get_callback_run() const { return callback_run_; } 988 + bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); }
955 989
956 ///@} 990 ///@}
957 /// @name Custom options 991 /// @name Custom options
@@ -971,11 +1005,40 @@ class Option : public OptionBase&lt;Option&gt; { @@ -971,11 +1005,40 @@ class Option : public OptionBase&lt;Option&gt; {
971 1005
972 /// Set a custom option size 1006 /// Set a custom option size
973 Option *type_size(int option_type_size) { 1007 Option *type_size(int option_type_size) {
974 - type_size_ = option_type_size;  
975 - if(type_size_ == 0) 1008 + if(option_type_size < 0) {
  1009 + // this section is included for backwards compatibility
  1010 + type_size_max_ = -option_type_size;
  1011 + type_size_min_ = -option_type_size;
  1012 + expected_max_ = detail::expected_max_vector_size;
  1013 + } else {
  1014 + type_size_max_ = option_type_size;
  1015 + if(type_size_max_ < detail::expected_max_vector_size) {
  1016 + type_size_min_ = option_type_size;
  1017 + }
  1018 + if(type_size_max_ == 0)
  1019 + required_ = false;
  1020 + }
  1021 + return this;
  1022 + }
  1023 + /// Set a custom option type size range
  1024 + Option *type_size(int option_type_size_min, int option_type_size_max) {
  1025 + if(option_type_size_min < 0 || option_type_size_max < 0) {
  1026 + // this section is included for backwards compatibility
  1027 + expected_max_ = detail::expected_max_vector_size;
  1028 + option_type_size_min = (std::abs)(option_type_size_min);
  1029 + option_type_size_max = (std::abs)(option_type_size_max);
  1030 + }
  1031 +
  1032 + if(option_type_size_min > option_type_size_max) {
  1033 + type_size_max_ = option_type_size_min;
  1034 + type_size_min_ = option_type_size_max;
  1035 + } else {
  1036 + type_size_min_ = option_type_size_min;
  1037 + type_size_max_ = option_type_size_max;
  1038 + }
  1039 + if(type_size_max_ == 0) {
976 required_ = false; 1040 required_ = false;
977 - if(option_type_size < 0)  
978 - expected_ = -1; 1041 + }
979 return this; 1042 return this;
980 } 1043 }
981 1044
@@ -1003,7 +1066,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1003,7 +1066,8 @@ class Option : public OptionBase&lt;Option&gt; {
1003 Option *default_val(std::string val) { 1066 Option *default_val(std::string val) {
1004 default_str(val); 1067 default_str(val);
1005 auto old_results = results_; 1068 auto old_results = results_;
1006 - results_ = {val}; 1069 + results_.clear();
  1070 + add_result(val);
1007 run_callback(); 1071 run_callback();
1008 results_ = std::move(old_results); 1072 results_ = std::move(old_results);
1009 return this; 1073 return this;
@@ -1013,8 +1077,8 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1013,8 +1077,8 @@ class Option : public OptionBase&lt;Option&gt; {
1013 std::string get_type_name() const { 1077 std::string get_type_name() const {
1014 std::string full_type_name = type_name_(); 1078 std::string full_type_name = type_name_();
1015 if(!validators_.empty()) { 1079 if(!validators_.empty()) {
1016 - for(auto &validator : validators_) {  
1017 - std::string vtype = validator.get_description(); 1080 + for(auto &Validator : validators_) {
  1081 + std::string vtype = Validator.get_description();
1018 if(!vtype.empty()) { 1082 if(!vtype.empty()) {
1019 full_type_name += ":" + vtype; 1083 full_type_name += ":" + vtype;
1020 } 1084 }
@@ -1024,9 +1088,106 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1024,9 +1088,106 @@ class Option : public OptionBase&lt;Option&gt; {
1024 } 1088 }
1025 1089
1026 private: 1090 private:
1027 - // Run a result through the validators  
1028 - std::string _validate(std::string &result, int index) { 1091 + /// Run the results through the Validators
  1092 + void _validate_results(results_t &res) const {
  1093 + // Run the Validators (can change the string)
  1094 + if(!validators_.empty()) {
  1095 + if(type_size_max_ > 1) { // in this context index refers to the index in the type
  1096 + int index = 0;
  1097 + if(get_items_expected_max() < static_cast<int>(res.size()) &&
  1098 + multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
  1099 + // create a negative index for the earliest ones
  1100 + index = get_items_expected_max() - static_cast<int>(res.size());
  1101 + }
  1102 +
  1103 + for(std::string &result : res) {
  1104 + if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) {
  1105 + index = 0; // reset index for variable size chunks
  1106 + continue;
  1107 + }
  1108 + auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
  1109 + if(!err_msg.empty())
  1110 + throw ValidationError(get_name(), err_msg);
  1111 + ++index;
  1112 + }
  1113 + } else {
  1114 + int index = 0;
  1115 + if(expected_max_ < static_cast<int>(res.size()) &&
  1116 + multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
  1117 + // create a negative index for the earliest ones
  1118 + index = expected_max_ - static_cast<int>(res.size());
  1119 + }
  1120 + for(std::string &result : res) {
  1121 + auto err_msg = _validate(result, index);
  1122 + ++index;
  1123 + if(!err_msg.empty())
  1124 + throw ValidationError(get_name(), err_msg);
  1125 + }
  1126 + }
  1127 + }
  1128 + }
  1129 +
  1130 + /** reduce the results in accordance with the MultiOptionPolicy
  1131 + @param[out] res results are assigned to res if there if they are different
  1132 + */
  1133 + void _reduce_results(results_t &res, const results_t &original) const {
  1134 +
  1135 + // max num items expected or length of vector, always at least 1
  1136 + // Only valid for a trimming policy
  1137 +
  1138 + res.clear();
  1139 + // Operation depends on the policy setting
  1140 + switch(multi_option_policy_) {
  1141 + case MultiOptionPolicy::TakeAll:
  1142 + break;
  1143 + case MultiOptionPolicy::TakeLast:
  1144 + // Allow multi-option sizes (including 0)
  1145 + {
  1146 + size_t trim_size = std::min<size_t>(std::max<size_t>(get_items_expected_max(), 1), original.size());
  1147 + if(original.size() != trim_size) {
  1148 + res.assign(original.end() - trim_size, original.end());
  1149 + }
  1150 + }
  1151 + break;
  1152 + case MultiOptionPolicy::TakeFirst: {
  1153 + size_t trim_size = std::min<size_t>(std::max<size_t>(get_items_expected_max(), 1), original.size());
  1154 + if(original.size() != trim_size) {
  1155 + res.assign(original.begin(), original.begin() + trim_size);
  1156 + }
  1157 + } break;
  1158 + case MultiOptionPolicy::Join:
  1159 + if(results_.size() > 1) {
  1160 + res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
  1161 + }
  1162 + break;
  1163 + case MultiOptionPolicy::Throw:
  1164 + default: {
  1165 + auto num_min = static_cast<size_t>(get_items_expected_min());
  1166 + auto num_max = static_cast<size_t>(get_items_expected_max());
  1167 + if(num_min == 0) {
  1168 + num_min = 1;
  1169 + }
  1170 + if(num_max == 0) {
  1171 + num_max = 1;
  1172 + }
  1173 + if(original.size() < num_min) {
  1174 + throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
  1175 + }
  1176 + if(original.size() > num_max) {
  1177 + throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
  1178 + }
  1179 + break;
  1180 + }
  1181 + }
  1182 + }
  1183 +
  1184 + // Run a result through the Validators
  1185 + std::string _validate(std::string &result, int index) const {
1029 std::string err_msg; 1186 std::string err_msg;
  1187 + if(result.empty() && expected_min_ == 0) {
  1188 + // an empty with nothing expected is allowed
  1189 + return err_msg;
  1190 + }
1030 for(const auto &vali : validators_) { 1191 for(const auto &vali : validators_) {
1031 auto v = vali.get_application_index(); 1192 auto v = vali.get_application_index();
1032 if(v == -1 || v == index) { 1193 if(v == -1 || v == index) {
@@ -1039,25 +1200,37 @@ class Option : public OptionBase&lt;Option&gt; { @@ -1039,25 +1200,37 @@ class Option : public OptionBase&lt;Option&gt; {
1039 break; 1200 break;
1040 } 1201 }
1041 } 1202 }
  1203 +
1042 return err_msg; 1204 return err_msg;
1043 } 1205 }
1044 1206
1045 /// Add a single result to the result set, taking into account delimiters 1207 /// Add a single result to the result set, taking into account delimiters
1046 - int _add_result(std::string &&result) { 1208 + int _add_result(std::string &&result, std::vector<std::string> &res) const {
1047 int result_count = 0; 1209 int result_count = 0;
  1210 + if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
  1211 + result.back() == ']') { // this is now a vector string likely from the default or user entry
  1212 + result.pop_back();
  1213 +
  1214 + for(auto &var : CLI::detail::split(result.substr(1), ',')) {
  1215 + if(!var.empty()) {
  1216 + result_count += _add_result(std::move(var), res);
  1217 + }
  1218 + }
  1219 + return result_count;
  1220 + }
1048 if(delimiter_ == '\0') { 1221 if(delimiter_ == '\0') {
1049 - results_.push_back(std::move(result)); 1222 + res.push_back(std::move(result));
1050 ++result_count; 1223 ++result_count;
1051 } else { 1224 } else {
1052 if((result.find_first_of(delimiter_) != std::string::npos)) { 1225 if((result.find_first_of(delimiter_) != std::string::npos)) {
1053 for(const auto &var : CLI::detail::split(result, delimiter_)) { 1226 for(const auto &var : CLI::detail::split(result, delimiter_)) {
1054 if(!var.empty()) { 1227 if(!var.empty()) {
1055 - results_.push_back(var); 1228 + res.push_back(var);
1056 ++result_count; 1229 ++result_count;
1057 } 1230 }
1058 } 1231 }
1059 } else { 1232 } else {
1060 - results_.push_back(std::move(result)); 1233 + res.push_back(std::move(result));
1061 ++result_count; 1234 ++result_count;
1062 } 1235 }
1063 } 1236 }
include/CLI/StringTools.hpp
@@ -31,7 +31,9 @@ std::ostream &amp;operator&lt;&lt;(std::ostream &amp;in, const T &amp;item) { @@ -31,7 +31,9 @@ std::ostream &amp;operator&lt;&lt;(std::ostream &amp;in, const T &amp;item) {
31 using namespace enums; 31 using namespace enums;
32 32
33 namespace detail { 33 namespace detail {
34 - 34 +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
  35 +/// produce overflow for some expected uses
  36 +constexpr int expected_max_vector_size{1 << 29};
35 // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c 37 // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
36 /// Split a string by a delim 38 /// Split a string by a delim
37 inline std::vector<std::string> split(const std::string &s, char delim) { 39 inline std::vector<std::string> split(const std::string &s, char delim) {
include/CLI/TypeTools.hpp
@@ -274,9 +274,24 @@ struct type_count&lt; @@ -274,9 +274,24 @@ struct type_count&lt;
274 typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> { 274 typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> {
275 static constexpr int value{1}; 275 static constexpr int value{1};
276 }; 276 };
  277 +
277 /// Type size of types that look like a vector 278 /// Type size of types that look like a vector
278 template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> { 279 template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> {
279 - static constexpr int value{-1}; 280 + static constexpr int value{is_vector<typename T::value_type>::value ? expected_max_vector_size
  281 + : type_count<typename T::value_type>::value};
  282 +};
  283 +
  284 +/// This will only trigger for actual void type
  285 +template <typename T, typename Enable = void> struct expected_count { static const int value{0}; };
  286 +
  287 +/// For most types the number of expected items is 1
  288 +template <typename T>
  289 +struct expected_count<T, typename std::enable_if<!is_vector<T>::value && !std::is_void<T>::value>::type> {
  290 + static constexpr int value{1};
  291 +};
  292 +/// number of expected items in a vector
  293 +template <typename T> struct expected_count<T, typename std::enable_if<is_vector<T>::value>::type> {
  294 + static constexpr int value{expected_max_vector_size};
280 }; 295 };
281 296
282 // Enumeration of the different supported categorizations of objects 297 // Enumeration of the different supported categorizations of objects
@@ -392,11 +407,11 @@ struct classify_object&lt;T, @@ -392,11 +407,11 @@ struct classify_object&lt;T,
392 407
393 /// Tuple type 408 /// Tuple type
394 template <typename T> 409 template <typename T>
395 -struct classify_object<  
396 - T,  
397 - typename std::enable_if<type_count<T>::value >= 2 || (is_tuple_like<T>::value && uncommon_type<T>::value &&  
398 - !is_direct_constructible<T, double>::value &&  
399 - !is_direct_constructible<T, int>::value)>::type> { 410 +struct classify_object<T,
  411 + typename std::enable_if<(type_count<T>::value >= 2 && !is_vector<T>::value) ||
  412 + (is_tuple_like<T>::value && uncommon_type<T>::value &&
  413 + !is_direct_constructible<T, double>::value &&
  414 + !is_direct_constructible<T, int>::value)>::type> {
400 static constexpr objCategory value{tuple_value}; 415 static constexpr objCategory value{tuple_value};
401 }; 416 };
402 417
@@ -450,17 +465,11 @@ constexpr const char *type_name() { @@ -450,17 +465,11 @@ constexpr const char *type_name() {
450 return "TEXT"; 465 return "TEXT";
451 } 466 }
452 467
453 -/// This one should not be used normally, since vector types print the internal type  
454 -template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>  
455 -constexpr const char *type_name() {  
456 - return type_name<typename T::value_type>();  
457 -}  
458 -  
459 /// Print name for single element tuple types 468 /// Print name for single element tuple types
460 template < 469 template <
461 typename T, 470 typename T,
462 enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 1, detail::enabler> = detail::dummy> 471 enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 1, detail::enabler> = detail::dummy>
463 -std::string type_name() { 472 +inline std::string type_name() {
464 return type_name<typename std::tuple_element<0, T>::type>(); 473 return type_name<typename std::tuple_element<0, T>::type>();
465 } 474 }
466 475
@@ -489,6 +498,12 @@ std::string type_name() { @@ -489,6 +498,12 @@ std::string type_name() {
489 return tname; 498 return tname;
490 } 499 }
491 500
  501 +/// This one should not be used normally, since vector types print the internal type
  502 +template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>
  503 +inline std::string type_name() {
  504 + return type_name<typename T::value_type>();
  505 +}
  506 +
492 // Lexical cast 507 // Lexical cast
493 508
494 /// Convert a flag into an integer value typically binary flags 509 /// Convert a flag into an integer value typically binary flags
@@ -673,19 +688,37 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) { @@ -673,19 +688,37 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
673 } 688 }
674 689
675 /// Assign a value through lexical cast operations 690 /// Assign a value through lexical cast operations
676 -template <typename T, typename XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy> 691 +template <typename T,
  692 + typename XC,
  693 + enable_if_t<std::is_same<T, XC>::value && (classify_object<T>::value == string_assignable ||
  694 + classify_object<T>::value == string_constructible),
  695 + detail::enabler> = detail::dummy>
677 bool lexical_assign(const std::string &input, T &output) { 696 bool lexical_assign(const std::string &input, T &output) {
678 return lexical_cast(input, output); 697 return lexical_cast(input, output);
679 } 698 }
680 699
  700 +/// Assign a value through lexical cast operations
  701 +template <typename T,
  702 + typename XC,
  703 + enable_if_t<std::is_same<T, XC>::value && classify_object<T>::value != string_assignable &&
  704 + classify_object<T>::value != string_constructible,
  705 + detail::enabler> = detail::dummy>
  706 +bool lexical_assign(const std::string &input, T &output) {
  707 + if(input.empty()) {
  708 + output = T{};
  709 + return true;
  710 + }
  711 + return lexical_cast(input, output);
  712 +}
  713 +
681 /// Assign a value converted from a string in lexical cast to the output value directly 714 /// Assign a value converted from a string in lexical cast to the output value directly
682 template < 715 template <
683 typename T, 716 typename T,
684 typename XC, 717 typename XC,
685 enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy> 718 enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
686 bool lexical_assign(const std::string &input, T &output) { 719 bool lexical_assign(const std::string &input, T &output) {
687 - XC val;  
688 - bool parse_result = lexical_cast<XC>(input, val); 720 + XC val{};
  721 + bool parse_result = (!input.empty()) ? lexical_cast<XC>(input, val) : true;
689 if(parse_result) { 722 if(parse_result) {
690 output = val; 723 output = val;
691 } 724 }
@@ -699,18 +732,19 @@ template &lt;typename T, @@ -699,18 +732,19 @@ template &lt;typename T,
699 std::is_move_assignable<T>::value, 732 std::is_move_assignable<T>::value,
700 detail::enabler> = detail::dummy> 733 detail::enabler> = detail::dummy>
701 bool lexical_assign(const std::string &input, T &output) { 734 bool lexical_assign(const std::string &input, T &output) {
702 - XC val;  
703 - bool parse_result = lexical_cast<XC>(input, val); 735 + XC val{};
  736 + bool parse_result = input.empty() ? true : lexical_cast<XC>(input, val);
704 if(parse_result) { 737 if(parse_result) {
705 output = T(val); // use () form of constructor to allow some implicit conversions 738 output = T(val); // use () form of constructor to allow some implicit conversions
706 } 739 }
707 return parse_result; 740 return parse_result;
708 } 741 }
709 /// Lexical conversion if there is only one element 742 /// Lexical conversion if there is only one element
710 -template <typename T,  
711 - typename XC,  
712 - enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value, detail::enabler> =  
713 - detail::dummy> 743 +template <
  744 + typename T,
  745 + typename XC,
  746 + enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value && !is_vector<XC>::value,
  747 + detail::enabler> = detail::dummy>
714 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { 748 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
715 return lexical_assign<T, XC>(strings[0], output); 749 return lexical_assign<T, XC>(strings[0], output);
716 } 750 }
@@ -722,9 +756,9 @@ template &lt;typename T, @@ -722,9 +756,9 @@ template &lt;typename T,
722 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { 756 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
723 typename std::tuple_element<0, XC>::type v1; 757 typename std::tuple_element<0, XC>::type v1;
724 typename std::tuple_element<1, XC>::type v2; 758 typename std::tuple_element<1, XC>::type v2;
725 - bool retval = lexical_cast(strings[0], v1); 759 + bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
726 if(strings.size() > 1) { 760 if(strings.size() > 1) {
727 - retval &= lexical_cast(strings[1], v2); 761 + retval &= lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
728 } 762 }
729 if(retval) { 763 if(retval) {
730 output = T{v1, v2}; 764 output = T{v1, v2};
@@ -735,7 +769,9 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) { @@ -735,7 +769,9 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
735 /// Lexical conversion of a vector types 769 /// Lexical conversion of a vector types
736 template <class T, 770 template <class T,
737 class XC, 771 class XC,
738 - enable_if_t<type_count<T>::value == -1 && type_count<XC>::value == -1, detail::enabler> = detail::dummy> 772 + enable_if_t<expected_count<T>::value == expected_max_vector_size &&
  773 + expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 1,
  774 + detail::enabler> = detail::dummy>
739 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { 775 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
740 bool retval = true; 776 bool retval = true;
741 output.clear(); 777 output.clear();
@@ -748,10 +784,38 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) { @@ -748,10 +784,38 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
748 return (!output.empty()) && retval; 784 return (!output.empty()) && retval;
749 } 785 }
750 786
  787 +/// Lexical conversion of a vector types with type size of two
  788 +template <class T,
  789 + class XC,
  790 + enable_if_t<expected_count<T>::value == expected_max_vector_size &&
  791 + expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 2,
  792 + detail::enabler> = detail::dummy>
  793 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  794 + bool retval = true;
  795 + output.clear();
  796 + for(size_t ii = 0; ii < strings.size(); ii += 2) {
  797 +
  798 + typename std::tuple_element<0, typename XC::value_type>::type v1;
  799 + typename std::tuple_element<1, typename XC::value_type>::type v2;
  800 + retval = lexical_assign<decltype(v1), decltype(v1)>(strings[ii], v1);
  801 + if(strings.size() > ii + 1) {
  802 + retval &= lexical_assign<decltype(v2), decltype(v2)>(strings[ii + 1], v2);
  803 + }
  804 + if(retval) {
  805 + output.emplace_back(v1, v2);
  806 + } else {
  807 + return false;
  808 + }
  809 + }
  810 + return (!output.empty()) && retval;
  811 +}
  812 +
751 /// Conversion to a vector type using a particular single type as the conversion type 813 /// Conversion to a vector type using a particular single type as the conversion type
752 template <class T, 814 template <class T,
753 class XC, 815 class XC,
754 - enable_if_t<(type_count<T>::value == -1) && (type_count<XC>::value == 1), detail::enabler> = detail::dummy> 816 + enable_if_t<(expected_count<T>::value == expected_max_vector_size) && (expected_count<XC>::value == 1) &&
  817 + (type_count<XC>::value == 1),
  818 + detail::enabler> = detail::dummy>
755 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { 819 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
756 bool retval = true; 820 bool retval = true;
757 output.clear(); 821 output.clear();
@@ -763,6 +827,23 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) { @@ -763,6 +827,23 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
763 } 827 }
764 return (!output.empty()) && retval; 828 return (!output.empty()) && retval;
765 } 829 }
  830 +// This one is last since it can call other lexical_conversion functions
  831 +/// Lexical conversion if there is only one element but the conversion type is a vector
  832 +template <typename T,
  833 + typename XC,
  834 + enable_if_t<!is_tuple_like<T>::value && !is_vector<T>::value && is_vector<XC>::value, detail::enabler> =
  835 + detail::dummy>
  836 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  837 +
  838 + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) {
  839 + XC val;
  840 + auto retval = lexical_conversion<XC, XC>(strings, val);
  841 + output = T{val};
  842 + return retval;
  843 + }
  844 + output = T{};
  845 + return true;
  846 +}
766 847
767 /// function template for converting tuples if the static Index is greater than the tuple size 848 /// function template for converting tuples if the static Index is greater than the tuple size
768 template <class T, class XC, std::size_t I> 849 template <class T, class XC, std::size_t I>
@@ -794,6 +875,38 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) { @@ -794,6 +875,38 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
794 return tuple_conversion<T, XC, 0>(strings, output); 875 return tuple_conversion<T, XC, 0>(strings, output);
795 } 876 }
796 877
  878 +/// Lexical conversion of a vector types with type_size >2
  879 +template <class T,
  880 + class XC,
  881 + enable_if_t<expected_count<T>::value == expected_max_vector_size &&
  882 + expected_count<XC>::value == expected_max_vector_size && (type_count<XC>::value > 2),
  883 + detail::enabler> = detail::dummy>
  884 +bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
  885 + bool retval = true;
  886 + output.clear();
  887 + std::vector<std::string> temp;
  888 + size_t ii = 0;
  889 + size_t icount = 0;
  890 + size_t xcm = type_count<XC>::value;
  891 + while(ii < strings.size()) {
  892 + temp.push_back(strings[ii]);
  893 + ++ii;
  894 + ++icount;
  895 + if(icount == xcm || temp.back().empty()) {
  896 + if(static_cast<int>(xcm) == expected_max_vector_size) {
  897 + temp.pop_back();
  898 + }
  899 + output.emplace_back();
  900 + retval = retval && lexical_conversion<typename T::value_type, typename XC::value_type>(temp, output.back());
  901 + temp.clear();
  902 + if(!retval) {
  903 + return false;
  904 + }
  905 + icount = 0;
  906 + }
  907 + }
  908 + return retval;
  909 +}
797 /// Sum a vector of flag representations 910 /// Sum a vector of flag representations
798 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is 911 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
799 /// by 912 /// by
include/CLI/Validators.hpp
@@ -325,14 +325,14 @@ class IPV4Validator : public Validator { @@ -325,14 +325,14 @@ class IPV4Validator : public Validator {
325 func_ = [](std::string &ip_addr) { 325 func_ = [](std::string &ip_addr) {
326 auto result = CLI::detail::split(ip_addr, '.'); 326 auto result = CLI::detail::split(ip_addr, '.');
327 if(result.size() != 4) { 327 if(result.size() != 4) {
328 - return "Invalid IPV4 address must have four parts " + ip_addr; 328 + return "Invalid IPV4 address must have four parts (" + ip_addr + ')';
329 } 329 }
330 int num; 330 int num;
331 bool retval = true; 331 bool retval = true;
332 for(const auto &var : result) { 332 for(const auto &var : result) {
333 retval &= detail::lexical_cast(var, num); 333 retval &= detail::lexical_cast(var, num);
334 if(!retval) { 334 if(!retval) {
335 - return "Failed parsing number " + var; 335 + return "Failed parsing number (" + var + ')';
336 } 336 }
337 if(num < 0 || num > 255) { 337 if(num < 0 || num > 255) {
338 return "Each IP number must be between 0 and 255 " + var; 338 return "Each IP number must be between 0 and 255 " + var;
@@ -350,10 +350,10 @@ class PositiveNumber : public Validator { @@ -350,10 +350,10 @@ class PositiveNumber : public Validator {
350 func_ = [](std::string &number_str) { 350 func_ = [](std::string &number_str) {
351 int number; 351 int number;
352 if(!detail::lexical_cast(number_str, number)) { 352 if(!detail::lexical_cast(number_str, number)) {
353 - return "Failed parsing number " + number_str; 353 + return "Failed parsing number: (" + number_str + ')';
354 } 354 }
355 if(number < 0) { 355 if(number < 0) {
356 - return "Number less then 0 " + number_str; 356 + return "Number less then 0: (" + number_str + ')';
357 } 357 }
358 return std::string(); 358 return std::string();
359 }; 359 };
@@ -367,7 +367,7 @@ class Number : public Validator { @@ -367,7 +367,7 @@ class Number : public Validator {
367 func_ = [](std::string &number_str) { 367 func_ = [](std::string &number_str) {
368 double number; 368 double number;
369 if(!detail::lexical_cast(number_str, number)) { 369 if(!detail::lexical_cast(number_str, number)) {
370 - return "Failed parsing as a number " + number_str; 370 + return "Failed parsing as a number (" + number_str + ')';
371 } 371 }
372 return std::string(); 372 return std::string();
373 }; 373 };
tests/AppTest.cpp
@@ -40,12 +40,14 @@ TEST_F(TApp, OneFlagShortValuesAs) { @@ -40,12 +40,14 @@ TEST_F(TApp, OneFlagShortValuesAs) {
40 flg->take_last(); 40 flg->take_last();
41 EXPECT_EQ(opt->as<int>(), 2); 41 EXPECT_EQ(opt->as<int>(), 2);
42 flg->multi_option_policy(CLI::MultiOptionPolicy::Throw); 42 flg->multi_option_policy(CLI::MultiOptionPolicy::Throw);
43 - EXPECT_THROW(opt->as<int>(), CLI::ConversionError);  
44 - 43 + EXPECT_THROW(opt->as<int>(), CLI::ArgumentMismatch);
  44 + flg->multi_option_policy(CLI::MultiOptionPolicy::TakeAll);
45 auto vec = opt->as<std::vector<int>>(); 45 auto vec = opt->as<std::vector<int>>();
46 EXPECT_EQ(vec[0], 1); 46 EXPECT_EQ(vec[0], 1);
47 EXPECT_EQ(vec[1], 2); 47 EXPECT_EQ(vec[1], 2);
48 flg->multi_option_policy(CLI::MultiOptionPolicy::Join); 48 flg->multi_option_policy(CLI::MultiOptionPolicy::Join);
  49 + EXPECT_EQ(opt->as<std::string>(), "1\n2");
  50 + flg->delimiter(',');
49 EXPECT_EQ(opt->as<std::string>(), "1,2"); 51 EXPECT_EQ(opt->as<std::string>(), "1,2");
50 } 52 }
51 53
@@ -403,6 +405,27 @@ TEST_F(TApp, OneStringEqualVersionSingleStringQuotedMultipleWithEqualAndProgram) @@ -403,6 +405,27 @@ TEST_F(TApp, OneStringEqualVersionSingleStringQuotedMultipleWithEqualAndProgram)
403 EXPECT_EQ(str4, "Unquoted"); 405 EXPECT_EQ(str4, "Unquoted");
404 } 406 }
405 407
  408 +TEST_F(TApp, OneStringFlagLike) {
  409 + std::string str{"something"};
  410 + app.add_option("-s,--string", str)->expected(0, 1);
  411 + args = {"--string"};
  412 + run();
  413 + EXPECT_EQ(1u, app.count("-s"));
  414 + EXPECT_EQ(1u, app.count("--string"));
  415 + EXPECT_TRUE(str.empty());
  416 +}
  417 +
  418 +TEST_F(TApp, OneIntFlagLike) {
  419 + int val;
  420 + auto opt = app.add_option("-i", val)->expected(0, 1);
  421 + args = {"-i"};
  422 + run();
  423 + EXPECT_EQ(1u, app.count("-i"));
  424 + opt->default_str("7");
  425 + run();
  426 + EXPECT_EQ(val, 7);
  427 +}
  428 +
406 TEST_F(TApp, TogetherInt) { 429 TEST_F(TApp, TogetherInt) {
407 int i; 430 int i;
408 app.add_option("-i,--int", i); 431 app.add_option("-i,--int", i);
@@ -620,6 +643,42 @@ TEST_F(TApp, BoolAndIntFlags) { @@ -620,6 +643,42 @@ TEST_F(TApp, BoolAndIntFlags) {
620 EXPECT_EQ((unsigned int)2, uflag); 643 EXPECT_EQ((unsigned int)2, uflag);
621 } 644 }
622 645
  646 +TEST_F(TApp, FlagLikeOption) {
  647 + bool val = false;
  648 + auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
  649 + args = {"--flag"};
  650 + run();
  651 + EXPECT_EQ(1u, app.count("--flag"));
  652 + EXPECT_TRUE(val);
  653 + val = false;
  654 + opt->type_size(0, 0); // should be the same as above
  655 + EXPECT_EQ(opt->get_type_size_min(), 0);
  656 + EXPECT_EQ(opt->get_type_size_max(), 0);
  657 + run();
  658 + EXPECT_EQ(1u, app.count("--flag"));
  659 + EXPECT_TRUE(val);
  660 +}
  661 +
  662 +TEST_F(TApp, FlagLikeIntOption) {
  663 + int val = -47;
  664 + auto opt = app.add_option("--flag", val)->expected(0, 1);
  665 + // normally some default value should be set, but this test is for some paths in the validators checks to skip
  666 + // validation on empty string if nothing is expected
  667 + opt->check(CLI::PositiveNumber);
  668 + args = {"--flag"};
  669 + EXPECT_TRUE(opt->as<std::string>().empty());
  670 + run();
  671 + EXPECT_EQ(1u, app.count("--flag"));
  672 + EXPECT_NE(val, -47);
  673 + args = {"--flag", "12"};
  674 + run();
  675 +
  676 + EXPECT_EQ(val, 12);
  677 + args.clear();
  678 + run();
  679 + EXPECT_TRUE(opt->as<std::string>().empty());
  680 +}
  681 +
623 TEST_F(TApp, BoolOnlyFlag) { 682 TEST_F(TApp, BoolOnlyFlag) {
624 bool bflag; 683 bool bflag;
625 app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw); 684 app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
@@ -629,7 +688,7 @@ TEST_F(TApp, BoolOnlyFlag) { @@ -629,7 +688,7 @@ TEST_F(TApp, BoolOnlyFlag) {
629 EXPECT_TRUE(bflag); 688 EXPECT_TRUE(bflag);
630 689
631 args = {"-b", "-b"}; 690 args = {"-b", "-b"};
632 - EXPECT_THROW(run(), CLI::ConversionError); 691 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
633 } 692 }
634 693
635 TEST_F(TApp, BoolOption) { 694 TEST_F(TApp, BoolOption) {
@@ -800,9 +859,51 @@ TEST_F(TApp, TakeLastOptMulti) { @@ -800,9 +859,51 @@ TEST_F(TApp, TakeLastOptMulti) {
800 EXPECT_EQ(vals, std::vector<int>({2, 3})); 859 EXPECT_EQ(vals, std::vector<int>({2, 3}));
801 } 860 }
802 861
  862 +TEST_F(TApp, vectorDefaults) {
  863 + std::vector<int> vals{4, 5};
  864 + auto opt = app.add_option("--long", vals, "", true);
  865 +
  866 + args = {"--long", "[1,2,3]"};
  867 +
  868 + run();
  869 +
  870 + EXPECT_EQ(vals, std::vector<int>({1, 2, 3}));
  871 +
  872 + args.clear();
  873 + run();
  874 + auto res = app["--long"]->as<std::vector<int>>();
  875 + EXPECT_EQ(res, std::vector<int>({4, 5}));
  876 +
  877 + app.clear();
  878 + opt->expected(1)->take_last();
  879 + res = app["--long"]->as<std::vector<int>>();
  880 + EXPECT_EQ(res, std::vector<int>({5}));
  881 + opt->take_first();
  882 + res = app["--long"]->as<std::vector<int>>();
  883 + EXPECT_EQ(res, std::vector<int>({4}));
  884 +
  885 + opt->expected(0, 1)->take_last();
  886 + run();
  887 +
  888 + EXPECT_EQ(res, std::vector<int>({4}));
  889 + res = app["--long"]->as<std::vector<int>>();
  890 + EXPECT_EQ(res, std::vector<int>({5}));
  891 +}
  892 +
  893 +TEST_F(TApp, TakeLastOptMulti_alternative_path) {
  894 + std::vector<int> vals;
  895 + app.add_option("--long", vals)->expected(2, -1)->take_last();
  896 +
  897 + args = {"--long", "1", "2", "3"};
  898 +
  899 + run();
  900 +
  901 + EXPECT_EQ(vals, std::vector<int>({2, 3}));
  902 +}
  903 +
803 TEST_F(TApp, TakeLastOptMultiCheck) { 904 TEST_F(TApp, TakeLastOptMultiCheck) {
804 std::vector<int> vals; 905 std::vector<int> vals;
805 - auto opt = app.add_option("--long", vals)->expected(2)->take_last(); 906 + auto opt = app.add_option("--long", vals)->expected(-2)->take_last();
806 907
807 opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0)); 908 opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
808 opt->check((!CLI::PositiveNumber).application_index(1)); 909 opt->check((!CLI::PositiveNumber).application_index(1));
@@ -826,7 +927,7 @@ TEST_F(TApp, TakeFirstOptMulti) { @@ -826,7 +927,7 @@ TEST_F(TApp, TakeFirstOptMulti) {
826 927
827 TEST_F(TApp, ComplexOptMulti) { 928 TEST_F(TApp, ComplexOptMulti) {
828 std::complex<double> val; 929 std::complex<double> val;
829 - app.add_complex("--long", val)->take_first(); 930 + app.add_complex("--long", val)->take_first()->allow_extra_args();
830 931
831 args = {"--long", "1", "2", "3", "4"}; 932 args = {"--long", "1", "2", "3", "4"};
832 933
@@ -1141,7 +1242,7 @@ TEST_F(TApp, RequiredOptsUnlimited) { @@ -1141,7 +1242,7 @@ TEST_F(TApp, RequiredOptsUnlimited) {
1141 1242
1142 app.allow_extras(false); 1243 app.allow_extras(false);
1143 std::vector<std::string> remain; 1244 std::vector<std::string> remain;
1144 - app.add_option("positional", remain); 1245 + auto popt = app.add_option("positional", remain);
1145 run(); 1246 run();
1146 EXPECT_EQ(strs, std::vector<std::string>({"one", "two"})); 1247 EXPECT_EQ(strs, std::vector<std::string>({"one", "two"}));
1147 EXPECT_EQ(remain, std::vector<std::string>()); 1248 EXPECT_EQ(remain, std::vector<std::string>());
@@ -1157,6 +1258,12 @@ TEST_F(TApp, RequiredOptsUnlimited) { @@ -1157,6 +1258,12 @@ TEST_F(TApp, RequiredOptsUnlimited) {
1157 run(); 1258 run();
1158 EXPECT_EQ(strs, std::vector<std::string>({"two"})); 1259 EXPECT_EQ(strs, std::vector<std::string>({"two"}));
1159 EXPECT_EQ(remain, std::vector<std::string>({"one"})); 1260 EXPECT_EQ(remain, std::vector<std::string>({"one"}));
  1261 +
  1262 + args = {"--str", "one", "two"};
  1263 + popt->required();
  1264 + run();
  1265 + EXPECT_EQ(strs, std::vector<std::string>({"one"}));
  1266 + EXPECT_EQ(remain, std::vector<std::string>({"two"}));
1160 } 1267 }
1161 1268
1162 TEST_F(TApp, RequiredOptsUnlimitedShort) { 1269 TEST_F(TApp, RequiredOptsUnlimitedShort) {
@@ -1217,9 +1324,9 @@ TEST_F(TApp, OptsUnlimitedEnd) { @@ -1217,9 +1324,9 @@ TEST_F(TApp, OptsUnlimitedEnd) {
1217 TEST_F(TApp, RequireOptPriority) { 1324 TEST_F(TApp, RequireOptPriority) {
1218 1325
1219 std::vector<std::string> strs; 1326 std::vector<std::string> strs;
1220 - app.add_option("--str", strs)->required(); 1327 + app.add_option("--str", strs);
1221 std::vector<std::string> remain; 1328 std::vector<std::string> remain;
1222 - app.add_option("positional", remain)->expected(2); 1329 + app.add_option("positional", remain)->expected(2)->required();
1223 1330
1224 args = {"--str", "one", "two", "three"}; 1331 args = {"--str", "one", "two", "three"};
1225 run(); 1332 run();
@@ -1239,7 +1346,7 @@ TEST_F(TApp, RequireOptPriorityShort) { @@ -1239,7 +1346,7 @@ TEST_F(TApp, RequireOptPriorityShort) {
1239 std::vector<std::string> strs; 1346 std::vector<std::string> strs;
1240 app.add_option("-s", strs)->required(); 1347 app.add_option("-s", strs)->required();
1241 std::vector<std::string> remain; 1348 std::vector<std::string> remain;
1242 - app.add_option("positional", remain)->expected(2); 1349 + app.add_option("positional", remain)->expected(2)->required();
1243 1350
1244 args = {"-s", "one", "two", "three"}; 1351 args = {"-s", "one", "two", "three"};
1245 run(); 1352 run();
@@ -1330,7 +1437,7 @@ TEST_F(TApp, CallbackBoolFlags) { @@ -1330,7 +1437,7 @@ TEST_F(TApp, CallbackBoolFlags) {
1330 EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction); 1437 EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
1331 cback->multi_option_policy(CLI::MultiOptionPolicy::Throw); 1438 cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
1332 args = {"--val", "--val=false"}; 1439 args = {"--val", "--val=false"};
1333 - EXPECT_THROW(run(), CLI::ConversionError); 1440 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
1334 } 1441 }
1335 1442
1336 TEST_F(TApp, CallbackFlagsFalse) { 1443 TEST_F(TApp, CallbackFlagsFalse) {
@@ -1638,7 +1745,7 @@ TEST_F(TApp, pair_check) { @@ -1638,7 +1745,7 @@ TEST_F(TApp, pair_check) {
1638 } 1745 }
1639 1746
1640 // this will require that modifying the multi-option policy for tuples be allowed which it isn't at present 1747 // this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
1641 -/* 1748 +
1642 TEST_F(TApp, pair_check_take_first) { 1749 TEST_F(TApp, pair_check_take_first) {
1643 std::string myfile{"pair_check_file2.txt"}; 1750 std::string myfile{"pair_check_file2.txt"};
1644 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 1751 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
@@ -1663,7 +1770,7 @@ TEST_F(TApp, pair_check_take_first) { @@ -1663,7 +1770,7 @@ TEST_F(TApp, pair_check_take_first) {
1663 1770
1664 EXPECT_THROW(run(), CLI::ValidationError); 1771 EXPECT_THROW(run(), CLI::ValidationError);
1665 } 1772 }
1666 -*/ 1773 +
1667 TEST_F(TApp, VectorFixedString) { 1774 TEST_F(TApp, VectorFixedString) {
1668 std::vector<std::string> strvec; 1775 std::vector<std::string> strvec;
1669 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 1776 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@@ -1720,7 +1827,7 @@ TEST_F(TApp, DefaultedResult) { @@ -1720,7 +1827,7 @@ TEST_F(TApp, DefaultedResult) {
1720 opts->results(nString); 1827 opts->results(nString);
1721 EXPECT_EQ(nString, "NA"); 1828 EXPECT_EQ(nString, "NA");
1722 int newIval; 1829 int newIval;
1723 - EXPECT_THROW(optv->results(newIval), CLI::ConversionError); 1830 + // EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
1724 optv->default_str("442"); 1831 optv->default_str("442");
1725 optv->results(newIval); 1832 optv->results(newIval);
1726 EXPECT_EQ(newIval, 442); 1833 EXPECT_EQ(newIval, 442);
@@ -1731,7 +1838,8 @@ TEST_F(TApp, VectorUnlimString) { @@ -1731,7 +1838,8 @@ TEST_F(TApp, VectorUnlimString) {
1731 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 1838 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
1732 1839
1733 CLI::Option *opt = app.add_option("-s,--string", strvec); 1840 CLI::Option *opt = app.add_option("-s,--string", strvec);
1734 - EXPECT_EQ(-1, opt->get_expected()); 1841 + EXPECT_EQ(1, opt->get_expected());
  1842 + EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max());
1735 1843
1736 args = {"--string", "mystring", "mystring2", "mystring3"}; 1844 args = {"--string", "mystring", "mystring2", "mystring3"};
1737 run(); 1845 run();
@@ -1744,6 +1852,35 @@ TEST_F(TApp, VectorUnlimString) { @@ -1744,6 +1852,35 @@ TEST_F(TApp, VectorUnlimString) {
1744 EXPECT_EQ(answer, strvec); 1852 EXPECT_EQ(answer, strvec);
1745 } 1853 }
1746 1854
  1855 +TEST_F(TApp, VectorExpectedRange) {
  1856 + std::vector<std::string> strvec;
  1857 +
  1858 + CLI::Option *opt = app.add_option("--string", strvec);
  1859 + opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
  1860 +
  1861 + args = {"--string", "mystring", "mystring2", "mystring3"};
  1862 + run();
  1863 + EXPECT_EQ(3u, app.count("--string"));
  1864 +
  1865 + args = {"--string", "mystring"};
  1866 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  1867 +
  1868 + args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"};
  1869 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  1870 +
  1871 + EXPECT_EQ(opt->get_expected_max(), 4);
  1872 + EXPECT_EQ(opt->get_expected_min(), 2);
  1873 + opt->expected(4, 2); // just test the handling of reversed arguments
  1874 + EXPECT_EQ(opt->get_expected_max(), 4);
  1875 + EXPECT_EQ(opt->get_expected_min(), 2);
  1876 + opt->expected(-5);
  1877 + EXPECT_EQ(opt->get_expected_max(), 5);
  1878 + EXPECT_EQ(opt->get_expected_min(), 5);
  1879 + opt->expected(-5, 7);
  1880 + EXPECT_EQ(opt->get_expected_max(), 7);
  1881 + EXPECT_EQ(opt->get_expected_min(), 5);
  1882 +}
  1883 +
1747 TEST_F(TApp, VectorFancyOpts) { 1884 TEST_F(TApp, VectorFancyOpts) {
1748 std::vector<std::string> strvec; 1885 std::vector<std::string> strvec;
1749 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 1886 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@@ -2227,6 +2364,138 @@ TEST_F(TApp, CustomDoubleOptionAlt) { @@ -2227,6 +2364,138 @@ TEST_F(TApp, CustomDoubleOptionAlt) {
2227 EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); 2364 EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
2228 } 2365 }
2229 2366
  2367 +// now with independent type sizes and expected this is possible
  2368 +TEST_F(TApp, vectorPair) {
  2369 +
  2370 + std::vector<std::pair<int, std::string>> custom_opt;
  2371 +
  2372 + auto opt = app.add_option("--dict", custom_opt);
  2373 +
  2374 + args = {"--dict", "1", "str1", "--dict", "3", "str3"};
  2375 +
  2376 + run();
  2377 + EXPECT_EQ(custom_opt.size(), 2u);
  2378 + EXPECT_EQ(custom_opt[0].first, 1);
  2379 + EXPECT_EQ(custom_opt[1].second, "str3");
  2380 +
  2381 + args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"};
  2382 + run();
  2383 + EXPECT_EQ(custom_opt.size(), 3u);
  2384 + EXPECT_EQ(custom_opt[2].first, -1);
  2385 + EXPECT_EQ(custom_opt[2].second, "str4");
  2386 + opt->check(CLI::PositiveNumber.application_index(0));
  2387 +
  2388 + EXPECT_THROW(run(), CLI::ValidationError);
  2389 +}
  2390 +
  2391 +TEST_F(TApp, vectorPairFail) {
  2392 +
  2393 + std::vector<std::pair<int, std::string>> custom_opt;
  2394 +
  2395 + app.add_option("--dict", custom_opt);
  2396 +
  2397 + args = {"--dict", "1", "str1", "--dict", "str3", "1"};
  2398 +
  2399 + EXPECT_THROW(run(), CLI::ConversionError);
  2400 +}
  2401 +
  2402 +TEST_F(TApp, vectorPairTypeRange) {
  2403 +
  2404 + std::vector<std::pair<int, std::string>> custom_opt;
  2405 +
  2406 + auto opt = app.add_option("--dict", custom_opt);
  2407 +
  2408 + opt->type_size(2, 1); // just test switched arguments
  2409 + EXPECT_EQ(opt->get_type_size_min(), 1);
  2410 + EXPECT_EQ(opt->get_type_size_max(), 2);
  2411 +
  2412 + args = {"--dict", "1", "str1", "--dict", "3", "str3"};
  2413 +
  2414 + run();
  2415 + EXPECT_EQ(custom_opt.size(), 2u);
  2416 + EXPECT_EQ(custom_opt[0].first, 1);
  2417 + EXPECT_EQ(custom_opt[1].second, "str3");
  2418 +
  2419 + args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
  2420 + run();
  2421 + EXPECT_EQ(custom_opt.size(), 3u);
  2422 + EXPECT_TRUE(custom_opt[1].second.empty());
  2423 + EXPECT_EQ(custom_opt[2].first, -1);
  2424 + EXPECT_EQ(custom_opt[2].second, "str4");
  2425 +
  2426 + opt->type_size(-2, -1); // test negative arguments
  2427 + EXPECT_EQ(opt->get_type_size_min(), 1);
  2428 + EXPECT_EQ(opt->get_type_size_max(), 2);
  2429 + // this type size spec should run exactly as before
  2430 + run();
  2431 + EXPECT_EQ(custom_opt.size(), 3u);
  2432 + EXPECT_TRUE(custom_opt[1].second.empty());
  2433 + EXPECT_EQ(custom_opt[2].first, -1);
  2434 + EXPECT_EQ(custom_opt[2].second, "str4");
  2435 +}
  2436 +
  2437 +// now with independent type sizes and expected this is possible
  2438 +TEST_F(TApp, vectorTuple) {
  2439 +
  2440 + std::vector<std::tuple<int, std::string, double>> custom_opt;
  2441 +
  2442 + auto opt = app.add_option("--dict", custom_opt);
  2443 +
  2444 + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
  2445 +
  2446 + run();
  2447 + EXPECT_EQ(custom_opt.size(), 2u);
  2448 + EXPECT_EQ(std::get<0>(custom_opt[0]), 1);
  2449 + EXPECT_EQ(std::get<1>(custom_opt[1]), "str3");
  2450 + EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7);
  2451 +
  2452 + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
  2453 + run();
  2454 + EXPECT_EQ(custom_opt.size(), 3u);
  2455 + EXPECT_EQ(std::get<0>(custom_opt[2]), -1);
  2456 + EXPECT_EQ(std::get<1>(custom_opt[2]), "str4");
  2457 + EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87);
  2458 + opt->check(CLI::PositiveNumber.application_index(0));
  2459 +
  2460 + EXPECT_THROW(run(), CLI::ValidationError);
  2461 +
  2462 + args.back() = "haha";
  2463 + args[9] = "45";
  2464 + EXPECT_THROW(run(), CLI::ConversionError);
  2465 +}
  2466 +
  2467 +// now with independent type sizes and expected this is possible
  2468 +TEST_F(TApp, vectorVector) {
  2469 +
  2470 + std::vector<std::vector<int>> custom_opt;
  2471 +
  2472 + auto opt = app.add_option("--dict", custom_opt);
  2473 +
  2474 + args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
  2475 +
  2476 + run();
  2477 + EXPECT_EQ(custom_opt.size(), 2u);
  2478 + EXPECT_EQ(custom_opt[0].size(), 3u);
  2479 + EXPECT_EQ(custom_opt[1].size(), 2u);
  2480 +
  2481 + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
  2482 + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"};
  2483 + run();
  2484 + EXPECT_EQ(custom_opt.size(), 4u);
  2485 + EXPECT_EQ(custom_opt[0].size(), 3u);
  2486 + EXPECT_EQ(custom_opt[1].size(), 2u);
  2487 + EXPECT_EQ(custom_opt[2].size(), 1u);
  2488 + EXPECT_EQ(custom_opt[3].size(), 10u);
  2489 + opt->check(CLI::PositiveNumber.application_index(9));
  2490 +
  2491 + EXPECT_THROW(run(), CLI::ValidationError);
  2492 + args.pop_back();
  2493 + EXPECT_NO_THROW(run());
  2494 +
  2495 + args.back() = "haha";
  2496 + EXPECT_THROW(run(), CLI::ConversionError);
  2497 +}
  2498 +
2230 // #128 2499 // #128
2231 TEST_F(TApp, RepeatingMultiArgumentOptions) { 2500 TEST_F(TApp, RepeatingMultiArgumentOptions) {
2232 std::vector<std::string> entries; 2501 std::vector<std::string> entries;
tests/CreationTest.cpp
@@ -173,46 +173,6 @@ TEST_F(TApp, IncorrectConstructionFlagPositional3) { @@ -173,46 +173,6 @@ TEST_F(TApp, IncorrectConstructionFlagPositional3) {
173 EXPECT_THROW(app.add_flag("cat", x), CLI::IncorrectConstruction); 173 EXPECT_THROW(app.add_flag("cat", x), CLI::IncorrectConstruction);
174 } 174 }
175 175
176 -TEST_F(TApp, IncorrectConstructionFlagExpected) {  
177 - auto cat = app.add_flag("--cat");  
178 - EXPECT_THROW(cat->expected(0), CLI::IncorrectConstruction);  
179 - EXPECT_THROW(cat->expected(1), CLI::IncorrectConstruction);  
180 -}  
181 -  
182 -TEST_F(TApp, IncorrectConstructionOptionAsFlag) {  
183 - int x;  
184 - auto cat = app.add_option("--cat", x);  
185 - EXPECT_NO_THROW(cat->expected(1));  
186 - EXPECT_THROW(cat->expected(0), CLI::IncorrectConstruction);  
187 - EXPECT_THROW(cat->expected(2), CLI::IncorrectConstruction);  
188 -}  
189 -  
190 -TEST_F(TApp, IncorrectConstructionOptionAsVector) {  
191 - int x;  
192 - auto cat = app.add_option("--cat", x);  
193 - EXPECT_THROW(cat->expected(2), CLI::IncorrectConstruction);  
194 -}  
195 -  
196 -TEST_F(TApp, IncorrectConstructionVectorAsFlag) {  
197 - std::vector<int> x;  
198 - auto cat = app.add_option("--cat", x);  
199 - EXPECT_THROW(cat->expected(0), CLI::IncorrectConstruction);  
200 -}  
201 -  
202 -TEST_F(TApp, IncorrectConstructionVectorTakeLast) {  
203 - std::vector<int> vec;  
204 - auto cat = app.add_option("--vec", vec);  
205 - EXPECT_THROW(cat->multi_option_policy(CLI::MultiOptionPolicy::TakeLast), CLI::IncorrectConstruction);  
206 -}  
207 -  
208 -TEST_F(TApp, IncorrectConstructionTakeLastExpected) {  
209 - std::vector<int> vec;  
210 - auto cat = app.add_option("--vec", vec);  
211 - cat->expected(1);  
212 - ASSERT_NO_THROW(cat->multi_option_policy(CLI::MultiOptionPolicy::TakeLast));  
213 - EXPECT_THROW(cat->expected(2), CLI::IncorrectConstruction);  
214 -}  
215 -  
216 TEST_F(TApp, IncorrectConstructionNeedsCannotFind) { 176 TEST_F(TApp, IncorrectConstructionNeedsCannotFind) {
217 auto cat = app.add_flag("--cat"); 177 auto cat = app.add_flag("--cat");
218 EXPECT_THROW(cat->needs("--nothing"), CLI::IncorrectConstruction); 178 EXPECT_THROW(cat->needs("--nothing"), CLI::IncorrectConstruction);
tests/DeprecatedTest.cpp
@@ -458,7 +458,7 @@ TEST_F(TApp, DefaultedResult) { @@ -458,7 +458,7 @@ TEST_F(TApp, DefaultedResult) {
458 opts->results(nString); 458 opts->results(nString);
459 EXPECT_EQ(nString, "NA"); 459 EXPECT_EQ(nString, "NA");
460 int newIval; 460 int newIval;
461 - EXPECT_THROW(optv->results(newIval), CLI::ConversionError); 461 + // EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
462 optv->default_str("442"); 462 optv->default_str("442");
463 optv->results(newIval); 463 optv->results(newIval);
464 EXPECT_EQ(newIval, 442); 464 EXPECT_EQ(newIval, 442);
tests/HelpersTest.cpp
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 #include <fstream> 8 #include <fstream>
9 #include <string> 9 #include <string>
10 #include <tuple> 10 #include <tuple>
  11 +#include <utility>
11 12
12 class NotStreamable {}; 13 class NotStreamable {};
13 14
@@ -42,13 +43,32 @@ TEST(TypeTools, type_size) { @@ -42,13 +43,32 @@ TEST(TypeTools, type_size) {
42 V = CLI::detail::type_count<void>::value; 43 V = CLI::detail::type_count<void>::value;
43 EXPECT_EQ(V, 0); 44 EXPECT_EQ(V, 0);
44 V = CLI::detail::type_count<std::vector<double>>::value; 45 V = CLI::detail::type_count<std::vector<double>>::value;
45 - EXPECT_EQ(V, -1); 46 + EXPECT_EQ(V, 1);
46 V = CLI::detail::type_count<std::tuple<double, int>>::value; 47 V = CLI::detail::type_count<std::tuple<double, int>>::value;
47 EXPECT_EQ(V, 2); 48 EXPECT_EQ(V, 2);
48 V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value; 49 V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value;
49 EXPECT_EQ(V, 3); 50 EXPECT_EQ(V, 3);
50 V = CLI::detail::type_count<std::array<std::string, 5>>::value; 51 V = CLI::detail::type_count<std::array<std::string, 5>>::value;
51 EXPECT_EQ(V, 5); 52 EXPECT_EQ(V, 5);
  53 + V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value;
  54 + EXPECT_EQ(V, 2);
  55 +}
  56 +
  57 +TEST(TypeTools, expected_count) {
  58 + auto V = CLI::detail::expected_count<int>::value;
  59 + EXPECT_EQ(V, 1);
  60 + V = CLI::detail::expected_count<void>::value;
  61 + EXPECT_EQ(V, 0);
  62 + V = CLI::detail::expected_count<std::vector<double>>::value;
  63 + EXPECT_EQ(V, CLI::detail::expected_max_vector_size);
  64 + V = CLI::detail::expected_count<std::tuple<double, int>>::value;
  65 + EXPECT_EQ(V, 1);
  66 + V = CLI::detail::expected_count<std::tuple<std::string, double, int>>::value;
  67 + EXPECT_EQ(V, 1);
  68 + V = CLI::detail::expected_count<std::array<std::string, 5>>::value;
  69 + EXPECT_EQ(V, 1);
  70 + V = CLI::detail::expected_count<std::vector<std::pair<std::string, double>>>::value;
  71 + EXPECT_EQ(V, CLI::detail::expected_max_vector_size);
52 } 72 }
53 73
54 TEST(Split, SimpleByToken) { 74 TEST(Split, SimpleByToken) {
@@ -277,6 +297,8 @@ TEST(Validators, IPValidate1) { @@ -277,6 +297,8 @@ TEST(Validators, IPValidate1) {
277 EXPECT_FALSE(CLI::ValidIPV4(ip).empty()); 297 EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
278 ip = "aaa"; 298 ip = "aaa";
279 EXPECT_FALSE(CLI::ValidIPV4(ip).empty()); 299 EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
  300 + ip = "1.2.3.abc";
  301 + EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
280 ip = "11.22"; 302 ip = "11.22";
281 EXPECT_FALSE(CLI::ValidIPV4(ip).empty()); 303 EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
282 } 304 }
@@ -813,10 +835,21 @@ TEST(Types, TypeName) { @@ -813,10 +835,21 @@ TEST(Types, TypeName) {
813 vector_name = CLI::detail::type_name<std::vector<double>>(); 835 vector_name = CLI::detail::type_name<std::vector<double>>();
814 EXPECT_EQ("FLOAT", vector_name); 836 EXPECT_EQ("FLOAT", vector_name);
815 837
  838 + static_assert(CLI::detail::classify_object<std::pair<int, std::string>>::value ==
  839 + CLI::detail::objCategory::tuple_value,
  840 + "pair<int,string> does not read like a tuple");
  841 +
  842 + std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>();
  843 + EXPECT_EQ("[INT,TEXT]", pair_name);
  844 +
816 vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>(); 845 vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>();
817 EXPECT_EQ("UINT", vector_name); 846 EXPECT_EQ("UINT", vector_name);
818 - auto vclass = CLI::detail::classify_object<std::tuple<double>>::value;  
819 - EXPECT_EQ(vclass, CLI::detail::objCategory::number_constructible); 847 +
  848 + auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value;
  849 + EXPECT_EQ(vclass, CLI::detail::objCategory::vector_value);
  850 +
  851 + auto tclass = CLI::detail::classify_object<std::tuple<double>>::value;
  852 + EXPECT_EQ(tclass, CLI::detail::objCategory::number_constructible);
820 853
821 std::string tuple_name = CLI::detail::type_name<std::tuple<double>>(); 854 std::string tuple_name = CLI::detail::type_name<std::tuple<double>>();
822 EXPECT_EQ("FLOAT", tuple_name); 855 EXPECT_EQ("FLOAT", tuple_name);
tests/NewParseTest.cpp
@@ -77,6 +77,26 @@ TEST_F(TApp, BuiltinComplex) { @@ -77,6 +77,26 @@ TEST_F(TApp, BuiltinComplex) {
77 EXPECT_DOUBLE_EQ(3, comp.imag()); 77 EXPECT_DOUBLE_EQ(3, comp.imag());
78 } 78 }
79 79
  80 +TEST_F(TApp, BuiltinComplexFloat) {
  81 + std::complex<float> comp{1, 2};
  82 + app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true);
  83 +
  84 + args = {"-c", "4", "3"};
  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_FLOAT_EQ(1, comp.real());
  92 + EXPECT_FLOAT_EQ(2, comp.imag());
  93 +
  94 + run();
  95 +
  96 + EXPECT_FLOAT_EQ(4, comp.real());
  97 + EXPECT_FLOAT_EQ(3, comp.imag());
  98 +}
  99 +
80 TEST_F(TApp, BuiltinComplexWithDelimiter) { 100 TEST_F(TApp, BuiltinComplexWithDelimiter) {
81 cx comp{1, 2}; 101 cx comp{1, 2};
82 app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); 102 app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
@@ -121,13 +141,61 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { @@ -121,13 +141,61 @@ TEST_F(TApp, BuiltinComplexIgnoreI) {
121 EXPECT_DOUBLE_EQ(3, comp.imag()); 141 EXPECT_DOUBLE_EQ(3, comp.imag());
122 } 142 }
123 143
124 -TEST_F(TApp, BuiltinComplexFail) { 144 +TEST_F(TApp, BuiltinComplexSingleArg) {
125 cx comp{1, 2}; 145 cx comp{1, 2};
126 app.add_complex("-c,--complex", comp); 146 app.add_complex("-c,--complex", comp);
127 147
128 args = {"-c", "4"}; 148 args = {"-c", "4"};
  149 + run();
  150 + EXPECT_DOUBLE_EQ(4, comp.real());
  151 + EXPECT_DOUBLE_EQ(0, comp.imag());
129 152
130 - EXPECT_THROW(run(), CLI::ArgumentMismatch); 153 + args = {"-c", "4-2i"};
  154 + run();
  155 + EXPECT_DOUBLE_EQ(4, comp.real());
  156 + EXPECT_DOUBLE_EQ(-2, comp.imag());
  157 + args = {"-c", "4+2i"};
  158 + run();
  159 + EXPECT_DOUBLE_EQ(4, comp.real());
  160 + EXPECT_DOUBLE_EQ(2, comp.imag());
  161 +
  162 + args = {"-c", "-4+2j"};
  163 + run();
  164 + EXPECT_DOUBLE_EQ(-4, comp.real());
  165 + EXPECT_DOUBLE_EQ(2, comp.imag());
  166 +
  167 + args = {"-c", "-4.2-2j"};
  168 + run();
  169 + EXPECT_DOUBLE_EQ(-4.2, comp.real());
  170 + EXPECT_DOUBLE_EQ(-2, comp.imag());
  171 +
  172 + args = {"-c", "-4.2-2.7i"};
  173 + run();
  174 + EXPECT_DOUBLE_EQ(-4.2, comp.real());
  175 + EXPECT_DOUBLE_EQ(-2.7, comp.imag());
  176 +}
  177 +
  178 +TEST_F(TApp, BuiltinComplexSingleImag) {
  179 + cx comp{1, 2};
  180 + app.add_complex("-c,--complex", comp);
  181 +
  182 + args = {"-c", "4j"};
  183 + run();
  184 + EXPECT_DOUBLE_EQ(0, comp.real());
  185 + EXPECT_DOUBLE_EQ(4, comp.imag());
  186 +
  187 + args = {"-c", "-4j"};
  188 + run();
  189 + EXPECT_DOUBLE_EQ(0, comp.real());
  190 + EXPECT_DOUBLE_EQ(-4, comp.imag());
  191 + args = {"-c", "-4"};
  192 + run();
  193 + EXPECT_DOUBLE_EQ(-4, comp.real());
  194 + EXPECT_DOUBLE_EQ(0, comp.imag());
  195 + args = {"-c", "+4"};
  196 + run();
  197 + EXPECT_DOUBLE_EQ(4, comp.real());
  198 + EXPECT_DOUBLE_EQ(0, comp.imag());
131 } 199 }
132 200
133 class spair { 201 class spair {
tests/OptionalTest.cpp
@@ -104,6 +104,22 @@ TEST_F(TApp, BoostOptionalTest) { @@ -104,6 +104,22 @@ TEST_F(TApp, BoostOptionalTest) {
104 EXPECT_EQ(*opt, 3); 104 EXPECT_EQ(*opt, 3);
105 } 105 }
106 106
  107 +TEST_F(TApp, BoostOptionalTestZarg) {
  108 + boost::optional<int> opt;
  109 + app.add_option("-c,--count", opt)->expected(0, 1);
  110 + run();
  111 + EXPECT_FALSE(opt);
  112 +
  113 + args = {"-c", "1"};
  114 + run();
  115 + EXPECT_TRUE(opt);
  116 + EXPECT_EQ(*opt, 1);
  117 + opt = {};
  118 + args = {"--count"};
  119 + run();
  120 + EXPECT_FALSE(opt);
  121 +}
  122 +
107 TEST_F(TApp, BoostOptionalint64Test) { 123 TEST_F(TApp, BoostOptionalint64Test) {
108 boost::optional<int64_t> opt; 124 boost::optional<int64_t> opt;
109 app.add_option("-c,--count", opt); 125 app.add_option("-c,--count", opt);
@@ -175,6 +191,22 @@ TEST_F(TApp, BoostOptionalVector) { @@ -175,6 +191,22 @@ TEST_F(TApp, BoostOptionalVector) {
175 EXPECT_EQ(*opt, expV); 191 EXPECT_EQ(*opt, expV);
176 } 192 }
177 193
  194 +TEST_F(TApp, BoostOptionalVectorEmpty) {
  195 + boost::optional<std::vector<int>> opt;
  196 + app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
  197 + run();
  198 + EXPECT_FALSE(opt);
  199 + args = {"-v"};
  200 + opt = std::vector<int>{4, 3};
  201 + run();
  202 + EXPECT_FALSE(opt);
  203 + args = {"-v", "1", "4", "5"};
  204 + run();
  205 + EXPECT_TRUE(opt);
  206 + std::vector<int> expV{1, 4, 5};
  207 + EXPECT_EQ(*opt, expV);
  208 +}
  209 +
178 #endif 210 #endif
179 211
180 #if !CLI11_OPTIONAL 212 #if !CLI11_OPTIONAL
tests/TransformTest.cpp
@@ -101,6 +101,24 @@ TEST_F(TApp, EnumCheckedTransform) { @@ -101,6 +101,24 @@ TEST_F(TApp, EnumCheckedTransform) {
101 EXPECT_THROW(run(), CLI::ValidationError); 101 EXPECT_THROW(run(), CLI::ValidationError);
102 } 102 }
103 103
  104 +// from jzakrzewski Issue #330
  105 +TEST_F(TApp, EnumCheckedDefualtTransform) {
  106 + enum class existing : int16_t { abort, overwrite, remove };
  107 + app.add_option("--existing", "What to do if file already exists in the destination")
  108 + ->transform(
  109 + CLI::CheckedTransformer(std::unordered_map<std::string, existing>{{"abort", existing::abort},
  110 + {"overwrite", existing ::overwrite},
  111 + {"delete", existing::remove},
  112 + {"remove", existing::remove}}))
  113 + ->default_val("abort");
  114 + args = {"--existing", "overwrite"};
  115 + run();
  116 + EXPECT_EQ(app.get_option("--existing")->as<existing>(), existing::overwrite);
  117 + args.clear();
  118 + run();
  119 + EXPECT_EQ(app.get_option("--existing")->as<existing>(), existing::abort);
  120 +}
  121 +
104 TEST_F(TApp, SimpleTransformFn) { 122 TEST_F(TApp, SimpleTransformFn) {
105 int value; 123 int value;
106 auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case)); 124 auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
tests/app_helper.hpp
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 #include "gtest/gtest.h" 9 #include "gtest/gtest.h"
10 #include <iostream> 10 #include <iostream>
11 11
12 -typedef std::vector<std::string> input_t; 12 +using input_t = std::vector<std::string>;
13 13
14 struct TApp : public ::testing::Test { 14 struct TApp : public ::testing::Test {
15 CLI::App app{"My Test Program"}; 15 CLI::App app{"My Test Program"};
@@ -27,7 +27,7 @@ class TempFile { @@ -27,7 +27,7 @@ class TempFile {
27 std::string _name; 27 std::string _name;
28 28
29 public: 29 public:
30 - explicit TempFile(std::string name) : _name(name) { 30 + explicit TempFile(std::string name) : _name(std::move(name)) {
31 if(!CLI::NonexistentPath(_name).empty()) 31 if(!CLI::NonexistentPath(_name).empty())
32 throw std::runtime_error(_name); 32 throw std::runtime_error(_name);
33 } 33 }