1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +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:
Philip Top 2024-11-09 07:57:45 -08:00 committed by GitHub
parent 5a03ee5838
commit af270cee4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 150 additions and 43 deletions

View File

@ -307,6 +307,30 @@ template <typename S> class is_tuple_like {
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)
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)) {
@ -332,12 +356,29 @@ std::string to_string(T &&value) {
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,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
!is_readable_container<typename std::remove_const<T>::type>::value,
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> = 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 {};
}
@ -346,7 +387,7 @@ template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
is_readable_container<T>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&variable) {
inline std::string to_string(T &&variable) {
auto cval = variable.begin();
auto end = variable.end();
if(cval == end) {
@ -360,6 +401,51 @@ std::string to_string(T &&variable) {
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
template <typename T1,
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;
};
/// 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
/// forward declare the subtype_count structure

View File

@ -71,25 +71,24 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const {
std::string desc = app->get_description();
auto min_options = app->get_require_option_min();
auto max_options = app->get_require_option_max();
if(app->get_required()) {
desc += " " + get_label("REQUIRED") + " ";
}
if((max_options == min_options) && (min_options > 0)) {
if(min_options == 1) {
desc += " \n[Exactly 1 of the following options is required]";
if(min_options > 0) {
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 {
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) {
if(min_options > 0) {
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]";
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
}
return (!desc.empty()) ? desc + "\n" : std::string{};
}

View File

@ -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 {
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.pop_back();
for(auto &var : CLI::detail::split(result.substr(1), ',')) {
for(auto &var : CLI::detail::split_up(result.substr(1), ',')) {
if(!var.empty()) {
result_count += _add_result(std::move(var), res);
}

View File

@ -14,6 +14,7 @@
#include <limits>
#include <map>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
@ -477,6 +478,46 @@ TEST_CASE_METHOD(TApp, "OneIntFlagLike", "[app]") {
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]") {
int i{0};
app.add_option("-i,--int", i);

View File

@ -49,6 +49,11 @@ TEST_CASE("TypeTools: tuple", "[helpers]") {
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]") {
auto V = CLI::detail::type_count<int>::value;
CHECK(1 == V);