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 72  
73 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 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 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 104  
102 105 CLI11_PARSE(app, argv, argc);
103 106  
104   -if(*opt)
  107 +if(* opt)
105 108 std::cout << "Flag recieved " << opt->count() << " times." << std::endl;
106 109 ```
107 110  
... ... @@ -109,10 +112,10 @@ if(*opt)
109 112  
110 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 120 An example of usage:
118 121  
... ... @@ -148,36 +151,68 @@ std::complex&lt;float&gt; val;
148 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 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 217 [^1]: For example, enums are not printable to `std::cout`.
183 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 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 561 Option *add_option(std::string option_name,
561   - T &variable, ///< The variable to set
  562 + AssignTo &variable, ///< The variable to set
562 563 std::string option_description = "",
563 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 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 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 578 opt->type_size((std::max)(Tcount, XCcount));
  579 + opt->expected(detail::expected_count<ConvertTo>::value);
578 580 return opt;
579 581 }
580 582  
... ... @@ -584,7 +586,7 @@ class App {
584 586 const std::function<void(const T &)> &func, ///< the callback to execute
585 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 590 T variable;
589 591 bool result = detail::lexical_conversion<T, T>(res, variable);
590 592 if(result) {
... ... @@ -596,6 +598,7 @@ class App {
596 598 Option *opt = add_option(option_name, std::move(fun), option_description, false);
597 599 opt->type_name(detail::type_name<T>());
598 600 opt->type_size(detail::type_count<T>::value);
  601 + opt->expected(detail::expected_count<T>::value);
599 602 return opt;
600 603 }
601 604  
... ... @@ -667,8 +670,9 @@ class App {
667 670 remove_option(opt);
668 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 676 return opt;
673 677 }
674 678  
... ... @@ -694,7 +698,7 @@ class App {
694 698 T &flag_count, ///< A variable holding the count
695 699 std::string flag_description = "") {
696 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 702 try {
699 703 detail::sum_flag_vector(res, flag_count);
700 704 } catch(const std::invalid_argument &) {
... ... @@ -702,7 +706,8 @@ class App {
702 706 }
703 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 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 721 T &flag_result, ///< A variable holding true if passed
717 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 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 730 /// Vector version to capture multiple flags.
... ... @@ -733,7 +733,7 @@ class App {
733 733 Option *add_flag(std::string flag_name,
734 734 std::vector<T> &flag_results, ///< A vector of values with the flag results
735 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 737 bool retval = true;
738 738 for(const auto &elem : res) {
739 739 flag_results.emplace_back();
... ... @@ -741,7 +741,8 @@ class App {
741 741 }
742 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 748 /// Add option for callback that is triggered with a true flag and takes no arguments
... ... @@ -749,19 +750,14 @@ class App {
749 750 std::function<void(void)> function, ///< A function to call, void(void)
750 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 754 bool trigger;
757 755 auto result = CLI::detail::lexical_cast(res[0], trigger);
758 756 if(trigger)
759 757 function();
760 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 763 /// Add option for callback with an integer value
... ... @@ -769,13 +765,14 @@ class App {
769 765 std::function<void(int64_t)> function, ///< A function to call, void(int)
770 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 769 int64_t flag_count = 0;
774 770 detail::sum_flag_vector(res, flag_count);
775 771 function(flag_count);
776 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 778 #ifdef CLI11_CPP14
... ... @@ -996,34 +993,52 @@ class App {
996 993 }
997 994  
998 995 /// Add a complex number
999   - template <typename T>
  996 + template <typename T, typename XC = double>
1000 997 Option *add_complex(std::string option_name,
1001 998 T &variable,
1002 999 std::string option_description = "",
1003 1000 bool defaulted = false,
1004 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 1031 if(worked)
1013   - variable = T(x, y);
  1032 + variable = T{x, y};
1014 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 1038 CLI::Option *opt =
1024 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 1042 return opt;
1028 1043 }
1029 1044  
... ... @@ -1922,14 +1937,22 @@ class App {
1922 1937 protected:
1923 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 1942 void _validate() const {
  1943 + // count the number of positional only args
1928 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 1957 size_t nameless_subs{0};
1935 1958 for(const App_p &app : subcommands_) {
... ... @@ -2199,15 +2222,9 @@ class App {
2199 2222 if(opt->count() != 0) {
2200 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 2229 // Requires
2213 2230 for(const Option *opt_req : opt->needs_)
... ... @@ -2408,7 +2425,7 @@ class App {
2408 2425  
2409 2426 if(op->empty()) {
2410 2427 // Flag parsing
2411   - if(op->get_type_size() == 0) {
  2428 + if(op->get_expected_min() == 0) {
2412 2429 auto res = config_formatter_->to_flag(item);
2413 2430 res = op->get_flag_value(item.name, res);
2414 2431  
... ... @@ -2459,7 +2476,6 @@ class App {
2459 2476 positional_only = true;
2460 2477 }
2461 2478 break;
2462   -
2463 2479 // LCOV_EXCL_START
2464 2480 default:
2465 2481 throw HorribleError("unrecognized classifier (you should not see this!)");
... ... @@ -2473,10 +2489,9 @@ class App {
2473 2489 size_t retval = 0;
2474 2490 for(const Option_p &opt : options_) {
2475 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 2501 /// Count the required remaining positional arguments
2487 2502 bool _has_remaining_positionals() const {
2488 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 2505 return true;
  2506 + }
2492 2507  
2493 2508 return false;
2494 2509 }
... ... @@ -2507,8 +2522,7 @@ class App {
2507 2522 if(arg_rem <= remreq) {
2508 2523 for(const Option_p &opt : options_) {
2509 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 2526 if(validate_positionals_) {
2513 2527 std::string pos = positional;
2514 2528 pos = opt->_validate(pos, 0);
... ... @@ -2528,7 +2542,7 @@ class App {
2528 2542 for(const Option_p &opt : options_) {
2529 2543 // Eat options, one by one, until done
2530 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 2546 if(validate_positionals_) {
2533 2547 std::string pos = positional;
2534 2548 pos = opt->_validate(pos, 0);
... ... @@ -2714,13 +2728,14 @@ class App {
2714 2728 // Get a reference to the pointer to make syntax bearable
2715 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 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 2739 auto res = op->get_flag_value(arg_name, value);
2725 2740 op->add_result(res);
2726 2741 parse_order_.push_back(op.get());
... ... @@ -2730,31 +2745,37 @@ class App {
2730 2745 op->add_result(value, result_count);
2731 2746 parse_order_.push_back(op.get());
2732 2747 collected += result_count;
2733   - // If exact number expected
2734   - if(num > 0)
2735   - num = (num >= result_count) ? num - result_count : 0;
2736   -
2737 2748 // -Trest
2738 2749 } else if(!rest.empty()) {
2739 2750 op->add_result(rest, result_count);
2740 2751 parse_order_.push_back(op.get());
2741 2752 rest = "";
2742 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 2779 op->add_result(args.back(), result_count);
2759 2780 parse_order_.push_back(op.get());
2760 2781 args.pop_back();
... ... @@ -2764,19 +2785,17 @@ class App {
2764 2785 // Allow -- to end an unlimited list and "eat" it
2765 2786 if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK)
2766 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 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 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 25 std::string value;
26 26  
27 27 // Non-flags
28   - if(opt->get_type_size() != 0) {
  28 + if(opt->get_expected_min() != 0) {
29 29  
30 30 // If the option was found on command line
31 31 if(opt->count() > 0)
... ... @@ -56,7 +56,7 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description,
56 56 }
57 57  
58 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 60 out << name << "=" << value << std::endl;
61 61 else
62 62 out << name << "=" << detail::add_quotes_if_needed(value) << std::endl;
... ...
include/CLI/Error.hpp
... ... @@ -246,8 +246,13 @@ class ArgumentMismatch : public ParseError {
246 246 ", got " + std::to_string(recieved)),
247 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 257 static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
253 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 234 out << " " << get_label(opt->get_type_name());
235 235 if(!opt->get_default_str().empty())
236 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 238 out << " ...";
  239 + else if(opt->get_expected_min() > 1)
  240 + out << " x " << opt->get_expected();
  241 +
241 242 if(opt->get_required())
242 243 out << " " << get_label("REQUIRED");
243 244 }
... ... @@ -262,11 +263,11 @@ inline std::string Formatter::make_option_usage(const Option *opt) const {
262 263 // Note that these are positionals usages
263 264 std::stringstream out;
264 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 267 out << "...";
  268 + else if(opt->get_expected_max() > 1)
  269 + out << "(" << opt->get_expected() << "x)";
  270 +
270 271 return opt->get_required() ? out.str() : "[" + out.str() + "]";
271 272 }
272 273  
... ...
include/CLI/Option.hpp
... ... @@ -21,14 +21,21 @@
21 21 namespace CLI {
22 22  
23 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 27 class Option;
27 28 class App;
28 29  
29 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 40 /// This is the CRTP base class for Option and OptionDefaults. It was designed this way
34 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 67 /// Automatically capture default value
61 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 71 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
65 72  
66 73 /// Copy the contents to another similar class (one based on OptionBase)
... ... @@ -119,7 +126,7 @@ template &lt;typename CRTP&gt; class OptionBase {
119 126 /// The status of configurable
120 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 130 char get_delimiter() const { return delimiter_; }
124 131  
125 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 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 162 CRTP *join() {
149 163 auto self = static_cast<CRTP *>(this);
150 164 self->multi_option_policy(MultiOptionPolicy::Join);
151 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 176 /// Allow in a configuration file
155 177 CRTP *configurable(bool value = true) {
156 178 configurable_ = value;
... ... @@ -213,7 +235,7 @@ class Option : public OptionBase&lt;Option&gt; {
213 235 /// A list of the short names (`-a`) without the leading dashes
214 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 239 std::vector<std::string> lnames_;
218 240  
219 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 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 288 std::vector<Validator> validators_;
264 289  
265 290 /// A list of options that are required with this option
... ... @@ -282,19 +307,27 @@ class Option : public OptionBase&lt;Option&gt; {
282 307 /// @name Parsing results
283 308 ///@{
284 309  
285   - /// Results of parsing
  310 + /// complete Results of parsing
286 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 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 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 331 : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
299 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 346 operator bool() const { return !empty(); }
314 347  
315 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 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 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 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 408 /// Adds a Validator with a built in type name
350 409 Option *check(Validator validator, std::string validator_name = "") {
... ... @@ -356,23 +415,23 @@ class Option : public OptionBase&lt;Option&gt; {
356 415 }
357 416  
358 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 422 validators_.back().non_modifying();
364 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 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 435 Option *transform(std::function<std::string(std::string)> func,
377 436 std::string transform_description = "",
378 437 std::string transform_name = "") {
... ... @@ -399,16 +458,16 @@ class Option : public OptionBase&lt;Option&gt; {
399 458 return this;
400 459 }
401 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 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 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 611  
553 612 /// Take the last argument if given multiple times (or another policy)
554 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 623 return this;
560 624 }
561 625  
... ... @@ -569,7 +633,12 @@ class Option : public OptionBase&lt;Option&gt; {
569 633 ///@{
570 634  
571 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 643 /// The environment variable associated to this value
575 644 std::string get_envname() const { return envname_; }
... ... @@ -600,28 +669,23 @@ class Option : public OptionBase&lt;Option&gt; {
600 669 const std::vector<std::string> get_fnames() const { return fnames_; }
601 670  
602 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 690 /// True if the argument can be given directly
627 691 bool get_positional() const { return pname_.length() > 0; }
... ... @@ -711,66 +775,26 @@ class Option : public OptionBase&lt;Option&gt; {
711 775 /// Process the callback
712 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 800 /// If options share any of the same names, find it
... ... @@ -833,7 +857,8 @@ class Option : public OptionBase&lt;Option&gt; {
833 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 862 std::string get_flag_value(std::string name, std::string input_value) const {
838 863 static const std::string trueString{"true"};
839 864 static const std::string falseString{"false"};
... ... @@ -856,7 +881,11 @@ class Option : public OptionBase&lt;Option&gt; {
856 881 }
857 882 auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
858 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 890 if(ind < 0) {
862 891 return input_value;
... ... @@ -875,73 +904,78 @@ class Option : public OptionBase&lt;Option&gt; {
875 904  
876 905 /// Puts a result at the end
877 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 909 return this;
881 910 }
882 911  
883 912 /// Puts a result at the end and get a count of the number of arguments actually added
884 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 916 return this;
888 917 }
889 918  
890 919 /// Puts a result at the end
891 920 Option *add_result(std::vector<std::string> s) {
892 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 925 return this;
897 926 }
898 927  
899 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 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 950 void results(T &output) const {
906 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 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 975 if(!retval) {
928 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 980 /// Return the results as the specified type
947 981 template <typename T> T as() const {
... ... @@ -951,7 +985,7 @@ class Option : public OptionBase&lt;Option&gt; {
951 985 }
952 986  
953 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 991 /// @name Custom options
... ... @@ -971,11 +1005,40 @@ class Option : public OptionBase&lt;Option&gt; {
971 1005  
972 1006 /// Set a custom option size
973 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 1040 required_ = false;
977   - if(option_type_size < 0)
978   - expected_ = -1;
  1041 + }
979 1042 return this;
980 1043 }
981 1044  
... ... @@ -1003,7 +1066,8 @@ class Option : public OptionBase&lt;Option&gt; {
1003 1066 Option *default_val(std::string val) {
1004 1067 default_str(val);
1005 1068 auto old_results = results_;
1006   - results_ = {val};
  1069 + results_.clear();
  1070 + add_result(val);
1007 1071 run_callback();
1008 1072 results_ = std::move(old_results);
1009 1073 return this;
... ... @@ -1013,8 +1077,8 @@ class Option : public OptionBase&lt;Option&gt; {
1013 1077 std::string get_type_name() const {
1014 1078 std::string full_type_name = type_name_();
1015 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 1082 if(!vtype.empty()) {
1019 1083 full_type_name += ":" + vtype;
1020 1084 }
... ... @@ -1024,9 +1088,106 @@ class Option : public OptionBase&lt;Option&gt; {
1024 1088 }
1025 1089  
1026 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 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 1191 for(const auto &vali : validators_) {
1031 1192 auto v = vali.get_application_index();
1032 1193 if(v == -1 || v == index) {
... ... @@ -1039,25 +1200,37 @@ class Option : public OptionBase&lt;Option&gt; {
1039 1200 break;
1040 1201 }
1041 1202 }
  1203 +
1042 1204 return err_msg;
1043 1205 }
1044 1206  
1045 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 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 1221 if(delimiter_ == '\0') {
1049   - results_.push_back(std::move(result));
  1222 + res.push_back(std::move(result));
1050 1223 ++result_count;
1051 1224 } else {
1052 1225 if((result.find_first_of(delimiter_) != std::string::npos)) {
1053 1226 for(const auto &var : CLI::detail::split(result, delimiter_)) {
1054 1227 if(!var.empty()) {
1055   - results_.push_back(var);
  1228 + res.push_back(var);
1056 1229 ++result_count;
1057 1230 }
1058 1231 }
1059 1232 } else {
1060   - results_.push_back(std::move(result));
  1233 + res.push_back(std::move(result));
1061 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 31 using namespace enums;
32 32  
33 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 37 // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
36 38 /// Split a string by a delim
37 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 274 typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> {
275 275 static constexpr int value{1};
276 276 };
  277 +
277 278 /// Type size of types that look like a vector
278 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 297 // Enumeration of the different supported categorizations of objects
... ... @@ -392,11 +407,11 @@ struct classify_object&lt;T,
392 407  
393 408 /// Tuple type
394 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 415 static constexpr objCategory value{tuple_value};
401 416 };
402 417  
... ... @@ -450,17 +465,11 @@ constexpr const char *type_name() {
450 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 468 /// Print name for single element tuple types
460 469 template <
461 470 typename T,
462 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 473 return type_name<typename std::tuple_element<0, T>::type>();
465 474 }
466 475  
... ... @@ -489,6 +498,12 @@ std::string type_name() {
489 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 507 // Lexical cast
493 508  
494 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 688 }
674 689  
675 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 696 bool lexical_assign(const std::string &input, T &output) {
678 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 714 /// Assign a value converted from a string in lexical cast to the output value directly
682 715 template <
683 716 typename T,
684 717 typename XC,
685 718 enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
686 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 722 if(parse_result) {
690 723 output = val;
691 724 }
... ... @@ -699,18 +732,19 @@ template &lt;typename T,
699 732 std::is_move_assignable<T>::value,
700 733 detail::enabler> = detail::dummy>
701 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 737 if(parse_result) {
705 738 output = T(val); // use () form of constructor to allow some implicit conversions
706 739 }
707 740 return parse_result;
708 741 }
709 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 748 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
715 749 return lexical_assign<T, XC>(strings[0], output);
716 750 }
... ... @@ -722,9 +756,9 @@ template &lt;typename T,
722 756 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
723 757 typename std::tuple_element<0, XC>::type v1;
724 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 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 763 if(retval) {
730 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 769 /// Lexical conversion of a vector types
736 770 template <class T,
737 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 775 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
740 776 bool retval = true;
741 777 output.clear();
... ... @@ -748,10 +784,38 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
748 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 813 /// Conversion to a vector type using a particular single type as the conversion type
752 814 template <class T,
753 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 819 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
756 820 bool retval = true;
757 821 output.clear();
... ... @@ -763,6 +827,23 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
763 827 }
764 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 848 /// function template for converting tuples if the static Index is greater than the tuple size
768 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 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 910 /// Sum a vector of flag representations
798 911 /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
799 912 /// by
... ...
include/CLI/Validators.hpp
... ... @@ -325,14 +325,14 @@ class IPV4Validator : public Validator {
325 325 func_ = [](std::string &ip_addr) {
326 326 auto result = CLI::detail::split(ip_addr, '.');
327 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 330 int num;
331 331 bool retval = true;
332 332 for(const auto &var : result) {
333 333 retval &= detail::lexical_cast(var, num);
334 334 if(!retval) {
335   - return "Failed parsing number " + var;
  335 + return "Failed parsing number (" + var + ')';
336 336 }
337 337 if(num < 0 || num > 255) {
338 338 return "Each IP number must be between 0 and 255 " + var;
... ... @@ -350,10 +350,10 @@ class PositiveNumber : public Validator {
350 350 func_ = [](std::string &number_str) {
351 351 int number;
352 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 355 if(number < 0) {
356   - return "Number less then 0 " + number_str;
  356 + return "Number less then 0: (" + number_str + ')';
357 357 }
358 358 return std::string();
359 359 };
... ... @@ -367,7 +367,7 @@ class Number : public Validator {
367 367 func_ = [](std::string &number_str) {
368 368 double number;
369 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 372 return std::string();
373 373 };
... ...
tests/AppTest.cpp
... ... @@ -40,12 +40,14 @@ TEST_F(TApp, OneFlagShortValuesAs) {
40 40 flg->take_last();
41 41 EXPECT_EQ(opt->as<int>(), 2);
42 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 45 auto vec = opt->as<std::vector<int>>();
46 46 EXPECT_EQ(vec[0], 1);
47 47 EXPECT_EQ(vec[1], 2);
48 48 flg->multi_option_policy(CLI::MultiOptionPolicy::Join);
  49 + EXPECT_EQ(opt->as<std::string>(), "1\n2");
  50 + flg->delimiter(',');
49 51 EXPECT_EQ(opt->as<std::string>(), "1,2");
50 52 }
51 53  
... ... @@ -403,6 +405,27 @@ TEST_F(TApp, OneStringEqualVersionSingleStringQuotedMultipleWithEqualAndProgram)
403 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 429 TEST_F(TApp, TogetherInt) {
407 430 int i;
408 431 app.add_option("-i,--int", i);
... ... @@ -620,6 +643,42 @@ TEST_F(TApp, BoolAndIntFlags) {
620 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 682 TEST_F(TApp, BoolOnlyFlag) {
624 683 bool bflag;
625 684 app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
... ... @@ -629,7 +688,7 @@ TEST_F(TApp, BoolOnlyFlag) {
629 688 EXPECT_TRUE(bflag);
630 689  
631 690 args = {"-b", "-b"};
632   - EXPECT_THROW(run(), CLI::ConversionError);
  691 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
633 692 }
634 693  
635 694 TEST_F(TApp, BoolOption) {
... ... @@ -800,9 +859,51 @@ TEST_F(TApp, TakeLastOptMulti) {
800 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 904 TEST_F(TApp, TakeLastOptMultiCheck) {
804 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 908 opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
808 909 opt->check((!CLI::PositiveNumber).application_index(1));
... ... @@ -826,7 +927,7 @@ TEST_F(TApp, TakeFirstOptMulti) {
826 927  
827 928 TEST_F(TApp, ComplexOptMulti) {
828 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 932 args = {"--long", "1", "2", "3", "4"};
832 933  
... ... @@ -1141,7 +1242,7 @@ TEST_F(TApp, RequiredOptsUnlimited) {
1141 1242  
1142 1243 app.allow_extras(false);
1143 1244 std::vector<std::string> remain;
1144   - app.add_option("positional", remain);
  1245 + auto popt = app.add_option("positional", remain);
1145 1246 run();
1146 1247 EXPECT_EQ(strs, std::vector<std::string>({"one", "two"}));
1147 1248 EXPECT_EQ(remain, std::vector<std::string>());
... ... @@ -1157,6 +1258,12 @@ TEST_F(TApp, RequiredOptsUnlimited) {
1157 1258 run();
1158 1259 EXPECT_EQ(strs, std::vector<std::string>({"two"}));
1159 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 1269 TEST_F(TApp, RequiredOptsUnlimitedShort) {
... ... @@ -1217,9 +1324,9 @@ TEST_F(TApp, OptsUnlimitedEnd) {
1217 1324 TEST_F(TApp, RequireOptPriority) {
1218 1325  
1219 1326 std::vector<std::string> strs;
1220   - app.add_option("--str", strs)->required();
  1327 + app.add_option("--str", strs);
1221 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 1331 args = {"--str", "one", "two", "three"};
1225 1332 run();
... ... @@ -1239,7 +1346,7 @@ TEST_F(TApp, RequireOptPriorityShort) {
1239 1346 std::vector<std::string> strs;
1240 1347 app.add_option("-s", strs)->required();
1241 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 1351 args = {"-s", "one", "two", "three"};
1245 1352 run();
... ... @@ -1330,7 +1437,7 @@ TEST_F(TApp, CallbackBoolFlags) {
1330 1437 EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
1331 1438 cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
1332 1439 args = {"--val", "--val=false"};
1333   - EXPECT_THROW(run(), CLI::ConversionError);
  1440 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
1334 1441 }
1335 1442  
1336 1443 TEST_F(TApp, CallbackFlagsFalse) {
... ... @@ -1638,7 +1745,7 @@ TEST_F(TApp, pair_check) {
1638 1745 }
1639 1746  
1640 1747 // this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
1641   -/*
  1748 +
1642 1749 TEST_F(TApp, pair_check_take_first) {
1643 1750 std::string myfile{"pair_check_file2.txt"};
1644 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 1770  
1664 1771 EXPECT_THROW(run(), CLI::ValidationError);
1665 1772 }
1666   -*/
  1773 +
1667 1774 TEST_F(TApp, VectorFixedString) {
1668 1775 std::vector<std::string> strvec;
1669 1776 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
... ... @@ -1720,7 +1827,7 @@ TEST_F(TApp, DefaultedResult) {
1720 1827 opts->results(nString);
1721 1828 EXPECT_EQ(nString, "NA");
1722 1829 int newIval;
1723   - EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
  1830 + // EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
1724 1831 optv->default_str("442");
1725 1832 optv->results(newIval);
1726 1833 EXPECT_EQ(newIval, 442);
... ... @@ -1731,7 +1838,8 @@ TEST_F(TApp, VectorUnlimString) {
1731 1838 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
1732 1839  
1733 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 1844 args = {"--string", "mystring", "mystring2", "mystring3"};
1737 1845 run();
... ... @@ -1744,6 +1852,35 @@ TEST_F(TApp, VectorUnlimString) {
1744 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 1884 TEST_F(TApp, VectorFancyOpts) {
1748 1885 std::vector<std::string> strvec;
1749 1886 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
... ... @@ -2227,6 +2364,138 @@ TEST_F(TApp, CustomDoubleOptionAlt) {
2227 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 2499 // #128
2231 2500 TEST_F(TApp, RepeatingMultiArgumentOptions) {
2232 2501 std::vector<std::string> entries;
... ...
tests/CreationTest.cpp
... ... @@ -173,46 +173,6 @@ TEST_F(TApp, IncorrectConstructionFlagPositional3) {
173 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 176 TEST_F(TApp, IncorrectConstructionNeedsCannotFind) {
217 177 auto cat = app.add_flag("--cat");
218 178 EXPECT_THROW(cat->needs("--nothing"), CLI::IncorrectConstruction);
... ...
tests/DeprecatedTest.cpp
... ... @@ -458,7 +458,7 @@ TEST_F(TApp, DefaultedResult) {
458 458 opts->results(nString);
459 459 EXPECT_EQ(nString, "NA");
460 460 int newIval;
461   - EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
  461 + // EXPECT_THROW(optv->results(newIval), CLI::ConversionError);
462 462 optv->default_str("442");
463 463 optv->results(newIval);
464 464 EXPECT_EQ(newIval, 442);
... ...
tests/HelpersTest.cpp
... ... @@ -8,6 +8,7 @@
8 8 #include <fstream>
9 9 #include <string>
10 10 #include <tuple>
  11 +#include <utility>
11 12  
12 13 class NotStreamable {};
13 14  
... ... @@ -42,13 +43,32 @@ TEST(TypeTools, type_size) {
42 43 V = CLI::detail::type_count<void>::value;
43 44 EXPECT_EQ(V, 0);
44 45 V = CLI::detail::type_count<std::vector<double>>::value;
45   - EXPECT_EQ(V, -1);
  46 + EXPECT_EQ(V, 1);
46 47 V = CLI::detail::type_count<std::tuple<double, int>>::value;
47 48 EXPECT_EQ(V, 2);
48 49 V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value;
49 50 EXPECT_EQ(V, 3);
50 51 V = CLI::detail::type_count<std::array<std::string, 5>>::value;
51 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 74 TEST(Split, SimpleByToken) {
... ... @@ -277,6 +297,8 @@ TEST(Validators, IPValidate1) {
277 297 EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
278 298 ip = "aaa";
279 299 EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
  300 + ip = "1.2.3.abc";
  301 + EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
280 302 ip = "11.22";
281 303 EXPECT_FALSE(CLI::ValidIPV4(ip).empty());
282 304 }
... ... @@ -813,10 +835,21 @@ TEST(Types, TypeName) {
813 835 vector_name = CLI::detail::type_name<std::vector<double>>();
814 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 845 vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>();
817 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 854 std::string tuple_name = CLI::detail::type_name<std::tuple<double>>();
822 855 EXPECT_EQ("FLOAT", tuple_name);
... ...
tests/NewParseTest.cpp
... ... @@ -77,6 +77,26 @@ TEST_F(TApp, BuiltinComplex) {
77 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 100 TEST_F(TApp, BuiltinComplexWithDelimiter) {
81 101 cx comp{1, 2};
82 102 app.add_complex("-c,--complex", comp, "", true)->delimiter('+');
... ... @@ -121,13 +141,61 @@ TEST_F(TApp, BuiltinComplexIgnoreI) {
121 141 EXPECT_DOUBLE_EQ(3, comp.imag());
122 142 }
123 143  
124   -TEST_F(TApp, BuiltinComplexFail) {
  144 +TEST_F(TApp, BuiltinComplexSingleArg) {
125 145 cx comp{1, 2};
126 146 app.add_complex("-c,--complex", comp);
127 147  
128 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 201 class spair {
... ...
tests/OptionalTest.cpp
... ... @@ -104,6 +104,22 @@ TEST_F(TApp, BoostOptionalTest) {
104 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 123 TEST_F(TApp, BoostOptionalint64Test) {
108 124 boost::optional<int64_t> opt;
109 125 app.add_option("-c,--count", opt);
... ... @@ -175,6 +191,22 @@ TEST_F(TApp, BoostOptionalVector) {
175 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 210 #endif
179 211  
180 212 #if !CLI11_OPTIONAL
... ...
tests/TransformTest.cpp
... ... @@ -101,6 +101,24 @@ TEST_F(TApp, EnumCheckedTransform) {
101 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 122 TEST_F(TApp, SimpleTransformFn) {
105 123 int value;
106 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 9 #include "gtest/gtest.h"
10 10 #include <iostream>
11 11  
12   -typedef std::vector<std::string> input_t;
  12 +using input_t = std::vector<std::string>;
13 13  
14 14 struct TApp : public ::testing::Test {
15 15 CLI::App app{"My Test Program"};
... ... @@ -27,7 +27,7 @@ class TempFile {
27 27 std::string _name;
28 28  
29 29 public:
30   - explicit TempFile(std::string name) : _name(name) {
  30 + explicit TempFile(std::string name) : _name(std::move(name)) {
31 31 if(!CLI::NonexistentPath(_name).empty())
32 32 throw std::runtime_error(_name);
33 33 }
... ...