mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +00:00
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
This commit is contained in:
parent
06ab2d0fbd
commit
67c441b527
@ -111,7 +111,7 @@ After I wrote this, I also found the following libraries:
|
||||
| [Clara][] | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope. |
|
||||
| [Argh!][] | Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free. |
|
||||
| [CLI][] | Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active. |
|
||||
|[argparse][] | C++17 single file argument parser. Design seems similar to CLI11 in some ways. |
|
||||
| [argparse][] | C++17 single file argument parser. Design seems similar to CLI11 in some ways. |
|
||||
|
||||
See [Awesome C++][] for a less-biased list of parsers. You can also find other single file libraries at [Single file libs][].
|
||||
|
||||
@ -194,7 +194,7 @@ While all options internally are the same type, there are several ways to add an
|
||||
app.add_option(option_name, help_str="") // 🆕
|
||||
|
||||
app.add_option(option_name,
|
||||
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.
|
||||
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🚧.
|
||||
help_string="")
|
||||
|
||||
app.add_option_function<type>(option_name,
|
||||
@ -202,7 +202,7 @@ app.add_option_function<type>(option_name,
|
||||
help_string="")
|
||||
|
||||
app.add_complex(... // Special case: support for complex numbers
|
||||
//🚧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.
|
||||
//🚧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
|
||||
app.add_option<typename T, typename XC>(option_name,
|
||||
T &output, // output must be assignable or constructible from a value of type XC
|
||||
help_string="")
|
||||
@ -261,7 +261,7 @@ otherwise the output would default to a string. The add_option can be used with
|
||||
|
||||
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.
|
||||
|
||||
Vector types can also be used int the two parameter template overload
|
||||
Vector types can also be used in the two parameter template overload
|
||||
```
|
||||
std::vector<double> v1;
|
||||
app.add_option<std::vector<double>,int>("--vs",v1);
|
||||
|
@ -1859,8 +1859,14 @@ class App {
|
||||
return detail::Classifier::SUBCOMMAND;
|
||||
if(detail::split_long(current, dummy1, dummy2))
|
||||
return detail::Classifier::LONG;
|
||||
if(detail::split_short(current, dummy1, dummy2))
|
||||
if(detail::split_short(current, dummy1, dummy2)) {
|
||||
if(dummy1[0] >= '0' && dummy1[0] <= '9') {
|
||||
if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) {
|
||||
return detail::Classifier::NONE;
|
||||
}
|
||||
}
|
||||
return detail::Classifier::SHORT;
|
||||
}
|
||||
if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
|
||||
return detail::Classifier::WINDOWS;
|
||||
if((current == "++") && !name_.empty() && parent_ != nullptr)
|
||||
@ -2314,7 +2320,7 @@ class App {
|
||||
(opt->get_items_expected() < 0 && opt->count() == 0lu)) {
|
||||
if(validate_positionals_) {
|
||||
std::string pos = positional;
|
||||
pos = opt->_validate(pos);
|
||||
pos = opt->_validate(pos, 0);
|
||||
if(!pos.empty()) {
|
||||
continue;
|
||||
}
|
||||
@ -2334,7 +2340,7 @@ class App {
|
||||
(static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) {
|
||||
if(validate_positionals_) {
|
||||
std::string pos = positional;
|
||||
pos = opt->_validate(pos);
|
||||
pos = opt->_validate(pos, 0);
|
||||
if(!pos.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -408,8 +408,17 @@ class Option : public OptionBase<Option> {
|
||||
if((validator_name.empty()) && (!validators_.empty())) {
|
||||
return &(validators_.front());
|
||||
}
|
||||
throw OptionNotFound(std::string("Validator ") + validator_name + " Not Found");
|
||||
throw OptionNotFound(std::string{"Validator "} + validator_name + " Not Found");
|
||||
}
|
||||
|
||||
/// Get a Validator by index NOTE: this may not be the order of definition
|
||||
Validator *get_validator(int index) {
|
||||
if(index >= 0 && index < static_cast<int>(validators_.size())) {
|
||||
return &(validators_[index]);
|
||||
}
|
||||
throw OptionNotFound("Validator index is not valid");
|
||||
}
|
||||
|
||||
/// Sets required options
|
||||
Option *needs(Option *opt) {
|
||||
auto tup = needs_.insert(opt);
|
||||
@ -679,8 +688,17 @@ class Option : public OptionBase<Option> {
|
||||
|
||||
// Run the validators (can change the string)
|
||||
if(!validators_.empty()) {
|
||||
int index = 0;
|
||||
// this is not available until multi_option_policy with type_size_>0 is enabled and functional
|
||||
// if(type_size_ > 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
|
||||
// index = type_size_ - static_cast<int>(results_.size());
|
||||
//}
|
||||
if(type_size_ < 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { // for vector operations
|
||||
index = expected_ - static_cast<int>(results_.size());
|
||||
}
|
||||
for(std::string &result : results_) {
|
||||
auto err_msg = _validate(result);
|
||||
auto err_msg = _validate(result, index);
|
||||
++index;
|
||||
if(!err_msg.empty())
|
||||
throw ValidationError(get_name(), err_msg);
|
||||
}
|
||||
@ -977,16 +995,19 @@ class Option : public OptionBase<Option> {
|
||||
|
||||
private:
|
||||
// Run a result through the validators
|
||||
std::string _validate(std::string &result) {
|
||||
std::string _validate(std::string &result, int index) {
|
||||
std::string err_msg;
|
||||
for(const auto &vali : validators_) {
|
||||
try {
|
||||
err_msg = vali(result);
|
||||
} catch(const ValidationError &err) {
|
||||
err_msg = err.what();
|
||||
auto v = vali.get_application_index();
|
||||
if(v == -1 || v == index) {
|
||||
try {
|
||||
err_msg = vali(result);
|
||||
} catch(const ValidationError &err) {
|
||||
err_msg = err.what();
|
||||
}
|
||||
if(!err_msg.empty())
|
||||
break;
|
||||
}
|
||||
if(!err_msg.empty())
|
||||
break;
|
||||
}
|
||||
return err_msg;
|
||||
}
|
||||
|
@ -137,12 +137,14 @@ struct pair_adaptor<
|
||||
// check for constructibility from a specific type and copy assignable used in the parse detection
|
||||
template <typename T, typename C> class is_direct_constructible {
|
||||
template <typename TT, typename CC>
|
||||
static auto test(int) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
|
||||
static auto test(int, std::true_type) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
|
||||
|
||||
template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type;
|
||||
|
||||
template <typename, typename> static auto test(...) -> std::false_type;
|
||||
|
||||
public:
|
||||
static constexpr bool value = decltype(test<T, C>(0))::value;
|
||||
static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value;
|
||||
};
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
@ -363,28 +365,28 @@ template <typename T> struct uncommon_type {
|
||||
|
||||
/// Assignable from double or int
|
||||
template <typename T>
|
||||
struct classify_object<
|
||||
T,
|
||||
typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
|
||||
is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
|
||||
struct classify_object<T,
|
||||
typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
|
||||
is_direct_constructible<T, double>::value &&
|
||||
is_direct_constructible<T, int>::value>::type> {
|
||||
static constexpr objCategory value{number_constructible};
|
||||
};
|
||||
|
||||
/// Assignable from int
|
||||
template <typename T>
|
||||
struct classify_object<
|
||||
T,
|
||||
typename std::enable_if<uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
|
||||
is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
|
||||
struct classify_object<T,
|
||||
typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
|
||||
!is_direct_constructible<T, double>::value &&
|
||||
is_direct_constructible<T, int>::value>::type> {
|
||||
static constexpr objCategory value{integer_constructible};
|
||||
};
|
||||
|
||||
/// Assignable from double
|
||||
template <typename T>
|
||||
struct classify_object<
|
||||
T,
|
||||
typename std::enable_if<uncommon_type<T>::value && is_direct_constructible<T, double>::value &&
|
||||
!is_direct_constructible<T, int>::value && type_count<T>::value == 1>::type> {
|
||||
struct classify_object<T,
|
||||
typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
|
||||
is_direct_constructible<T, double>::value &&
|
||||
!is_direct_constructible<T, int>::value>::type> {
|
||||
static constexpr objCategory value{double_constructible};
|
||||
};
|
||||
|
||||
@ -392,9 +394,9 @@ struct classify_object<
|
||||
template <typename T>
|
||||
struct classify_object<
|
||||
T,
|
||||
typename std::enable_if<(is_tuple_like<T>::value && uncommon_type<T>::value &&
|
||||
!is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value) ||
|
||||
type_count<T>::value >= 2>::type> {
|
||||
typename std::enable_if<type_count<T>::value >= 2 || (is_tuple_like<T>::value && uncommon_type<T>::value &&
|
||||
!is_direct_constructible<T, double>::value &&
|
||||
!is_direct_constructible<T, int>::value)>::type> {
|
||||
static constexpr objCategory value{tuple_value};
|
||||
};
|
||||
|
||||
@ -454,54 +456,39 @@ constexpr const char *type_name() {
|
||||
return type_name<typename T::value_type>();
|
||||
}
|
||||
|
||||
/// Print name for tuple types
|
||||
/// Print name for single element tuple types
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 1, detail::enabler> = detail::dummy>
|
||||
std::string type_name() {
|
||||
return type_name<typename std::tuple_element<0, T>::type>();
|
||||
}
|
||||
/// Print type name for 2 element tuples
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 2, detail::enabler> = detail::dummy>
|
||||
std::string type_name() {
|
||||
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<1, T>::type>() + "]";
|
||||
|
||||
/// Empty string if the index > tuple size
|
||||
template <typename T, std::size_t I>
|
||||
inline typename std::enable_if<I == type_count<T>::value, std::string>::type tuple_name() {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
/// Print type name for 3 element tuples
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 3, detail::enabler> = detail::dummy>
|
||||
std::string type_name() {
|
||||
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<1, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<2, T>::type>() + "]";
|
||||
/// Recursively generate the tuple type name
|
||||
template <typename T, std::size_t I>
|
||||
inline typename std::enable_if < I<type_count<T>::value, std::string>::type tuple_name() {
|
||||
std::string str = std::string(type_name<typename std::tuple_element<I, T>::type>()) + ',' + tuple_name<T, I + 1>();
|
||||
if(str.back() == ',')
|
||||
str.pop_back();
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Print type name for 4 element tuples
|
||||
/// Print type name for tuples with 2 or more elements
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 4, detail::enabler> = detail::dummy>
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value >= 2, detail::enabler> = detail::dummy>
|
||||
std::string type_name() {
|
||||
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<1, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<2, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<3, T>::type>() + "]";
|
||||
auto tname = std::string(1, '[') + tuple_name<T, 0>();
|
||||
tname.push_back(']');
|
||||
return tname;
|
||||
}
|
||||
|
||||
/// Print type name for 5 element tuples
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 5, detail::enabler> = detail::dummy>
|
||||
std::string type_name() {
|
||||
return std::string("[") + type_name<typename std::tuple_element<0, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<1, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<2, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<3, T>::type>() + "," +
|
||||
type_name<typename std::tuple_element<4, T>::type>() + "]";
|
||||
}
|
||||
// Lexical cast
|
||||
|
||||
/// Convert a flag into an integer value typically binary flags
|
||||
@ -777,101 +764,34 @@ bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
return (!output.empty()) && retval;
|
||||
}
|
||||
|
||||
/// Conversion for single element tuple and single element tuple conversion type
|
||||
template <class T,
|
||||
class XC,
|
||||
enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && is_tuple_like<XC>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
static_assert(type_count<T>::value == type_count<XC>::value,
|
||||
"when using converting to tuples different cross conversion are not possible");
|
||||
|
||||
bool retval = lexical_assign<typename std::tuple_element<0, T>::type, typename std::tuple_element<0, XC>::type>(
|
||||
strings[0], std::get<0>(output));
|
||||
/// function template for converting tuples if the static Index is greater than the tuple size
|
||||
template <class T, class XC, std::size_t I>
|
||||
inline typename std::enable_if<I >= type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &,
|
||||
T &) {
|
||||
return true;
|
||||
}
|
||||
/// Tuple conversion operation
|
||||
template <class T, class XC, std::size_t I>
|
||||
inline typename std::enable_if <
|
||||
I<type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &strings, T &output) {
|
||||
bool retval = true;
|
||||
if(strings.size() > I) {
|
||||
retval &= lexical_assign<
|
||||
typename std::tuple_element<I, T>::type,
|
||||
typename std::conditional<is_tuple_like<XC>::value, typename std::tuple_element<I, XC>::type, XC>::type>(
|
||||
strings[I], std::get<I>(output));
|
||||
}
|
||||
retval &= tuple_conversion<T, XC, I + 1>(strings, output);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// Conversion for single element tuple and single defined type
|
||||
template <class T,
|
||||
class XC,
|
||||
enable_if_t<type_count<T>::value == 1 && is_tuple_like<T>::value && !is_tuple_like<XC>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
/// Conversion for tuples
|
||||
template <class T, class XC, enable_if_t<is_tuple_like<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
static_assert(type_count<T>::value == type_count<XC>::value,
|
||||
"when using converting to tuples different cross conversion are not possible");
|
||||
|
||||
bool retval = lexical_assign<typename std::tuple_element<0, T>::type, XC>(strings[0], std::get<0>(output));
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// conversion for two element tuple
|
||||
template <class T, class XC, enable_if_t<type_count<T>::value == 2, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
static_assert(type_count<T>::value == type_count<XC>::value,
|
||||
"when using converting to tuples different cross conversion are not possible");
|
||||
|
||||
bool retval = lexical_cast(strings[0], std::get<0>(output));
|
||||
if(strings.size() > 1) {
|
||||
retval &= lexical_cast(strings[1], std::get<1>(output));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// conversion for three element tuple
|
||||
template <class T, class XC, enable_if_t<type_count<T>::value == 3, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
static_assert(type_count<T>::value == type_count<XC>::value,
|
||||
"when using converting to tuples different cross conversion are not possible");
|
||||
|
||||
bool retval = lexical_cast(strings[0], std::get<0>(output));
|
||||
if(strings.size() > 1) {
|
||||
retval &= lexical_cast(strings[1], std::get<1>(output));
|
||||
}
|
||||
if(strings.size() > 2) {
|
||||
retval &= lexical_cast(strings[2], std::get<2>(output));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// conversion for four element tuple
|
||||
template <class T, class XC, enable_if_t<type_count<T>::value == 4, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
static_assert(type_count<T>::value == type_count<XC>::value,
|
||||
"when using converting to tuples different cross conversion are not possible");
|
||||
|
||||
bool retval = lexical_cast(strings[0], std::get<0>(output));
|
||||
if(strings.size() > 1) {
|
||||
retval &= lexical_cast(strings[1], std::get<1>(output));
|
||||
}
|
||||
if(strings.size() > 2) {
|
||||
retval &= lexical_cast(strings[2], std::get<2>(output));
|
||||
}
|
||||
if(strings.size() > 3) {
|
||||
retval &= lexical_cast(strings[3], std::get<3>(output));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// conversion for five element tuple
|
||||
template <class T, class XC, enable_if_t<type_count<T>::value == 5, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
static_assert(type_count<T>::value == type_count<XC>::value,
|
||||
"when using converting to tuples different cross conversion are not possible");
|
||||
|
||||
bool retval = lexical_cast(strings[0], std::get<0>(output));
|
||||
if(strings.size() > 1) {
|
||||
retval &= lexical_cast(strings[1], std::get<1>(output));
|
||||
}
|
||||
if(strings.size() > 2) {
|
||||
retval &= lexical_cast(strings[2], std::get<2>(output));
|
||||
}
|
||||
if(strings.size() > 3) {
|
||||
retval &= lexical_cast(strings[3], std::get<3>(output));
|
||||
}
|
||||
if(strings.size() > 4) {
|
||||
retval &= lexical_cast(strings[4], std::get<4>(output));
|
||||
}
|
||||
return retval;
|
||||
static_assert(
|
||||
!is_tuple_like<XC>::value || type_count<T>::value == type_count<XC>::value,
|
||||
"if the conversion type is defined as a tuple it must be the same size as the type you are converting to");
|
||||
return tuple_conversion<T, XC, 0>(strings, output);
|
||||
}
|
||||
|
||||
/// Sum a vector of flag representations
|
||||
|
@ -43,6 +43,8 @@ class Validator {
|
||||
std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
|
||||
/// The name for search purposes of the Validator
|
||||
std::string name_;
|
||||
/// A validate will only apply to an indexed value (-1 is all elements)
|
||||
int application_index_ = -1;
|
||||
/// Enable for Validator to allow it to be disabled if need be
|
||||
bool active_{true};
|
||||
/// specify that a validator should not modify the input
|
||||
@ -113,7 +115,13 @@ class Validator {
|
||||
non_modifying_ = no_modify;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Specify the application index of a validator
|
||||
Validator &application_index(int app_index) {
|
||||
application_index_ = app_index;
|
||||
return *this;
|
||||
};
|
||||
/// Get the current value of the application index
|
||||
int get_application_index() const { return application_index_; }
|
||||
/// Get a boolean if the validator is active
|
||||
bool get_active() const { return active_; }
|
||||
|
||||
@ -141,6 +149,7 @@ class Validator {
|
||||
};
|
||||
|
||||
newval.active_ = (active_ & other.active_);
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
|
||||
@ -164,6 +173,7 @@ class Validator {
|
||||
return std::string("(") + s1 + ") OR (" + s2 + ")";
|
||||
};
|
||||
newval.active_ = (active_ & other.active_);
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
|
||||
@ -186,6 +196,7 @@ class Validator {
|
||||
return std::string{};
|
||||
};
|
||||
newval.active_ = active_;
|
||||
newval.application_index_ = application_index_;
|
||||
return newval;
|
||||
}
|
||||
|
||||
@ -201,10 +212,10 @@ class Validator {
|
||||
if((f1.empty()) || (f2.empty())) {
|
||||
return f1 + f2;
|
||||
}
|
||||
return std::string("(") + f1 + ")" + merger + "(" + f2 + ")";
|
||||
return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
|
||||
};
|
||||
}
|
||||
};
|
||||
}; // namespace CLI
|
||||
|
||||
/// Class wrapping some of the accessors of Validator
|
||||
class CustomValidator : public Validator {
|
||||
|
@ -800,6 +800,19 @@ TEST_F(TApp, TakeLastOptMulti) {
|
||||
EXPECT_EQ(vals, std::vector<int>({2, 3}));
|
||||
}
|
||||
|
||||
TEST_F(TApp, TakeLastOptMultiCheck) {
|
||||
std::vector<int> vals;
|
||||
auto opt = app.add_option("--long", vals)->expected(2)->take_last();
|
||||
|
||||
opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
|
||||
opt->check((!CLI::PositiveNumber).application_index(1));
|
||||
args = {"--long", "-1", "2", "-3"};
|
||||
|
||||
EXPECT_NO_THROW(run());
|
||||
|
||||
EXPECT_EQ(vals, std::vector<int>({2, -3}));
|
||||
}
|
||||
|
||||
TEST_F(TApp, TakeFirstOptMulti) {
|
||||
std::vector<int> vals;
|
||||
app.add_option("--long", vals)->expected(2)->take_first();
|
||||
@ -1591,6 +1604,63 @@ TEST_F(TApp, NotFileExists) {
|
||||
EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
|
||||
}
|
||||
|
||||
TEST_F(TApp, pair_check) {
|
||||
std::string myfile{"pair_check_file.txt"};
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
|
||||
std::pair<std::string, int> findex;
|
||||
|
||||
auto v0 = CLI::ExistingFile;
|
||||
v0.application_index(0);
|
||||
auto v1 = CLI::PositiveNumber;
|
||||
v1.application_index(1);
|
||||
app.add_option("--file", findex)->check(v0)->check(v1);
|
||||
|
||||
args = {"--file", myfile, "2"};
|
||||
|
||||
EXPECT_NO_THROW(run());
|
||||
|
||||
EXPECT_EQ(findex.first, myfile);
|
||||
EXPECT_EQ(findex.second, 2);
|
||||
|
||||
args = {"--file", myfile, "-3"};
|
||||
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"--file", myfile, "2"};
|
||||
std::remove(myfile.c_str());
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present
|
||||
/*
|
||||
TEST_F(TApp, pair_check_take_first) {
|
||||
std::string myfile{"pair_check_file2.txt"};
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
|
||||
std::pair<std::string, int> findex;
|
||||
|
||||
auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
|
||||
EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound);
|
||||
opt->get_validator(0)->application_index(0);
|
||||
opt->get_validator(1)->application_index(1);
|
||||
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
|
||||
args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
|
||||
// should only check the last one
|
||||
EXPECT_NO_THROW(run());
|
||||
|
||||
EXPECT_EQ(findex.first, myfile);
|
||||
EXPECT_EQ(findex.second, 2);
|
||||
|
||||
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
|
||||
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
*/
|
||||
TEST_F(TApp, VectorFixedString) {
|
||||
std::vector<std::string> strvec;
|
||||
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
|
||||
@ -1617,6 +1687,24 @@ TEST_F(TApp, VectorDefaultedFixedString) {
|
||||
EXPECT_EQ(answer, strvec);
|
||||
}
|
||||
|
||||
TEST_F(TApp, VectorIndexedValidator) {
|
||||
std::vector<int> vvec;
|
||||
|
||||
CLI::Option *opt = app.add_option("-v", vvec);
|
||||
|
||||
args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
|
||||
run();
|
||||
EXPECT_EQ(4u, app.count("-v"));
|
||||
EXPECT_EQ(4u, vvec.size());
|
||||
opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
|
||||
opt->check((!CLI::PositiveNumber).application_index(1));
|
||||
EXPECT_NO_THROW(run());
|
||||
EXPECT_EQ(4u, vvec.size());
|
||||
// v[3] would be negative
|
||||
opt->check(CLI::Validator(CLI::PositiveNumber).application_index(3));
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, DefaultedResult) {
|
||||
std::string sval = "NA";
|
||||
int ival;
|
||||
@ -2118,6 +2206,20 @@ TEST_F(TApp, CustomDoubleOption) {
|
||||
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
|
||||
}
|
||||
|
||||
// now with tuple support this is possible
|
||||
TEST_F(TApp, CustomDoubleOptionAlt) {
|
||||
|
||||
std::pair<int, double> custom_opt;
|
||||
|
||||
app.add_option("posit", custom_opt);
|
||||
|
||||
args = {"12", "1.5"};
|
||||
|
||||
run();
|
||||
EXPECT_EQ(custom_opt.first, 12);
|
||||
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
|
||||
}
|
||||
|
||||
// #128
|
||||
TEST_F(TApp, RepeatingMultiArgumentOptions) {
|
||||
std::vector<std::string> entries;
|
||||
|
@ -836,6 +836,9 @@ TEST(Types, TypeName) {
|
||||
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int, std::string>>();
|
||||
EXPECT_EQ("[INT,TEXT,FLOAT,UINT,TEXT]", tuple_name);
|
||||
|
||||
tuple_name = CLI::detail::type_name<std::array<int, 10>>();
|
||||
EXPECT_EQ("[INT,INT,INT,INT,INT,INT,INT,INT,INT,INT]", tuple_name);
|
||||
|
||||
std::string text_name = CLI::detail::type_name<std::string>();
|
||||
EXPECT_EQ("TEXT", text_name);
|
||||
|
||||
@ -1005,7 +1008,8 @@ TEST(Types, LexicalConversionVectorDouble) {
|
||||
|
||||
static_assert(!CLI::detail::is_tuple_like<std::vector<double>>::value, "vector should not be like a tuple");
|
||||
static_assert(CLI::detail::is_tuple_like<std::pair<double, double>>::value, "pair of double should be like a tuple");
|
||||
static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array should be like a tuple");
|
||||
static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array<double,4> should be like a tuple");
|
||||
static_assert(CLI::detail::is_tuple_like<std::array<int, 10>>::value, "std::array<int,10> should be like a tuple");
|
||||
static_assert(!CLI::detail::is_tuple_like<std::string>::value, "std::string should not be like a tuple");
|
||||
static_assert(!CLI::detail::is_tuple_like<double>::value, "double should not be like a tuple");
|
||||
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) {
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionXomplwz) {
|
||||
TEST(Types, LexicalConversionTuple10) {
|
||||
CLI::results_t input = {"9", "19", "18", "5", "235235", "9", "19", "18", "5", "235235"};
|
||||
std::array<unsigned int, 10> x;
|
||||
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(std::get<1>(x), 19u);
|
||||
EXPECT_EQ(x[0], 9u);
|
||||
EXPECT_EQ(x[2], 18u);
|
||||
EXPECT_EQ(x[3], 5u);
|
||||
EXPECT_EQ(x[4], 235235u);
|
||||
EXPECT_EQ(x[9], 235235u);
|
||||
input[3] = "hippo";
|
||||
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionTuple10XC) {
|
||||
CLI::results_t input = {"9", "19", "18", "5", "235235", "9", "19", "18", "5", "235235"};
|
||||
std::array<double, 10> x;
|
||||
bool res = CLI::detail::lexical_conversion<decltype(x), std::array<unsigned int, 10>>(input, x);
|
||||
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(std::get<1>(x), 19.0);
|
||||
EXPECT_EQ(x[0], 9.0);
|
||||
EXPECT_EQ(x[2], 18.0);
|
||||
EXPECT_EQ(x[3], 5.0);
|
||||
EXPECT_EQ(x[4], 235235.0);
|
||||
EXPECT_EQ(x[9], 235235.0);
|
||||
input[3] = "19.7";
|
||||
res = CLI::detail::lexical_conversion<decltype(x), std::array<unsigned int, 10>>(input, x);
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionComplex) {
|
||||
CLI::results_t input = {"5.1", "3.5"};
|
||||
std::complex<double> x;
|
||||
bool res = CLI::detail::lexical_conversion<std::complex<double>, std::array<double, 2>>(input, x);
|
||||
|
Loading…
x
Reference in New Issue
Block a user