Commit 67c441b527ffa265023eec6c294f44eaf04039ab

Authored by Philip Top
Committed by Henry Schreiner
1 parent 06ab2d0f

add separate condition for index into vectors

remove restrictions on tuple size, and add some additional tests and modified documentation

fix some issues with the negative number check

add some test for indexed validation on tuple

allow specific validators for specific elements in a type with multiple values, or to just apply to the last given argument
README.md
... ... @@ -111,7 +111,7 @@ After I wrote this, I also found the following libraries:
111 111 | [Clara][] | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope. |
112 112 | [Argh!][] | Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free. |
113 113 | [CLI][] | Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active. |
114   -|[argparse][] | C++17 single file argument parser. Design seems similar to CLI11 in some ways. |
  114 +| [argparse][] | C++17 single file argument parser. Design seems similar to CLI11 in some ways. |
115 115  
116 116 See [Awesome C++][] for a less-biased list of parsers. You can also find other single file libraries at [Single file libs][].
117 117  
... ... @@ -194,7 +194,7 @@ While all options internally are the same type, there are several ways to add an
194 194 app.add_option(option_name, help_str="") // 🆕
195 195  
196 196 app.add_option(option_name,
197   - variable_to_bind_to, // bool, int, float, vector, 🆕 enum, or string-like, or anything with a defined conversion from a string or that takes an int🚧, double🚧, or string in a constructor. Also allowed are tuples(up to 5 elements) and tuple like structures such as std::array or std::pair.
  197 + variable_to_bind_to, // bool, int, float, vector, 🆕 enum, or string-like, or anything with a defined conversion from a string or that takes an int🚧, double🚧, or string in a constructor. Also allowed are tuples🚧,std::array🚧 or std::pair🚧.
198 198 help_string="")
199 199  
200 200 app.add_option_function<type>(option_name,
... ... @@ -202,7 +202,7 @@ app.add_option_function&lt;type&gt;(option_name,
202 202 help_string="")
203 203  
204 204 app.add_complex(... // Special case: support for complex numbers
205   -//🚧There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value.
  205 +//🚧There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
206 206 app.add_option<typename T, typename XC>(option_name,
207 207 T &output, // output must be assignable or constructible from a value of type XC
208 208 help_string="")
... ... @@ -261,7 +261,7 @@ otherwise the output would default to a string. The add_option can be used with
261 261  
262 262 Type such as optional<int>, optional<double>, and optional<string> are supported directly, other optional types can be added using the two parameter template. See [CLI11 Internals][] for information on how this could done and how you can add your own converters for additional types.
263 263  
264   -Vector types can also be used int the two parameter template overload
  264 +Vector types can also be used in the two parameter template overload
265 265 ```
266 266 std::vector<double> v1;
267 267 app.add_option<std::vector<double>,int>("--vs",v1);
... ...
include/CLI/App.hpp
... ... @@ -1859,8 +1859,14 @@ class App {
1859 1859 return detail::Classifier::SUBCOMMAND;
1860 1860 if(detail::split_long(current, dummy1, dummy2))
1861 1861 return detail::Classifier::LONG;
1862   - if(detail::split_short(current, dummy1, dummy2))
  1862 + if(detail::split_short(current, dummy1, dummy2)) {
  1863 + if(dummy1[0] >= '0' && dummy1[0] <= '9') {
  1864 + if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) {
  1865 + return detail::Classifier::NONE;
  1866 + }
  1867 + }
1863 1868 return detail::Classifier::SHORT;
  1869 + }
1864 1870 if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
1865 1871 return detail::Classifier::WINDOWS;
1866 1872 if((current == "++") && !name_.empty() && parent_ != nullptr)
... ... @@ -2314,7 +2320,7 @@ class App {
2314 2320 (opt->get_items_expected() < 0 && opt->count() == 0lu)) {
2315 2321 if(validate_positionals_) {
2316 2322 std::string pos = positional;
2317   - pos = opt->_validate(pos);
  2323 + pos = opt->_validate(pos, 0);
2318 2324 if(!pos.empty()) {
2319 2325 continue;
2320 2326 }
... ... @@ -2334,7 +2340,7 @@ class App {
2334 2340 (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) {
2335 2341 if(validate_positionals_) {
2336 2342 std::string pos = positional;
2337   - pos = opt->_validate(pos);
  2343 + pos = opt->_validate(pos, 0);
2338 2344 if(!pos.empty()) {
2339 2345 continue;
2340 2346 }
... ...
include/CLI/Option.hpp
... ... @@ -408,8 +408,17 @@ class Option : public OptionBase&lt;Option&gt; {
408 408 if((validator_name.empty()) && (!validators_.empty())) {
409 409 return &(validators_.front());
410 410 }
411   - throw OptionNotFound(std::string("Validator ") + validator_name + " Not Found");
  411 + throw OptionNotFound(std::string{"Validator "} + validator_name + " Not Found");
412 412 }
  413 +
  414 + /// Get a Validator by index NOTE: this may not be the order of definition
  415 + Validator *get_validator(int index) {
  416 + if(index >= 0 && index < static_cast<int>(validators_.size())) {
  417 + return &(validators_[index]);
  418 + }
  419 + throw OptionNotFound("Validator index is not valid");
  420 + }
  421 +
413 422 /// Sets required options
414 423 Option *needs(Option *opt) {
415 424 auto tup = needs_.insert(opt);
... ... @@ -679,8 +688,17 @@ class Option : public OptionBase&lt;Option&gt; {
679 688  
680 689 // Run the validators (can change the string)
681 690 if(!validators_.empty()) {
  691 + int index = 0;
  692 + // this is not available until multi_option_policy with type_size_>0 is enabled and functional
  693 + // if(type_size_ > 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
  694 + // index = type_size_ - static_cast<int>(results_.size());
  695 + //}
  696 + if(type_size_ < 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { // for vector operations
  697 + index = expected_ - static_cast<int>(results_.size());
  698 + }
682 699 for(std::string &result : results_) {
683   - auto err_msg = _validate(result);
  700 + auto err_msg = _validate(result, index);
  701 + ++index;
684 702 if(!err_msg.empty())
685 703 throw ValidationError(get_name(), err_msg);
686 704 }
... ... @@ -977,16 +995,19 @@ class Option : public OptionBase&lt;Option&gt; {
977 995  
978 996 private:
979 997 // Run a result through the validators
980   - std::string _validate(std::string &result) {
  998 + std::string _validate(std::string &result, int index) {
981 999 std::string err_msg;
982 1000 for(const auto &vali : validators_) {
983   - try {
984   - err_msg = vali(result);
985   - } catch(const ValidationError &err) {
986   - err_msg = err.what();
  1001 + auto v = vali.get_application_index();
  1002 + if(v == -1 || v == index) {
  1003 + try {
  1004 + err_msg = vali(result);
  1005 + } catch(const ValidationError &err) {
  1006 + err_msg = err.what();
  1007 + }
  1008 + if(!err_msg.empty())
  1009 + break;
987 1010 }
988   - if(!err_msg.empty())
989   - break;
990 1011 }
991 1012 return err_msg;
992 1013 }
... ...
include/CLI/TypeTools.hpp
... ... @@ -137,12 +137,14 @@ struct pair_adaptor&lt;
137 137 // check for constructibility from a specific type and copy assignable used in the parse detection
138 138 template <typename T, typename C> class is_direct_constructible {
139 139 template <typename TT, typename CC>
140   - static auto test(int) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
  140 + static auto test(int, std::true_type) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
  141 +
  142 + template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type;
141 143  
142 144 template <typename, typename> static auto test(...) -> std::false_type;
143 145  
144 146 public:
145   - static constexpr bool value = decltype(test<T, C>(0))::value;
  147 + static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value;
146 148 };
147 149 #ifdef __GNUC__
148 150 #pragma GCC diagnostic pop
... ... @@ -363,28 +365,28 @@ template &lt;typename T&gt; struct uncommon_type {
363 365  
364 366 /// Assignable from double or int
365 367 template <typename T>
366   -struct classify_object<
367   - T,
368   - typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
369   - is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
  368 +struct classify_object<T,
  369 + typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
  370 + is_direct_constructible<T, double>::value &&
  371 + is_direct_constructible<T, int>::value>::type> {
370 372 static constexpr objCategory value{number_constructible};
371 373 };
372 374  
373 375 /// Assignable from int
374 376 template <typename T>
375   -struct classify_object<
376   - T,
377   - typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
378   - is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
  377 +struct classify_object<T,
  378 + typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
  379 + !is_direct_constructible<T, double>::value &&
  380 + is_direct_constructible<T, int>::value>::type> {
379 381 static constexpr objCategory value{integer_constructible};
380 382 };
381 383  
382 384 /// Assignable from double
383 385 template <typename T>
384   -struct classify_object<
385   - T,
386   - typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
387   - !is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
  386 +struct classify_object<T,
  387 + typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
  388 + is_direct_constructible<T, double>::value &&
  389 + !is_direct_constructible<T, int>::value>::type> {
388 390 static constexpr objCategory value{double_constructible};
389 391 };
390 392  
... ... @@ -392,9 +394,9 @@ struct classify_object&lt;
392 394 template <typename T>
393 395 struct classify_object<
394 396 T,
395   - typename std::enable_if<(is_tuple_like<T>::value && uncommon_type<T>::value &&
396   - !is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value) ||
397   - type_count<T>::value >= 2>::type> {
  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> {
398 400 static constexpr objCategory value{tuple_value};
399 401 };
400 402  
... ... @@ -454,54 +456,39 @@ constexpr const char *type_name() {
454 456 return type_name<typename T::value_type>();
455 457 }
456 458  
457   -/// Print name for tuple types
  459 +/// Print name for single element tuple types
458 460 template <
459 461 typename T,
460 462 enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 1, detail::enabler> = detail::dummy>
461 463 std::string type_name() {
462 464 return type_name<typename std::tuple_element<0, T>::type>();
463 465 }
464   -/// Print type name for 2 element tuples
465   -template <
466   - typename T,
467   - enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 2, detail::enabler> = detail::dummy>
468   -std::string type_name() {
469   - return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
470   - type_name<typename std::tuple_element<1, T>::type>() + "]";
471   -}
472 466  
473   -/// Print type name for 3 element tuples
474   -template <
475   - typename T,
476   - enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 3, detail::enabler> = detail::dummy>
477   -std::string type_name() {
478   - return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
479   - type_name<typename std::tuple_element<1, T>::type>() + "," +
480   - type_name<typename std::tuple_element<2, T>::type>() + "]";
  467 +/// Empty string if the index > tuple size
  468 +template <typename T, std::size_t I>
  469 +inline typename std::enable_if<I == type_count<T>::value, std::string>::type tuple_name() {
  470 + return std::string{};
481 471 }
482 472  
483   -/// Print type name for 4 element tuples
484   -template <
485   - typename T,
486   - enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 4, detail::enabler> = detail::dummy>
487   -std::string type_name() {
488   - return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
489   - type_name<typename std::tuple_element<1, T>::type>() + "," +
490   - type_name<typename std::tuple_element<2, T>::type>() + "," +
491   - type_name<typename std::tuple_element<3, T>::type>() + "]";
  473 +/// Recursively generate the tuple type name
  474 +template <typename T, std::size_t I>
  475 + inline typename std::enable_if < I<type_count<T>::value, std::string>::type tuple_name() {
  476 + std::string str = std::string(type_name<typename std::tuple_element<I, T>::type>()) + ',' + tuple_name<T, I + 1>();
  477 + if(str.back() == ',')
  478 + str.pop_back();
  479 + return str;
492 480 }
493 481  
494   -/// Print type name for 5 element tuples
  482 +/// Print type name for tuples with 2 or more elements
495 483 template <
496 484 typename T,
497   - enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 5, detail::enabler> = detail::dummy>
  485 + enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value >= 2, detail::enabler> = detail::dummy>
498 486 std::string type_name() {
499   - return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
500   - type_name<typename std::tuple_element<1, T>::type>() + "," +
501   - type_name<typename std::tuple_element<2, T>::type>() + "," +
502   - type_name<typename std::tuple_element<3, T>::type>() + "," +
503   - type_name<typename std::tuple_element<4, T>::type>() + "]";
  487 + auto tname = std::string(1, '[') + tuple_name<T, 0>();
  488 + tname.push_back(']');
  489 + return tname;
504 490 }
  491 +
505 492 // Lexical cast
506 493  
507 494 /// Convert a flag into an integer value typically binary flags
... ... @@ -777,101 +764,34 @@ bool lexical_conversion(const std::vector&lt;std ::string&gt; &amp;strings, T &amp;output) {
777 764 return (!output.empty()) && retval;
778 765 }
779 766  
780   -/// Conversion for single element tuple and single element tuple conversion type
781   -template <class T,
782   - class XC,
783   - enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && is_tuple_like<XC>::value,
784   - detail::enabler> = detail::dummy>
785   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
786   - static_assert(type_count<T>::value == type_count<XC>::value,
787   - "when using converting to tuples different cross conversion are not possible");
788   -
789   - bool retval = lexical_assign<typename std::tuple_element<0, T>::type, typename std::tuple_element<0, XC>::type>(
790   - strings[0], std::get<0>(output));
791   - return retval;
792   -}
793   -
794   -/// Conversion for single element tuple and single defined type
795   -template <class T,
796   - class XC,
797   - enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && !is_tuple_like<XC>::value,
798   - detail::enabler> = detail::dummy>
799   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
800   - static_assert(type_count<T>::value == type_count<XC>::value,
801   - "when using converting to tuples different cross conversion are not possible");
802   -
803   - bool retval = lexical_assign<typename std::tuple_element<0, T>::type, XC>(strings[0], std::get<0>(output));
804   - return retval;
805   -}
806   -
807   -/// conversion for two element tuple
808   -template <class T, class XC, enable_if_t<type_count<T>::value == 2, detail::enabler> = detail::dummy>
809   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
810   - static_assert(type_count<T>::value == type_count<XC>::value,
811   - "when using converting to tuples different cross conversion are not possible");
812   -
813   - bool retval = lexical_cast(strings[0], std::get<0>(output));
814   - if(strings.size() > 1) {
815   - retval &= lexical_cast(strings[1], std::get<1>(output));
816   - }
817   - return retval;
818   -}
819   -
820   -/// conversion for three element tuple
821   -template <class T, class XC, enable_if_t<type_count<T>::value == 3, detail::enabler> = detail::dummy>
822   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
823   - static_assert(type_count<T>::value == type_count<XC>::value,
824   - "when using converting to tuples different cross conversion are not possible");
825   -
826   - bool retval = lexical_cast(strings[0], std::get<0>(output));
827   - if(strings.size() > 1) {
828   - retval &= lexical_cast(strings[1], std::get<1>(output));
829   - }
830   - if(strings.size() > 2) {
831   - retval &= lexical_cast(strings[2], std::get<2>(output));
832   - }
833   - return retval;
  767 +/// function template for converting tuples if the static Index is greater than the tuple size
  768 +template <class T, class XC, std::size_t I>
  769 +inline typename std::enable_if<I >= type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &,
  770 + T &) {
  771 + return true;
834 772 }
835   -
836   -/// conversion for four element tuple
837   -template <class T, class XC, enable_if_t<type_count<T>::value == 4, detail::enabler> = detail::dummy>
838   -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
839   - static_assert(type_count<T>::value == type_count<XC>::value,
840   - "when using converting to tuples different cross conversion are not possible");
841   -
842   - bool retval = lexical_cast(strings[0], std::get<0>(output));
843   - if(strings.size() > 1) {
844   - retval &= lexical_cast(strings[1], std::get<1>(output));
845   - }
846   - if(strings.size() > 2) {
847   - retval &= lexical_cast(strings[2], std::get<2>(output));
848   - }
849   - if(strings.size() > 3) {
850   - retval &= lexical_cast(strings[3], std::get<3>(output));
  773 +/// Tuple conversion operation
  774 +template <class T, class XC, std::size_t I>
  775 + inline typename std::enable_if <
  776 + I<type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &strings, T &output) {
  777 + bool retval = true;
  778 + if(strings.size() > I) {
  779 + retval &= lexical_assign<
  780 + typename std::tuple_element<I, T>::type,
  781 + typename std::conditional<is_tuple_like<XC>::value, typename std::tuple_element<I, XC>::type, XC>::type>(
  782 + strings[I], std::get<I>(output));
851 783 }
  784 + retval &= tuple_conversion<T, XC, I + 1>(strings, output);
852 785 return retval;
853 786 }
854 787  
855   -/// conversion for five element tuple
856   -template <class T, class XC, enable_if_t<type_count<T>::value == 5, detail::enabler> = detail::dummy>
  788 +/// Conversion for tuples
  789 +template <class T, class XC, enable_if_t<is_tuple_like<T>::value, detail::enabler> = detail::dummy>
857 790 bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
858   - static_assert(type_count<T>::value == type_count<XC>::value,
859   - "when using converting to tuples different cross conversion are not possible");
860   -
861   - bool retval = lexical_cast(strings[0], std::get<0>(output));
862   - if(strings.size() > 1) {
863   - retval &= lexical_cast(strings[1], std::get<1>(output));
864   - }
865   - if(strings.size() > 2) {
866   - retval &= lexical_cast(strings[2], std::get<2>(output));
867   - }
868   - if(strings.size() > 3) {
869   - retval &= lexical_cast(strings[3], std::get<3>(output));
870   - }
871   - if(strings.size() > 4) {
872   - retval &= lexical_cast(strings[4], std::get<4>(output));
873   - }
874   - return retval;
  791 + static_assert(
  792 + !is_tuple_like<XC>::value || type_count<T>::value == type_count<XC>::value,
  793 + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to");
  794 + return tuple_conversion<T, XC, 0>(strings, output);
875 795 }
876 796  
877 797 /// Sum a vector of flag representations
... ...
include/CLI/Validators.hpp
... ... @@ -43,6 +43,8 @@ class Validator {
43 43 std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
44 44 /// The name for search purposes of the Validator
45 45 std::string name_;
  46 + /// A validate will only apply to an indexed value (-1 is all elements)
  47 + int application_index_ = -1;
46 48 /// Enable for Validator to allow it to be disabled if need be
47 49 bool active_{true};
48 50 /// specify that a validator should not modify the input
... ... @@ -113,7 +115,13 @@ class Validator {
113 115 non_modifying_ = no_modify;
114 116 return *this;
115 117 }
116   -
  118 + /// Specify the application index of a validator
  119 + Validator &application_index(int app_index) {
  120 + application_index_ = app_index;
  121 + return *this;
  122 + };
  123 + /// Get the current value of the application index
  124 + int get_application_index() const { return application_index_; }
117 125 /// Get a boolean if the validator is active
118 126 bool get_active() const { return active_; }
119 127  
... ... @@ -141,6 +149,7 @@ class Validator {
141 149 };
142 150  
143 151 newval.active_ = (active_ & other.active_);
  152 + newval.application_index_ = application_index_;
144 153 return newval;
145 154 }
146 155  
... ... @@ -164,6 +173,7 @@ class Validator {
164 173 return std::string("(") + s1 + ") OR (" + s2 + ")";
165 174 };
166 175 newval.active_ = (active_ & other.active_);
  176 + newval.application_index_ = application_index_;
167 177 return newval;
168 178 }
169 179  
... ... @@ -186,6 +196,7 @@ class Validator {
186 196 return std::string{};
187 197 };
188 198 newval.active_ = active_;
  199 + newval.application_index_ = application_index_;
189 200 return newval;
190 201 }
191 202  
... ... @@ -201,10 +212,10 @@ class Validator {
201 212 if((f1.empty()) || (f2.empty())) {
202 213 return f1 + f2;
203 214 }
204   - return std::string("(") + f1 + ")" + merger + "(" + f2 + ")";
  215 + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
205 216 };
206 217 }
207   -};
  218 +}; // namespace CLI
208 219  
209 220 /// Class wrapping some of the accessors of Validator
210 221 class CustomValidator : public Validator {
... ...
tests/AppTest.cpp
... ... @@ -800,6 +800,19 @@ TEST_F(TApp, TakeLastOptMulti) {
800 800 EXPECT_EQ(vals, std::vector<int>({2, 3}));
801 801 }
802 802  
  803 +TEST_F(TApp, TakeLastOptMultiCheck) {
  804 + std::vector<int> vals;
  805 + auto opt = app.add_option("--long", vals)->expected(2)->take_last();
  806 +
  807 + opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
  808 + opt->check((!CLI::PositiveNumber).application_index(1));
  809 + args = {"--long", "-1", "2", "-3"};
  810 +
  811 + EXPECT_NO_THROW(run());
  812 +
  813 + EXPECT_EQ(vals, std::vector<int>({2, -3}));
  814 +}
  815 +
803 816 TEST_F(TApp, TakeFirstOptMulti) {
804 817 std::vector<int> vals;
805 818 app.add_option("--long", vals)->expected(2)->take_first();
... ... @@ -1591,6 +1604,63 @@ TEST_F(TApp, NotFileExists) {
1591 1604 EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
1592 1605 }
1593 1606  
  1607 +TEST_F(TApp, pair_check) {
  1608 + std::string myfile{"pair_check_file.txt"};
  1609 + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
  1610 + EXPECT_TRUE(ok);
  1611 +
  1612 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
  1613 + std::pair<std::string, int> findex;
  1614 +
  1615 + auto v0 = CLI::ExistingFile;
  1616 + v0.application_index(0);
  1617 + auto v1 = CLI::PositiveNumber;
  1618 + v1.application_index(1);
  1619 + app.add_option("--file", findex)->check(v0)->check(v1);
  1620 +
  1621 + args = {"--file", myfile, "2"};
  1622 +
  1623 + EXPECT_NO_THROW(run());
  1624 +
  1625 + EXPECT_EQ(findex.first, myfile);
  1626 + EXPECT_EQ(findex.second, 2);
  1627 +
  1628 + args = {"--file", myfile, "-3"};
  1629 +
  1630 + EXPECT_THROW(run(), CLI::ValidationError);
  1631 +
  1632 + args = {"--file", myfile, "2"};
  1633 + std::remove(myfile.c_str());
  1634 + EXPECT_THROW(run(), CLI::ValidationError);
  1635 +}
  1636 +
  1637 +// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
  1638 +/*
  1639 +TEST_F(TApp, pair_check_take_first) {
  1640 + std::string myfile{"pair_check_file2.txt"};
  1641 + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
  1642 + EXPECT_TRUE(ok);
  1643 +
  1644 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
  1645 + std::pair<std::string, int> findex;
  1646 +
  1647 + auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
  1648 + EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound);
  1649 + opt->get_validator(0)->application_index(0);
  1650 + opt->get_validator(1)->application_index(1);
  1651 + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
  1652 + args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
  1653 + // should only check the last one
  1654 + EXPECT_NO_THROW(run());
  1655 +
  1656 + EXPECT_EQ(findex.first, myfile);
  1657 + EXPECT_EQ(findex.second, 2);
  1658 +
  1659 + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
  1660 +
  1661 + EXPECT_THROW(run(), CLI::ValidationError);
  1662 +}
  1663 +*/
1594 1664 TEST_F(TApp, VectorFixedString) {
1595 1665 std::vector<std::string> strvec;
1596 1666 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
... ... @@ -1617,6 +1687,24 @@ TEST_F(TApp, VectorDefaultedFixedString) {
1617 1687 EXPECT_EQ(answer, strvec);
1618 1688 }
1619 1689  
  1690 +TEST_F(TApp, VectorIndexedValidator) {
  1691 + std::vector<int> vvec;
  1692 +
  1693 + CLI::Option *opt = app.add_option("-v", vvec);
  1694 +
  1695 + args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
  1696 + run();
  1697 + EXPECT_EQ(4u, app.count("-v"));
  1698 + EXPECT_EQ(4u, vvec.size());
  1699 + opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
  1700 + opt->check((!CLI::PositiveNumber).application_index(1));
  1701 + EXPECT_NO_THROW(run());
  1702 + EXPECT_EQ(4u, vvec.size());
  1703 + // v[3] would be negative
  1704 + opt->check(CLI::Validator(CLI::PositiveNumber).application_index(3));
  1705 + EXPECT_THROW(run(), CLI::ValidationError);
  1706 +}
  1707 +
1620 1708 TEST_F(TApp, DefaultedResult) {
1621 1709 std::string sval = "NA";
1622 1710 int ival;
... ... @@ -2118,6 +2206,20 @@ TEST_F(TApp, CustomDoubleOption) {
2118 2206 EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
2119 2207 }
2120 2208  
  2209 +// now with tuple support this is possible
  2210 +TEST_F(TApp, CustomDoubleOptionAlt) {
  2211 +
  2212 + std::pair<int, double> custom_opt;
  2213 +
  2214 + app.add_option("posit", custom_opt);
  2215 +
  2216 + args = {"12", "1.5"};
  2217 +
  2218 + run();
  2219 + EXPECT_EQ(custom_opt.first, 12);
  2220 + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
  2221 +}
  2222 +
2121 2223 // #128
2122 2224 TEST_F(TApp, RepeatingMultiArgumentOptions) {
2123 2225 std::vector<std::string> entries;
... ...
tests/HelpersTest.cpp
... ... @@ -836,6 +836,9 @@ TEST(Types, TypeName) {
836 836 tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int, std::string>>();
837 837 EXPECT_EQ("[INT,TEXT,FLOAT,UINT,TEXT]", tuple_name);
838 838  
  839 + tuple_name = CLI::detail::type_name<std::array<int, 10>>();
  840 + EXPECT_EQ("[INT,INT,INT,INT,INT,INT,INT,INT,INT,INT]", tuple_name);
  841 +
839 842 std::string text_name = CLI::detail::type_name<std::string>();
840 843 EXPECT_EQ("TEXT", text_name);
841 844  
... ... @@ -1005,7 +1008,8 @@ TEST(Types, LexicalConversionVectorDouble) {
1005 1008  
1006 1009 static_assert(!CLI::detail::is_tuple_like<std::vector<double>>::value, "vector should not be like a tuple");
1007 1010 static_assert(CLI::detail::is_tuple_like<std::pair<double, double>>::value, "pair of double should be like a tuple");
1008   -static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array should be like a tuple");
  1011 +static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array<double,4> should be like a tuple");
  1012 +static_assert(CLI::detail::is_tuple_like<std::array<int, 10>>::value, "std::array<int,10> should be like a tuple");
1009 1013 static_assert(!CLI::detail::is_tuple_like<std::string>::value, "std::string should not be like a tuple");
1010 1014 static_assert(!CLI::detail::is_tuple_like<double>::value, "double should not be like a tuple");
1011 1015 static_assert(CLI::detail::is_tuple_like<std::tuple<double, int, double>>::value, "tuple should look like a tuple");
... ... @@ -1071,7 +1075,40 @@ TEST(Types, LexicalConversionTuple5) {
1071 1075 EXPECT_FALSE(res);
1072 1076 }
1073 1077  
1074   -TEST(Types, LexicalConversionXomplwz) {
  1078 +TEST(Types, LexicalConversionTuple10) {
  1079 + CLI::results_t input = {"9", "19", "18", "5", "235235", "9", "19", "18", "5", "235235"};
  1080 + std::array<unsigned int, 10> x;
  1081 + bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1082 + EXPECT_TRUE(res);
  1083 + EXPECT_EQ(std::get<1>(x), 19u);
  1084 + EXPECT_EQ(x[0], 9u);
  1085 + EXPECT_EQ(x[2], 18u);
  1086 + EXPECT_EQ(x[3], 5u);
  1087 + EXPECT_EQ(x[4], 235235u);
  1088 + EXPECT_EQ(x[9], 235235u);
  1089 + input[3] = "hippo";
  1090 + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
  1091 + EXPECT_FALSE(res);
  1092 +}
  1093 +
  1094 +TEST(Types, LexicalConversionTuple10XC) {
  1095 + CLI::results_t input = {"9", "19", "18", "5", "235235", "9", "19", "18", "5", "235235"};
  1096 + std::array<double, 10> x;
  1097 + bool res = CLI::detail::lexical_conversion<decltype(x), std::array<unsigned int, 10>>(input, x);
  1098 +
  1099 + EXPECT_TRUE(res);
  1100 + EXPECT_EQ(std::get<1>(x), 19.0);
  1101 + EXPECT_EQ(x[0], 9.0);
  1102 + EXPECT_EQ(x[2], 18.0);
  1103 + EXPECT_EQ(x[3], 5.0);
  1104 + EXPECT_EQ(x[4], 235235.0);
  1105 + EXPECT_EQ(x[9], 235235.0);
  1106 + input[3] = "19.7";
  1107 + res = CLI::detail::lexical_conversion<decltype(x), std::array<unsigned int, 10>>(input, x);
  1108 + EXPECT_FALSE(res);
  1109 +}
  1110 +
  1111 +TEST(Types, LexicalConversionComplex) {
1075 1112 CLI::results_t input = {"5.1", "3.5"};
1076 1113 std::complex<double> x;
1077 1114 bool res = CLI::detail::lexical_conversion<std::complex<double>, std::array<double, 2>>(input, x);
... ...