diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 4cd7a9f5..1043955b 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -307,6 +307,30 @@ template class is_tuple_like { static constexpr bool value = decltype(test(0))::value; }; +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::type>::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + /// Convert an object to a string (directly forward if this can become a string) template ::value, detail::enabler> = detail::dummy> auto to_string(T &&value) -> decltype(std::forward(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 ::value && !is_ostreamable::value && - !is_readable_container::type>::value, + enable_if_t::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::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 ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::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::value && !is_ostreamable::value && + !is_readable_container::type>::value && !is_tuple_like::value, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&) { return {}; } @@ -346,7 +387,7 @@ template ::value && !is_ostreamable::value && is_readable_container::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 +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/); + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value); + +/// Print tuple value string for tuples of size ==1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::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 ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler>> +inline std::string to_string(T &&value) { + auto tname = std::string(1, '[') + tuple_value_string(value); + tname.push_back(']'); + return tname; +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/) { + return std::string{}; +} + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value) { + auto str = std::string{to_string(std::get(value))} + ',' + tuple_value_string(value); + if(str.back() == ',') + str.pop_back(); + return str; +} + /// special template overload template struct wrapped_type struct type_count_base { - static const int value{0}; -}; - -/// Type size for regular object types that do not look like a tuple -template -struct type_count_base::value && !is_mutable_container::value && - !std::is_void::value>::type> { - static constexpr int value{1}; -}; - -/// the base tuple size -template -struct type_count_base::value && !is_mutable_container::value>::type> { - static constexpr int value{std::tuple_size::value}; -}; - -/// Type count base for containers is the type_count_base of the individual element -template struct type_count_base::value>::type> { - static constexpr int value{type_count_base::value}; -}; - /// Set of overloads to get the type size of an object /// forward declare the subtype_count structure diff --git a/include/CLI/impl/Formatter_inl.hpp b/include/CLI/impl/Formatter_inl.hpp index 9a5e9f7b..ab324d6e 100644 --- a/include/CLI/impl/Formatter_inl.hpp +++ b/include/CLI/impl/Formatter_inl.hpp @@ -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{}; } diff --git a/include/CLI/impl/Option_inl.hpp b/include/CLI/impl/Option_inl.hpp index 71d13a0c..ae955daf 100644 --- a/include/CLI/impl/Option_inl.hpp +++ b/include/CLI/impl/Option_inl.hpp @@ -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 &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); } diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index f3f5ac8c..65c93c51 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -477,6 +478,46 @@ TEST_CASE_METHOD(TApp, "OneIntFlagLike", "[app]") { CHECK(9 == val); } +TEST_CASE_METHOD(TApp, "PairDefault", "[app]") { + std::pair pr{57.5, "test"}; + auto *opt = app.add_option("-i", pr)->expected(0, 2); + args = {"-i"}; + run(); + CHECK(app.count("-i") == 1u); + + std::pair pr2{92.5, "t2"}; + opt->default_val(pr2); + run(); + CHECK(pr == pr2); +} + +TEST_CASE_METHOD(TApp, "TupleDefault", "[app]") { + std::tuple 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 pr2{99.5, "test2", 87, "total3"}; + opt->default_val(pr2); + run(); + CHECK(pr == pr2); +} + +TEST_CASE_METHOD(TApp, "TupleComplex", "[app]") { + std::tuple> 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> 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); diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index eff8fcb5..a105c93e 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -49,6 +49,11 @@ TEST_CASE("TypeTools: tuple", "[helpers]") { CHECK(v); } +TEST_CASE("TypeTools: tuple_to_string", "[helpers]") { + std::pair 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::value; CHECK(1 == V);