mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-30 04:33:53 +00:00
pair/tuple defaults (#1081)
add capability to accept pair/tuple default values and a little cleaner parsing in some cases Fixes #711 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5a03ee5838
commit
af270cee4a
@ -307,6 +307,30 @@ template <typename S> class is_tuple_like {
|
|||||||
static constexpr bool value = decltype(test<S>(0))::value;
|
static constexpr bool value = decltype(test<S>(0))::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// This will only trigger for actual void type
|
||||||
|
template <typename T, typename Enable = void> struct type_count_base {
|
||||||
|
static const int value{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Type size for regular object types that do not look like a tuple
|
||||||
|
template <typename T>
|
||||||
|
struct type_count_base<T,
|
||||||
|
typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
|
||||||
|
!std::is_void<T>::value>::type> {
|
||||||
|
static constexpr int value{1};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// the base tuple size
|
||||||
|
template <typename T>
|
||||||
|
struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
|
||||||
|
static constexpr int value{std::tuple_size<typename std::remove_reference<T>::type>::value};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Type count base for containers is the type_count_base of the individual element
|
||||||
|
template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
|
||||||
|
static constexpr int value{type_count_base<typename T::value_type>::value};
|
||||||
|
};
|
||||||
|
|
||||||
/// Convert an object to a string (directly forward if this can become a string)
|
/// Convert an object to a string (directly forward if this can become a string)
|
||||||
template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
|
template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
|
||||||
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
|
||||||
@ -332,12 +356,29 @@ std::string to_string(T &&value) {
|
|||||||
return stream.str();
|
return stream.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If conversion is not supported, return an empty string (streaming is not supported for that type)
|
// additional forward declarations
|
||||||
|
|
||||||
|
/// Print tuple value string for tuples of size ==1
|
||||||
template <typename T,
|
template <typename T,
|
||||||
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
|
||||||
!is_readable_container<typename std::remove_const<T>::type>::value,
|
!is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value == 1,
|
||||||
detail::enabler> = detail::dummy>
|
detail::enabler> = detail::dummy>
|
||||||
std::string to_string(T &&) {
|
inline std::string to_string(T &&value);
|
||||||
|
|
||||||
|
/// Print tuple value string for tuples of size > 1
|
||||||
|
template <typename T,
|
||||||
|
enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
|
||||||
|
!is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value >= 2,
|
||||||
|
detail::enabler> = detail::dummy>
|
||||||
|
inline 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 &&
|
||||||
|
!is_readable_container<typename std::remove_const<T>::type>::value && !is_tuple_like<T>::value,
|
||||||
|
detail::enabler> = detail::dummy>
|
||||||
|
inline std::string to_string(T &&) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +387,7 @@ template <typename T,
|
|||||||
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
|
||||||
is_readable_container<T>::value,
|
is_readable_container<T>::value,
|
||||||
detail::enabler> = detail::dummy>
|
detail::enabler> = detail::dummy>
|
||||||
std::string to_string(T &&variable) {
|
inline std::string to_string(T &&variable) {
|
||||||
auto cval = variable.begin();
|
auto cval = variable.begin();
|
||||||
auto end = variable.end();
|
auto end = variable.end();
|
||||||
if(cval == end) {
|
if(cval == end) {
|
||||||
@ -360,6 +401,51 @@ std::string to_string(T &&variable) {
|
|||||||
return {"[" + detail::join(defaults) + "]"};
|
return {"[" + detail::join(defaults) + "]"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a tuple like object to a string
|
||||||
|
|
||||||
|
/// forward declarations for tuple_value_strings
|
||||||
|
template <typename T, std::size_t I>
|
||||||
|
inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_value_string(T && /*value*/);
|
||||||
|
|
||||||
|
/// Recursively generate the tuple value string
|
||||||
|
template <typename T, std::size_t I>
|
||||||
|
inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_value_string(T &&value);
|
||||||
|
|
||||||
|
/// Print tuple value string for tuples of size ==1
|
||||||
|
template <typename T,
|
||||||
|
enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
|
||||||
|
!is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value == 1,
|
||||||
|
detail::enabler>>
|
||||||
|
inline std::string to_string(T &&value) {
|
||||||
|
return to_string(std::get<0>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print tuple value string for tuples of size > 1
|
||||||
|
template <typename T,
|
||||||
|
enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
|
||||||
|
!is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value >= 2,
|
||||||
|
detail::enabler>>
|
||||||
|
inline std::string to_string(T &&value) {
|
||||||
|
auto tname = std::string(1, '[') + tuple_value_string<T, 0>(value);
|
||||||
|
tname.push_back(']');
|
||||||
|
return tname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Empty string if the index > tuple size
|
||||||
|
template <typename T, std::size_t I>
|
||||||
|
inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_value_string(T && /*value*/) {
|
||||||
|
return std::string{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively generate the tuple value string
|
||||||
|
template <typename T, std::size_t I>
|
||||||
|
inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_value_string(T &&value) {
|
||||||
|
auto str = std::string{to_string(std::get<I>(value))} + ',' + tuple_value_string<T, I + 1>(value);
|
||||||
|
if(str.back() == ',')
|
||||||
|
str.pop_back();
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
/// special template overload
|
/// special template overload
|
||||||
template <typename T1,
|
template <typename T1,
|
||||||
typename T2,
|
typename T2,
|
||||||
@ -404,30 +490,6 @@ template <typename T, typename def> struct wrapped_type<T, def, typename std::en
|
|||||||
using type = typename T::value_type;
|
using type = typename T::value_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This will only trigger for actual void type
|
|
||||||
template <typename T, typename Enable = void> struct type_count_base {
|
|
||||||
static const int value{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Type size for regular object types that do not look like a tuple
|
|
||||||
template <typename T>
|
|
||||||
struct type_count_base<T,
|
|
||||||
typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
|
|
||||||
!std::is_void<T>::value>::type> {
|
|
||||||
static constexpr int value{1};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// the base tuple size
|
|
||||||
template <typename T>
|
|
||||||
struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
|
|
||||||
static constexpr int value{std::tuple_size<T>::value};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Type count base for containers is the type_count_base of the individual element
|
|
||||||
template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
|
|
||||||
static constexpr int value{type_count_base<typename T::value_type>::value};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Set of overloads to get the type size of an object
|
/// Set of overloads to get the type size of an object
|
||||||
|
|
||||||
/// forward declare the subtype_count structure
|
/// forward declare the subtype_count structure
|
||||||
|
@ -71,25 +71,24 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const {
|
|||||||
std::string desc = app->get_description();
|
std::string desc = app->get_description();
|
||||||
auto min_options = app->get_require_option_min();
|
auto min_options = app->get_require_option_min();
|
||||||
auto max_options = app->get_require_option_max();
|
auto max_options = app->get_require_option_max();
|
||||||
|
|
||||||
if(app->get_required()) {
|
if(app->get_required()) {
|
||||||
desc += " " + get_label("REQUIRED") + " ";
|
desc += " " + get_label("REQUIRED") + " ";
|
||||||
}
|
}
|
||||||
if((max_options == min_options) && (min_options > 0)) {
|
|
||||||
if(min_options == 1) {
|
if(min_options > 0) {
|
||||||
desc += " \n[Exactly 1 of the following options is required]";
|
if(max_options == min_options) {
|
||||||
|
desc += " \n[Exactly " + std::to_string(min_options) + " of the following options are required]";
|
||||||
|
} else if(max_options > 0) {
|
||||||
|
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
|
||||||
|
" of the following options are required]";
|
||||||
} else {
|
} else {
|
||||||
desc += " \n[Exactly " + std::to_string(min_options) + " options from the following list are required]";
|
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
|
||||||
}
|
}
|
||||||
} else if(max_options > 0) {
|
} else if(max_options > 0) {
|
||||||
if(min_options > 0) {
|
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
|
||||||
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
|
|
||||||
" of the follow options are required]";
|
|
||||||
} else {
|
|
||||||
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
|
|
||||||
}
|
|
||||||
} else if(min_options > 0) {
|
|
||||||
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (!desc.empty()) ? desc + "\n" : std::string{};
|
return (!desc.empty()) ? desc + "\n" : std::string{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,11 +662,11 @@ CLI11_INLINE std::string Option::_validate(std::string &result, int index) const
|
|||||||
|
|
||||||
CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::string> &res) const {
|
CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::string> &res) const {
|
||||||
int result_count = 0;
|
int result_count = 0;
|
||||||
if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
|
if((allow_extra_args_ || get_expected_max() > 1) && !result.empty() && result.front() == '[' &&
|
||||||
result.back() == ']') { // this is now a vector string likely from the default or user entry
|
result.back() == ']') { // this is now a vector string likely from the default or user entry
|
||||||
result.pop_back();
|
result.pop_back();
|
||||||
|
|
||||||
for(auto &var : CLI::detail::split(result.substr(1), ',')) {
|
for(auto &var : CLI::detail::split_up(result.substr(1), ',')) {
|
||||||
if(!var.empty()) {
|
if(!var.empty()) {
|
||||||
result_count += _add_result(std::move(var), res);
|
result_count += _add_result(std::move(var), res);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -477,6 +478,46 @@ TEST_CASE_METHOD(TApp, "OneIntFlagLike", "[app]") {
|
|||||||
CHECK(9 == val);
|
CHECK(9 == val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(TApp, "PairDefault", "[app]") {
|
||||||
|
std::pair<double, std::string> pr{57.5, "test"};
|
||||||
|
auto *opt = app.add_option("-i", pr)->expected(0, 2);
|
||||||
|
args = {"-i"};
|
||||||
|
run();
|
||||||
|
CHECK(app.count("-i") == 1u);
|
||||||
|
|
||||||
|
std::pair<double, std::string> pr2{92.5, "t2"};
|
||||||
|
opt->default_val(pr2);
|
||||||
|
run();
|
||||||
|
CHECK(pr == pr2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(TApp, "TupleDefault", "[app]") {
|
||||||
|
std::tuple<double, std::string, int, std::string> pr{57.5, "test", 5, "total"};
|
||||||
|
auto *opt = app.add_option("-i", pr)->expected(0, 4);
|
||||||
|
args = {"-i"};
|
||||||
|
run();
|
||||||
|
CHECK(app.count("-i") == 1u);
|
||||||
|
|
||||||
|
std::tuple<double, std::string, int, std::string> pr2{99.5, "test2", 87, "total3"};
|
||||||
|
opt->default_val(pr2);
|
||||||
|
run();
|
||||||
|
CHECK(pr == pr2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(TApp, "TupleComplex", "[app]") {
|
||||||
|
std::tuple<double, std::string, int, std::pair<std::string, std::string>> pr{57.5, "test", 5, {"total", "total2"}};
|
||||||
|
auto *opt = app.add_option("-i", pr)->expected(0, 4);
|
||||||
|
args = {"-i"};
|
||||||
|
run();
|
||||||
|
CHECK(app.count("-i") == 1u);
|
||||||
|
|
||||||
|
std::tuple<double, std::string, int, std::pair<std::string, std::string>> pr2{
|
||||||
|
99.5, "test2", 87, {"total3", "total4"}};
|
||||||
|
opt->default_val(pr2);
|
||||||
|
run();
|
||||||
|
CHECK(pr == pr2);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_METHOD(TApp, "TogetherInt", "[app]") {
|
TEST_CASE_METHOD(TApp, "TogetherInt", "[app]") {
|
||||||
int i{0};
|
int i{0};
|
||||||
app.add_option("-i,--int", i);
|
app.add_option("-i,--int", i);
|
||||||
|
@ -49,6 +49,11 @@ TEST_CASE("TypeTools: tuple", "[helpers]") {
|
|||||||
CHECK(v);
|
CHECK(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("TypeTools: tuple_to_string", "[helpers]") {
|
||||||
|
std::pair<double, std::string> p1{0.999, "kWh"};
|
||||||
|
CHECK(CLI::detail::to_string(p1) == "[0.999,kWh]");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("TypeTools: type_size", "[helpers]") {
|
TEST_CASE("TypeTools: type_size", "[helpers]") {
|
||||||
auto V = CLI::detail::type_count<int>::value;
|
auto V = CLI::detail::type_count<int>::value;
|
||||||
CHECK(1 == V);
|
CHECK(1 == V);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user