mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +00:00
Support tuple (#307)
* add some tests with default capture on the two parameter template and some notes about it in the README.md remove the test from visual studio 2015 vs2015 doesn't seem to properly deal with is_assignable in the cases we care about so make a standalone version that is more direct in what we are doing add version to appveyor and add some notes to the readme fix a few test cases to make sure code is covered and test a few other paths remove unneeded enum streaming operator add some diagnostic escapes around trait code to eliminate gcc Wnarrowing warnings work specification of the template operations remove optional add some templates for options conversions add the two parameter template for add_option * Fix some comments from Code review and add more description * start work on trying to clean up the type traits for which lexical cast overload to use * fix readme issue and make the condition tests a little clearer * add a check for out of range errors on boolean conversions * Fix capitalization and some comments on option functions * Allow immediate_callback on the main app to run the main app callback prior to named subcommand callbacks, and reflect this change in the a new test and docs. * add a is_tuple_like trait, and type_count structure for getting the number of elements to require * add lexical conversion functions for tuples and other types * remove the separate vector option and option function * test out the type names for tuples * add some more lexical conversion functions and test * more work on tuples and tests * fix some merge warnings * fix some typename usage and c++14 only constructs * tweak some of the template to remove undefined references * add extra static assert about is_tuple_like * fix some undefined references in clang * move around some of the type_count templates to be used in the type_name functions * move the type_count around and add some additional checks on the classification * add some info to the readme
This commit is contained in:
parent
4bfce43795
commit
127f5388ab
15
README.md
15
README.md
@ -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.
|
||||
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.
|
||||
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.
|
||||
//🚧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.
|
||||
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="")
|
||||
@ -212,7 +212,7 @@ app.add_flag(option_name,
|
||||
help_string="")
|
||||
|
||||
app.add_flag(option_name,
|
||||
variable_to_bind_to, // bool, int, 🆕 float, 🆕 vector, 🆕 enum, or 🆕 string-like, or 🆕 anything with a defined conversion from a string like add_option
|
||||
variable_to_bind_to, // bool, int, 🆕 float, 🆕 vector, 🆕 enum, or 🆕 string-like, or 🆕 any singular object with a defined conversion from a string like add_option
|
||||
help_string="")
|
||||
|
||||
app.add_flag_function(option_name, // 🆕
|
||||
@ -249,7 +249,7 @@ The `add_option_function<type>(...` function will typically require the template
|
||||
double val
|
||||
app.add_option<double,unsigned int>("-v",val);
|
||||
```
|
||||
which would first verify the input is convertible to an int before assigning it. Or using some variant type
|
||||
which would first verify the input is convertible to an unsigned int before assigning it. Or using some variant type
|
||||
```
|
||||
using vtype=std::variant<int, double, std::string>;
|
||||
vtype v1;
|
||||
@ -261,6 +261,13 @@ 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
|
||||
```
|
||||
std::vector<double> v1;
|
||||
app.add_option<std::vector<double>,int>("--vs",v1);
|
||||
```
|
||||
would load a vector of doubles but ensure all values can be represented as integers.
|
||||
|
||||
Automatic direct capture of the default string is disabled when using the two parameter template. Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases.
|
||||
|
||||
🆕 Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example:
|
||||
|
@ -468,35 +468,37 @@ class App {
|
||||
|
||||
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
|
||||
|
||||
template <typename T,
|
||||
typename XC = T,
|
||||
enable_if_t<!is_vector<XC>::value && !std::is_const<XC>::value, detail::enabler> = detail::dummy>
|
||||
template <typename T, typename XC = T, enable_if_t<!std::is_const<XC>::value, detail::enabler> = detail::dummy>
|
||||
Option *add_option(std::string option_name,
|
||||
T &variable, ///< The variable to set
|
||||
std::string option_description = "",
|
||||
bool defaulted = false) {
|
||||
|
||||
auto fun = [&variable](CLI::results_t res) { // comment for spacing
|
||||
return detail::lexical_assign<T, XC>(res[0], variable);
|
||||
return detail::lexical_conversion<T, XC>(res, variable);
|
||||
};
|
||||
|
||||
Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
|
||||
return std::string(CLI::detail::checked_to_string<T, XC>(variable));
|
||||
return CLI::detail::checked_to_string<T, XC>(variable);
|
||||
});
|
||||
opt->type_name(detail::type_name<XC>());
|
||||
|
||||
// these must be actual variable since (std::max) sometimes is defined in terms of references and references to
|
||||
// structs used in the evaluation can be temporary so that would cause issues.
|
||||
auto Tcount = detail::type_count<T>::value;
|
||||
auto XCcount = detail::type_count<XC>::value;
|
||||
opt->type_size((std::max)(Tcount, XCcount));
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add option for a callback of a specific type
|
||||
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
|
||||
template <typename T>
|
||||
Option *add_option_function(std::string option_name,
|
||||
const std::function<void(const T &)> &func, ///< the callback to execute
|
||||
std::string option_description = "") {
|
||||
|
||||
auto fun = [func](CLI::results_t res) {
|
||||
T variable;
|
||||
bool result = detail::lexical_cast(res[0], variable);
|
||||
bool result = detail::lexical_conversion<T, T>(res, variable);
|
||||
if(result) {
|
||||
func(variable);
|
||||
}
|
||||
@ -505,6 +507,7 @@ class App {
|
||||
|
||||
Option *opt = add_option(option_name, std::move(fun), option_description, false);
|
||||
opt->type_name(detail::type_name<T>());
|
||||
opt->type_size(detail::type_count<T>::value);
|
||||
return opt;
|
||||
}
|
||||
|
||||
@ -521,65 +524,6 @@ class App {
|
||||
return add_option(option_name, CLI::callback_t(), option_description, false);
|
||||
}
|
||||
|
||||
/// Add option for vectors
|
||||
template <typename T>
|
||||
Option *add_option(std::string option_name,
|
||||
std::vector<T> &variable, ///< The variable vector to set
|
||||
std::string option_description = "",
|
||||
bool defaulted = false) {
|
||||
|
||||
auto fun = [&variable](CLI::results_t res) {
|
||||
bool retval = true;
|
||||
variable.clear();
|
||||
variable.reserve(res.size());
|
||||
for(const auto &elem : res) {
|
||||
|
||||
variable.emplace_back();
|
||||
retval &= detail::lexical_cast(elem, variable.back());
|
||||
}
|
||||
return (!variable.empty()) && retval;
|
||||
};
|
||||
|
||||
auto default_function = [&variable]() {
|
||||
std::vector<std::string> defaults;
|
||||
defaults.resize(variable.size());
|
||||
std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) {
|
||||
return std::string(CLI::detail::to_string(val));
|
||||
});
|
||||
return std::string("[" + detail::join(defaults) + "]");
|
||||
};
|
||||
|
||||
Option *opt = add_option(option_name, fun, option_description, defaulted, default_function);
|
||||
opt->type_name(detail::type_name<T>())->type_size(-1);
|
||||
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Add option for a vector callback of a specific type
|
||||
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
|
||||
Option *add_option_function(std::string option_name,
|
||||
const std::function<void(const T &)> &func, ///< the callback to execute
|
||||
std::string option_description = "") {
|
||||
|
||||
CLI::callback_t fun = [func](CLI::results_t res) {
|
||||
T values;
|
||||
bool retval = true;
|
||||
values.reserve(res.size());
|
||||
for(const auto &elem : res) {
|
||||
values.emplace_back();
|
||||
retval &= detail::lexical_cast(elem, values.back());
|
||||
}
|
||||
if(retval) {
|
||||
func(values);
|
||||
}
|
||||
return retval;
|
||||
};
|
||||
|
||||
Option *opt = add_option(option_name, std::move(fun), std::move(option_description), false);
|
||||
opt->type_name(detail::type_name<T>())->type_size(-1);
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// Set a help flag, replace the existing one if present
|
||||
Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
|
||||
// take flag_description by const reference otherwise add_flag tries to assign to help_description
|
||||
|
@ -142,7 +142,7 @@ template <typename T, typename C> class is_direct_constructible {
|
||||
template <typename, typename> static auto test(...) -> std::false_type;
|
||||
|
||||
public:
|
||||
static const bool value = decltype(test<T, C>(0))::value;
|
||||
static constexpr bool value = decltype(test<T, C>(0))::value;
|
||||
};
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
@ -158,7 +158,7 @@ template <typename T, typename S = std::ostringstream> class is_ostreamable {
|
||||
template <typename, typename> static auto test(...) -> std::false_type;
|
||||
|
||||
public:
|
||||
static const bool value = decltype(test<T, S>(0))::value;
|
||||
static constexpr bool value = decltype(test<T, S>(0))::value;
|
||||
};
|
||||
|
||||
/// Check for input streamability
|
||||
@ -169,7 +169,7 @@ template <typename T, typename S = std::istringstream> class is_istreamable {
|
||||
template <typename, typename> static auto test(...) -> std::false_type;
|
||||
|
||||
public:
|
||||
static const bool value = decltype(test<T, S>(0))::value;
|
||||
static constexpr bool value = decltype(test<T, S>(0))::value;
|
||||
};
|
||||
|
||||
/// Templated operation to get a value from a stream
|
||||
@ -186,6 +186,18 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for tuple like types, as in classes with a tuple_size type trait
|
||||
template <typename S> class is_tuple_like {
|
||||
template <typename SS>
|
||||
// static auto test(int)
|
||||
// -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
|
||||
static auto test(int) -> decltype(std::tuple_size<SS>::value, std::true_type{});
|
||||
template <typename> static auto test(...) -> std::false_type;
|
||||
|
||||
public:
|
||||
static constexpr bool value = decltype(test<S>(0))::value;
|
||||
};
|
||||
|
||||
/// Convert an object to a string (directly forward if this can become a string)
|
||||
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
|
||||
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
||||
@ -204,12 +216,30 @@ std::string to_string(T &&value) {
|
||||
|
||||
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
|
||||
template <typename T,
|
||||
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value, detail::enabler> =
|
||||
detail::dummy>
|
||||
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
||||
!is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
std::string to_string(T &&) {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
/// convert a vector to a string
|
||||
template <typename T,
|
||||
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
||||
is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
std::string to_string(T &&variable) {
|
||||
std::vector<std::string> defaults;
|
||||
defaults.reserve(variable.size());
|
||||
auto cval = variable.begin();
|
||||
auto end = variable.end();
|
||||
while(cval != end) {
|
||||
defaults.emplace_back(CLI::detail::to_string(*cval));
|
||||
++cval;
|
||||
}
|
||||
return std::string("[" + detail::join(defaults) + "]");
|
||||
}
|
||||
|
||||
/// special template overload
|
||||
template <typename T1,
|
||||
typename T2,
|
||||
@ -228,6 +258,25 @@ std::string checked_to_string(T &&) {
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
/// This will only trigger for actual void type
|
||||
template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
|
||||
|
||||
/// Set of overloads to get the type size of an object
|
||||
template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
|
||||
static constexpr int value{std::tuple_size<T>::value};
|
||||
};
|
||||
/// Type size for regular object types that do not look like a tuple
|
||||
template <typename T>
|
||||
struct type_count<
|
||||
T,
|
||||
typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> {
|
||||
static constexpr int value{1};
|
||||
};
|
||||
/// Type size of types that look like a vector
|
||||
template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> {
|
||||
static constexpr int value{-1};
|
||||
};
|
||||
|
||||
// Enumeration of the different supported categorizations of objects
|
||||
enum objCategory : int {
|
||||
integral_value = 2,
|
||||
@ -239,6 +288,7 @@ enum objCategory : int {
|
||||
double_constructible = 14,
|
||||
integer_constructible = 16,
|
||||
vector_value = 30,
|
||||
tuple_value = 35,
|
||||
// string assignable or greater used in a condition so anything string like must come last
|
||||
string_assignable = 50,
|
||||
string_constructible = 60,
|
||||
@ -308,36 +358,49 @@ template <typename T> struct uncommon_type {
|
||||
!std::is_enum<T>::value,
|
||||
std::true_type,
|
||||
std::false_type>::type;
|
||||
static const bool value = type::value;
|
||||
static constexpr bool value = type::value;
|
||||
};
|
||||
|
||||
/// 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> {
|
||||
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> {
|
||||
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> {
|
||||
static const objCategory value{integer_constructible};
|
||||
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> {
|
||||
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> {
|
||||
static const objCategory value{double_constructible};
|
||||
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> {
|
||||
static constexpr objCategory value{double_constructible};
|
||||
};
|
||||
|
||||
/// vector type
|
||||
/// Tuple type
|
||||
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> {
|
||||
static constexpr objCategory value{tuple_value};
|
||||
};
|
||||
|
||||
/// Vector type
|
||||
template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> {
|
||||
static const objCategory value{vector_value};
|
||||
static constexpr objCategory value{vector_value};
|
||||
};
|
||||
|
||||
// Type name print
|
||||
@ -367,11 +430,6 @@ constexpr const char *type_name() {
|
||||
return "FLOAT";
|
||||
}
|
||||
|
||||
/// This one should not be used, since vector types print the internal type
|
||||
template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>
|
||||
constexpr const char *type_name() {
|
||||
return "VECTOR";
|
||||
}
|
||||
/// Print name for enumeration types
|
||||
template <typename T, enable_if_t<classify_object<T>::value == enumeration, detail::enabler> = detail::dummy>
|
||||
constexpr const char *type_name() {
|
||||
@ -390,6 +448,60 @@ constexpr const char *type_name() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
/// This one should not be used normally, since vector types print the internal type
|
||||
template <typename T, enable_if_t<classify_object<T>::value == vector_value, detail::enabler> = detail::dummy>
|
||||
constexpr const char *type_name() {
|
||||
return type_name<typename T::value_type>();
|
||||
}
|
||||
|
||||
/// Print name for 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>() + "]";
|
||||
}
|
||||
|
||||
/// 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>() + "]";
|
||||
}
|
||||
|
||||
/// Print type name for 4 element tuples
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<classify_object<T>::value == tuple_value && type_count<T>::value == 4, 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>() + "]";
|
||||
}
|
||||
|
||||
/// 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
|
||||
@ -574,19 +686,19 @@ bool lexical_cast(const std::string &input, T &output) {
|
||||
}
|
||||
|
||||
/// Assign a value through lexical cast operations
|
||||
template <class T, class XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy>
|
||||
template <typename T, typename XC, enable_if_t<std::is_same<T, XC>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_assign(const std::string &input, T &output) {
|
||||
return lexical_cast(input, output);
|
||||
}
|
||||
|
||||
/// Assign a value converted from a string in lexical cast to the output value directly
|
||||
template <
|
||||
class T,
|
||||
class XC,
|
||||
typename T,
|
||||
typename XC,
|
||||
enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_assign(const std::string &input, T &output) {
|
||||
XC val;
|
||||
auto parse_result = lexical_cast<XC>(input, val);
|
||||
bool parse_result = lexical_cast<XC>(input, val);
|
||||
if(parse_result) {
|
||||
output = val;
|
||||
}
|
||||
@ -594,8 +706,8 @@ bool lexical_assign(const std::string &input, T &output) {
|
||||
}
|
||||
|
||||
/// Assign a value from a lexical cast through constructing a value and move assigning it
|
||||
template <class T,
|
||||
class XC,
|
||||
template <typename T,
|
||||
typename XC,
|
||||
enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value &&
|
||||
std::is_move_assignable<T>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
@ -607,6 +719,160 @@ bool lexical_assign(const std::string &input, T &output) {
|
||||
}
|
||||
return parse_result;
|
||||
}
|
||||
/// Lexical conversion if there is only one element
|
||||
template <typename T,
|
||||
typename XC,
|
||||
enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value, detail::enabler> =
|
||||
detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
return lexical_assign<T, XC>(strings[0], output);
|
||||
}
|
||||
|
||||
/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor
|
||||
template <typename T,
|
||||
typename XC,
|
||||
enable_if_t<type_count<T>::value == 1 && type_count<XC>::value == 2, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
typename std::tuple_element<0, XC>::type v1;
|
||||
typename std::tuple_element<1, XC>::type v2;
|
||||
bool retval = lexical_cast(strings[0], v1);
|
||||
if(strings.size() > 1) {
|
||||
retval &= lexical_cast(strings[1], v2);
|
||||
}
|
||||
if(retval) {
|
||||
output = T{v1, v2};
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// Lexical conversion of a vector types
|
||||
template <class T,
|
||||
class XC,
|
||||
enable_if_t<type_count<T>::value == -1 && type_count<XC>::value == -1, detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
bool retval = true;
|
||||
output.clear();
|
||||
output.reserve(strings.size());
|
||||
for(const auto &elem : strings) {
|
||||
|
||||
output.emplace_back();
|
||||
retval &= lexical_assign<typename T::value_type, typename XC::value_type>(elem, output.back());
|
||||
}
|
||||
return (!output.empty()) && retval;
|
||||
}
|
||||
|
||||
/// Conversion to a vector type using a particular single type as the conversion type
|
||||
template <class T,
|
||||
class XC,
|
||||
enable_if_t<(type_count<T>::value == -1) && (type_count<XC>::value == 1), detail::enabler> = detail::dummy>
|
||||
bool lexical_conversion(const std::vector<std ::string> &strings, T &output) {
|
||||
bool retval = true;
|
||||
output.clear();
|
||||
output.reserve(strings.size());
|
||||
for(const auto &elem : strings) {
|
||||
|
||||
output.emplace_back();
|
||||
retval &= lexical_assign<typename T::value_type, XC>(elem, output.back());
|
||||
}
|
||||
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));
|
||||
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>
|
||||
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;
|
||||
}
|
||||
|
||||
/// Sum a vector of flag representations
|
||||
/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is
|
||||
|
@ -1,11 +1,13 @@
|
||||
#include "app_helper.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
class NotStreamable {};
|
||||
|
||||
@ -25,6 +27,30 @@ TEST(TypeTools, Streaming) {
|
||||
EXPECT_EQ(CLI::detail::to_string(std::string("string")), std::string("string"));
|
||||
}
|
||||
|
||||
TEST(TypeTools, tuple) {
|
||||
EXPECT_FALSE(CLI::detail::is_tuple_like<int>::value);
|
||||
EXPECT_FALSE(CLI::detail::is_tuple_like<std::vector<double>>::value);
|
||||
auto v = CLI::detail::is_tuple_like<std::tuple<double, int>>::value;
|
||||
EXPECT_TRUE(v);
|
||||
v = CLI::detail::is_tuple_like<std::tuple<double, double, double>>::value;
|
||||
EXPECT_TRUE(v);
|
||||
}
|
||||
|
||||
TEST(TypeTools, type_size) {
|
||||
auto V = CLI::detail::type_count<int>::value;
|
||||
EXPECT_EQ(V, 1);
|
||||
V = CLI::detail::type_count<void>::value;
|
||||
EXPECT_EQ(V, 0);
|
||||
V = CLI::detail::type_count<std::vector<double>>::value;
|
||||
EXPECT_EQ(V, -1);
|
||||
V = CLI::detail::type_count<std::tuple<double, int>>::value;
|
||||
EXPECT_EQ(V, 2);
|
||||
V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value;
|
||||
EXPECT_EQ(V, 3);
|
||||
V = CLI::detail::type_count<std::array<std::string, 5>>::value;
|
||||
EXPECT_EQ(V, 5);
|
||||
}
|
||||
|
||||
TEST(Split, SimpleByToken) {
|
||||
auto out = CLI::detail::split("one.two.three", '.');
|
||||
ASSERT_EQ(3u, out.size());
|
||||
@ -782,7 +808,33 @@ TEST(Types, TypeName) {
|
||||
EXPECT_EQ("FLOAT", float_name);
|
||||
|
||||
std::string vector_name = CLI::detail::type_name<std::vector<int>>();
|
||||
EXPECT_EQ("VECTOR", vector_name);
|
||||
EXPECT_EQ("INT", vector_name);
|
||||
|
||||
vector_name = CLI::detail::type_name<std::vector<double>>();
|
||||
EXPECT_EQ("FLOAT", vector_name);
|
||||
|
||||
vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>();
|
||||
EXPECT_EQ("UINT", vector_name);
|
||||
auto vclass = CLI::detail::classify_object<std::tuple<double>>::value;
|
||||
EXPECT_EQ(vclass, CLI::detail::objCategory::number_constructible);
|
||||
|
||||
std::string tuple_name = CLI::detail::type_name<std::tuple<double>>();
|
||||
EXPECT_EQ("FLOAT", tuple_name);
|
||||
|
||||
static_assert(CLI::detail::classify_object<std::tuple<int, std::string>>::value ==
|
||||
CLI::detail::objCategory::tuple_value,
|
||||
"tuple<int,string> does not read like a tuple");
|
||||
tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
|
||||
EXPECT_EQ("[INT,TEXT]", tuple_name);
|
||||
|
||||
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
|
||||
EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name);
|
||||
|
||||
tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int>>();
|
||||
EXPECT_EQ("[INT,TEXT,FLOAT,UINT]", tuple_name);
|
||||
|
||||
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);
|
||||
|
||||
std::string text_name = CLI::detail::type_name<std::string>();
|
||||
EXPECT_EQ("TEXT", text_name);
|
||||
@ -793,6 +845,13 @@ TEST(Types, TypeName) {
|
||||
enum class test { test1, test2, test3 };
|
||||
std::string enum_name = CLI::detail::type_name<test>();
|
||||
EXPECT_EQ("ENUM", enum_name);
|
||||
|
||||
vclass = CLI::detail::classify_object<std::tuple<test>>::value;
|
||||
EXPECT_EQ(vclass, CLI::detail::objCategory::tuple_value);
|
||||
static_assert(CLI::detail::classify_object<std::tuple<test>>::value == CLI::detail::objCategory::tuple_value,
|
||||
"tuple<test> does not classify as a tuple");
|
||||
std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
|
||||
EXPECT_EQ("ENUM", enum_name2);
|
||||
}
|
||||
|
||||
TEST(Types, OverflowSmall) {
|
||||
@ -906,6 +965,121 @@ TEST(Types, LexicalCastEnum) {
|
||||
EXPECT_EQ(output2, t2::enum3);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionDouble) {
|
||||
CLI::results_t input = {"9.12"};
|
||||
long double x;
|
||||
bool res = CLI::detail::lexical_conversion<long double, double>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_FLOAT_EQ((float)9.12, (float)x);
|
||||
|
||||
CLI::results_t bad_input = {"hello"};
|
||||
res = CLI::detail::lexical_conversion<long double, double>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionDoubleTuple) {
|
||||
CLI::results_t input = {"9.12"};
|
||||
std::tuple<double> x;
|
||||
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_DOUBLE_EQ(9.12, std::get<0>(x));
|
||||
|
||||
CLI::results_t bad_input = {"hello"};
|
||||
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionVectorDouble) {
|
||||
CLI::results_t input = {"9.12", "10.79", "-3.54"};
|
||||
std::vector<double> x;
|
||||
bool res = CLI::detail::lexical_conversion<std::vector<double>, double>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(x.size(), 3u);
|
||||
EXPECT_DOUBLE_EQ(x[2], -3.54);
|
||||
|
||||
res = CLI::detail::lexical_conversion<std::vector<double>, std::vector<double>>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(x.size(), 3u);
|
||||
EXPECT_DOUBLE_EQ(x[2], -3.54);
|
||||
}
|
||||
|
||||
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::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");
|
||||
|
||||
TEST(Types, LexicalConversionTuple2) {
|
||||
CLI::results_t input = {"9.12", "19"};
|
||||
|
||||
std::tuple<double, int> x;
|
||||
static_assert(CLI::detail::is_tuple_like<decltype(x)>::value,
|
||||
"tuple type must have is_tuple_like trait to be true");
|
||||
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(std::get<1>(x), 19);
|
||||
EXPECT_DOUBLE_EQ(std::get<0>(x), 9.12);
|
||||
|
||||
input = {"19", "9.12"};
|
||||
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionTuple3) {
|
||||
CLI::results_t input = {"9.12", "19", "hippo"};
|
||||
std::tuple<double, int, std::string> x;
|
||||
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(std::get<1>(x), 19);
|
||||
EXPECT_DOUBLE_EQ(std::get<0>(x), 9.12);
|
||||
EXPECT_EQ(std::get<2>(x), "hippo");
|
||||
|
||||
input = {"19", "9.12"};
|
||||
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionTuple4) {
|
||||
CLI::results_t input = {"9.12", "19", "18.6", "5.87"};
|
||||
std::array<double, 4> x;
|
||||
bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_DOUBLE_EQ(std::get<1>(x), 19);
|
||||
EXPECT_DOUBLE_EQ(x[0], 9.12);
|
||||
EXPECT_DOUBLE_EQ(x[2], 18.6);
|
||||
EXPECT_DOUBLE_EQ(x[3], 5.87);
|
||||
|
||||
input = {"19", "9.12", "hippo"};
|
||||
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionTuple5) {
|
||||
CLI::results_t input = {"9", "19", "18", "5", "235235"};
|
||||
std::array<unsigned int, 5> 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);
|
||||
|
||||
input = {"19", "9.12", "hippo"};
|
||||
res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalConversionXomplwz) {
|
||||
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);
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(x.real(), 5.1);
|
||||
EXPECT_EQ(x.imag(), 3.5);
|
||||
}
|
||||
|
||||
TEST(FixNewLines, BasicCheck) {
|
||||
std::string input = "one\ntwo";
|
||||
std::string output = "one\n; two";
|
||||
|
@ -130,15 +130,20 @@ TEST_F(TApp, BuiltinComplexFail) {
|
||||
EXPECT_THROW(run(), CLI::ArgumentMismatch);
|
||||
}
|
||||
|
||||
class spair {
|
||||
public:
|
||||
spair() = default;
|
||||
spair(const std::string &s1, const std::string &s2) : first(s1), second(s2) {}
|
||||
std::string first;
|
||||
std::string second;
|
||||
};
|
||||
// an example of custom converter that can be used to add new parsing options
|
||||
// On MSVC and possibly some other new compilers this can be a free standing function without the template
|
||||
// specialization but this is compiler dependent
|
||||
namespace CLI {
|
||||
namespace detail {
|
||||
|
||||
template <>
|
||||
bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
|
||||
std::pair<std::string, std::string> &output) {
|
||||
template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
|
||||
|
||||
auto sep = input.find_first_of(':');
|
||||
if((sep == std::string::npos) && (sep > 0)) {
|
||||
@ -151,7 +156,7 @@ bool lexical_cast<std::pair<std::string, std::string>>(const std::string &input,
|
||||
} // namespace CLI
|
||||
|
||||
TEST_F(TApp, custom_string_converter) {
|
||||
std::pair<std::string, std::string> val;
|
||||
spair val;
|
||||
app.add_option("-d,--dual_string", val);
|
||||
|
||||
args = {"-d", "string1:string2"};
|
||||
@ -162,7 +167,7 @@ TEST_F(TApp, custom_string_converter) {
|
||||
}
|
||||
|
||||
TEST_F(TApp, custom_string_converterFail) {
|
||||
std::pair<std::string, std::string> val;
|
||||
spair val;
|
||||
app.add_option("-d,--dual_string", val);
|
||||
|
||||
args = {"-d", "string2"};
|
||||
|
Loading…
x
Reference in New Issue
Block a user