diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index f7b567b64..9fd90c977 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -592,7 +592,7 @@ class App { /// Add option for assigning to a variable template ::value, detail::enabler> = detail::dummy> + enable_if_t::value && !detail::is_complex::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, AssignTo &variable, ///< The variable to set std::string option_description = "", @@ -616,6 +616,27 @@ class App { return opt; } + /// Add option for assigning to a complex variable + template ::value && detail::is_complex::value, detail::enabler> = detail::dummy> + Option *add_option(std::string option_name, + AssignTo &variable, ///< The variable to set + std::string option_description = "", + bool defaulted = false) { + + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing + return detail::lexical_conversion(res, variable); + }; + + Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { + return CLI::detail::checked_to_string(variable); + }); + opt->type_name("COMPLEX"); + opt->type_size(1, 2)->delimiter('+')->expected(1)->run_callback_for_default(); + return opt; + } + /// Add option for assigning to a variable template ::value, detail::enabler> = detail::dummy> diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 0b179b0ea..ccdab96aa 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -186,16 +186,16 @@ template class is_istreamable { static constexpr bool value = decltype(test(0))::value; }; - /// Check for complex - template class is_complex { - template - static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); - template static auto test(...)->std::false_type; + template static auto test(...) -> std::false_type; - public: - static constexpr bool value = decltype(test(0))::value; - }; + public: + static constexpr bool value = decltype(test(0))::value; +}; /// Templated operation to get a value from a stream template ::value, detail::enabler> = detail::dummy> @@ -214,9 +214,9 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) { // check to see if an object is a mutable container (fail by default) template struct is_mutable_container : std::false_type {}; -/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end -/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from -/// a std::string +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string template struct is_mutable_container< T, @@ -239,12 +239,8 @@ template struct is_readable_container : std::fal template struct is_readable_container< T, - conditional_t().end()), - decltype(std::declval().begin())>, - void>> - : public std::true_type{}; - + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; // check to see if an object is a wrapper (fail by default) template struct is_wrapper : std::false_type {}; @@ -291,21 +287,19 @@ 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, - detail::enabler> = detail::dummy> +template ::value && !is_ostreamable::value && + !is_readable_container::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::value && !is_ostreamable::value && - is_readable_container::type>::value, - detail::enabler> = detail::dummy> +template ::value && !is_ostreamable::value && + is_readable_container::type>::value, + detail::enabler> = detail::dummy> std::string to_string(T &&variable) { std::vector defaults; auto cval = variable.begin(); @@ -360,23 +354,31 @@ template struct type_count -struct type_count::value && !is_wrapper::value && - !is_tuple_like::value && !std::is_void::value>::type> { +struct type_count< + T, + typename std::enable_if::value && !is_wrapper::value && !is_tuple_like::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { static constexpr int value{1}; }; /// Type size of types that look like a container template struct type_count::value>::type> { - static constexpr int value{is_mutable_container::value ? expected_max_vector_size - : type_count::value}; + static constexpr int value{is_mutable_container::value + ? expected_max_vector_size + : type_count::value}; }; /// Type size for wrapper type that are not containers template -struct type_count::value && is_wrapper::value && !is_tuple_like::value && - !std::is_void::value>::type> { +struct type_count< + T, + typename std::enable_if::value && is_wrapper::value && !is_complex::value && + !is_tuple_like::value && !std::is_void::value>::type> { static constexpr int value{type_count::value}; }; @@ -403,7 +405,7 @@ enum class object_category : int { number_constructible = 12, double_constructible = 14, integer_constructible = 16, - complex_number=22, + complex_number = 22, tuple_value = 35, wrapper_value = 39, container_value = 40, @@ -468,7 +470,7 @@ template struct classify_object struct classify_object::value>::type> { - static constexpr object_category value{ object_category::complex_number }; + static constexpr object_category value{object_category::complex_number}; }; /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, @@ -494,7 +496,7 @@ struct classify_object struct classify_object::value && type_count::value == 1 && + typename std::enable_if::value && type_count::value == 1 && !is_wrapper::value && is_direct_constructible::value && is_direct_constructible::value>::type> { static constexpr object_category value{object_category::number_constructible}; @@ -503,7 +505,7 @@ struct classify_object struct classify_object::value && type_count::value == 1 && + typename std::enable_if::value && type_count::value == 1 && !is_wrapper::value && !is_direct_constructible::value && is_direct_constructible::value>::type> { static constexpr object_category value{object_category::integer_constructible}; @@ -521,7 +523,7 @@ struct classify_object struct classify_object::value >= 2 && !is_wrapper::value ) || + typename std::enable_if<(type_count::value >= 2 && !is_wrapper::value) || (is_tuple_like::value && uncommon_type::value && !is_direct_constructible::value && !is_direct_constructible::value)>::type> { @@ -578,8 +580,8 @@ constexpr const char *type_name() { /// Print name for enumeration types template ::value == object_category::complex_number, detail::enabler> = detail::dummy> - constexpr const char *type_name() { + enable_if_t::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { return "COMPLEX"; } @@ -747,34 +749,31 @@ bool lexical_cast(const std::string &input, T &output) { /// complex template ::value == object_category::complex_number, detail::enabler> = detail::dummy> - bool lexical_cast(const std::string &input, T &output) { - using XC=typename conditional_t::value, typename T::value_type, double>; + enable_if_t::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename conditional_t::value, typename T::value_type, double>; XC x, y; auto str1 = input; bool worked = false; auto nloc = str1.find_last_of('-'); - if (nloc != std::string::npos && nloc > 0) { + if(nloc != std::string::npos && nloc > 0) { worked = detail::lexical_cast(str1.substr(0, nloc), x); str1 = str1.substr(nloc); - if (str1.back() == 'i' || str1.back() == 'j') + if(str1.back() == 'i' || str1.back() == 'j') str1.pop_back(); worked = worked && detail::lexical_cast(str1, y); - } - else { - if (str1.back() == 'i' || str1.back() == 'j') { + } else { + if(str1.back() == 'i' || str1.back() == 'j') { str1.pop_back(); worked = detail::lexical_cast(str1, y); - x = XC{ 0 }; - } - else { + x = XC{0}; + } else { worked = detail::lexical_cast(str1, x); - y = XC{ 0 }; + y = XC{0}; } } - if (worked) - { - output = T{ x,y }; + if(worked) { + output = T{x, y}; return worked; } return from_stream(input, output); @@ -933,6 +932,7 @@ bool lexical_assign(const std::string &input, T &output) { template ::value && !is_tuple_like::value && !is_mutable_container::value && + !is_complex::value && !(classify_object::value == object_category::wrapper_value || classify_object::value == object_category::container_value), detail::enabler> = detail::dummy> @@ -957,25 +957,6 @@ bool lexical_conversion(const std::vector &strings, T &output) { return retval; } -/// wrapper types interiar typesize==1 -template ::value == object_category::wrapper_value && type_count::value == 1, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - if (strings.empty() || strings.front().empty()) - { - output = XC{}; - return true; - } - typename XC::value_type val; - if(lexical_conversion(strings, val)) { - output = XC{val}; - return true; - } - return false; -} - /// Lexical conversion of a container types template &strings, T &output) { return (!output.empty()); } +/// Lexical conversion of a container types with type size of two +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename conditional_t::value, typename XC::value_type, double>; + XC2 x, y; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + if(worked) { + output = XC{x, y}; + } + return worked; + } else { + return lexical_assign(strings[0], output); + } +} + /// Conversion to a vector type using a particular single type as the conversion type template &strings, T &output) { /// Lexical conversion if there is only one element but the conversion type is a mutable container template ::value && !is_mutable_container::value && is_mutable_container::value, detail::enabler> = - detail::dummy> + enable_if_t::value && !is_mutable_container::value && is_mutable_container::value, + detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector &strings, T &output) { if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { @@ -1119,20 +1121,18 @@ bool lexical_conversion(const std::vector &strings, T &output) { return retval; } -/// wrapper types interiar typesize>1 +/// wrapper types template ::value == object_category::wrapper_value && (type_count::value > 1), - detail::enabler> = detail::dummy> - bool lexical_conversion(const std::vector &strings, T &output) { - if (strings.empty() || strings.front().empty()) - { + class XC, + enable_if_t::value == object_category::wrapper_value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, T &output) { + if(strings.empty() || strings.front().empty()) { output = XC{}; return true; } typename XC::value_type val; - if (lexical_conversion(strings, val)) { - output = XC{ val }; + if(lexical_conversion(strings, val)) { + output = XC{val}; return true; } return false; diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp index 8e9e62c1e..d0e2618cd 100644 --- a/tests/NewParseTest.cpp +++ b/tests/NewParseTest.cpp @@ -78,6 +78,27 @@ TEST_F(TApp, BuiltinComplex) { EXPECT_DOUBLE_EQ(3, comp.imag()); } + +TEST_F(TApp, BuiltinComplexOption) { + cx comp{ 1, 2 }; + app.add_option("-c,--complex", comp, "", true); + + args = { "-c", "4", "3" }; + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("1")); + EXPECT_THAT(help, HasSubstr("2")); + EXPECT_THAT(help, HasSubstr("COMPLEX")); + + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + run(); + + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); +} + TEST_F(TApp, BuiltinComplexFloat) { std::complex comp{1, 2}; app.add_complex, float>("-c,--complex", comp, "", true); @@ -98,6 +119,26 @@ TEST_F(TApp, BuiltinComplexFloat) { EXPECT_FLOAT_EQ(3, comp.imag()); } +TEST_F(TApp, BuiltinComplexFloatOption) { + std::complex comp{ 1, 2 }; + app.add_option("-c,--complex", comp, "", true); + + args = { "-c", "4", "3" }; + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("1")); + EXPECT_THAT(help, HasSubstr("2")); + EXPECT_THAT(help, HasSubstr("COMPLEX")); + + EXPECT_FLOAT_EQ(1, comp.real()); + EXPECT_FLOAT_EQ(2, comp.imag()); + + run(); + + EXPECT_FLOAT_EQ(4, comp.real()); + EXPECT_FLOAT_EQ(3, comp.imag()); +} + TEST_F(TApp, BuiltinComplexWithDelimiter) { cx comp{1, 2}; app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); @@ -130,6 +171,38 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) { EXPECT_DOUBLE_EQ(-4, comp.imag()); } +TEST_F(TApp, BuiltinComplexWithDelimiterOption) { + cx comp{ 1, 2 }; + app.add_option("-c,--complex", comp, "", true)->delimiter('+'); + + args = { "-c", "4+3i" }; + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("1")); + EXPECT_THAT(help, HasSubstr("2")); + EXPECT_THAT(help, HasSubstr("COMPLEX")); + + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + run(); + + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); + + args = { "-c", "5+-3i" }; + run(); + + EXPECT_DOUBLE_EQ(5, comp.real()); + EXPECT_DOUBLE_EQ(-3, comp.imag()); + + args = { "-c", "6", "-4i" }; + run(); + + EXPECT_DOUBLE_EQ(6, comp.real()); + EXPECT_DOUBLE_EQ(-4, comp.imag()); +} + TEST_F(TApp, BuiltinComplexIgnoreI) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); @@ -142,6 +215,18 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { EXPECT_DOUBLE_EQ(3, comp.imag()); } +TEST_F(TApp, BuiltinComplexIgnoreIOption) { + cx comp{ 1, 2 }; + app.add_option("-c,--complex", comp); + + args = { "-c", "4", "3i" }; + + run(); + + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); +} + TEST_F(TApp, BuiltinComplexSingleArg) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); @@ -176,6 +261,40 @@ TEST_F(TApp, BuiltinComplexSingleArg) { EXPECT_DOUBLE_EQ(-2.7, comp.imag()); } +TEST_F(TApp, BuiltinComplexSingleArgOption) { + cx comp{ 1, 2 }; + app.add_option("-c,--complex", comp); + + args = { "-c", "4" }; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(0, comp.imag()); + + args = { "-c", "4-2i" }; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(-2, comp.imag()); + args = { "-c", "4+2i" }; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + args = { "-c", "-4+2j" }; + run(); + EXPECT_DOUBLE_EQ(-4, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + args = { "-c", "-4.2-2j" }; + run(); + EXPECT_DOUBLE_EQ(-4.2, comp.real()); + EXPECT_DOUBLE_EQ(-2, comp.imag()); + + args = { "-c", "-4.2-2.7i" }; + run(); + EXPECT_DOUBLE_EQ(-4.2, comp.real()); + EXPECT_DOUBLE_EQ(-2.7, comp.imag()); +} + TEST_F(TApp, BuiltinComplexSingleImag) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); @@ -199,6 +318,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) { EXPECT_DOUBLE_EQ(0, comp.imag()); } +TEST_F(TApp, BuiltinComplexSingleImagOption) { + cx comp{ 1, 2 }; + app.add_option("-c,--complex", comp); + + args = { "-c", "4j" }; + run(); + EXPECT_DOUBLE_EQ(0, comp.real()); + EXPECT_DOUBLE_EQ(4, comp.imag()); + + args = { "-c", "-4j" }; + run(); + EXPECT_DOUBLE_EQ(0, comp.real()); + EXPECT_DOUBLE_EQ(-4, comp.imag()); + args = { "-c", "-4" }; + run(); + EXPECT_DOUBLE_EQ(-4, comp.real()); + EXPECT_DOUBLE_EQ(0, comp.imag()); + args = { "-c", "+4" }; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(0, comp.imag()); +} + /// Simple class containing two strings useful for testing lexical cast and conversions class spair { public: @@ -321,7 +463,8 @@ TEST_F(TApp, AddingComplexParserDetail) { static_assert(CLI::detail::is_complex::value, "complex should register as complex in this situation"); if(!skip_tests) { cx comp{0, 0}; - app.add_option("-c,--complex", comp, "add a complex number option"); + //the default delimiter for complex is '+' so change it back to ',' + app.add_option("-c,--complex", comp, "add a complex number option")->delimiter(','); args = {"-c", "1.5+2.5j"}; run();