#include "app_helper.hpp" #include "gmock/gmock.h" #include using ::testing::HasSubstr; using cx = std::complex; CLI::Option * add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { CLI::callback_t fun = [&variable](CLI::results_t res) { double x, y; bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); if(worked) variable = cx(x, y); return worked; }; CLI::Option *opt = app.add_option(name, fun, description, defaulted); opt->type_name("COMPLEX")->type_size(2); if(defaulted) { std::stringstream out; out << variable; opt->default_str(out.str()); } return opt; } TEST_F(TApp, AddingComplexParser) { cx comp{0, 0}; add_option(app, "-c,--complex", comp); args = {"-c", "1.5", "2.5"}; run(); EXPECT_DOUBLE_EQ(1.5, comp.real()); EXPECT_DOUBLE_EQ(2.5, comp.imag()); } TEST_F(TApp, DefaultComplex) { cx comp{1, 2}; add_option(app, "-c,--complex", comp, "", true); args = {"-c", "4", "3"}; std::string help = app.help(); EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); 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, BuiltinComplex) { cx comp{1, 2}; app.add_complex("-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, BuiltinComplexIgnoreI) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); args = {"-c", "4", "3i"}; run(); EXPECT_DOUBLE_EQ(4, comp.real()); EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexFail) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); args = {"-c", "4"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); } // 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::string input, std::pair &output) { auto sep = input.find_first_of(':'); if((sep == std::string::npos) && (sep > 0)) { return false; } output = {input.substr(0, sep), input.substr(sep + 1)}; return true; } } // namespace detail } // namespace CLI TEST_F(TApp, custom_string_converter) { std::pair val; app.add_option("-d,--dual_string", val); args = {"-d", "string1:string2"}; run(); EXPECT_EQ(val.first, "string1"); EXPECT_EQ(val.second, "string2"); } TEST_F(TApp, custom_string_converterFail) { std::pair val; app.add_option("-d,--dual_string", val); args = {"-d", "string2"}; EXPECT_THROW(run(), CLI::ConversionError); } // an example of custom complex number converter that can be used to add new parsing options #if defined(__has_include) #if __has_include() // an example of custom converter that can be used to add new parsing options #define HAS_REGEX_INCLUDE #endif #endif #ifdef HAS_REGEX_INCLUDE // Gcc 4.8 and older and the corresponding standard libraries have a broken so this would // fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to // simplify compilation and prevent a much more complicated #if expression #include namespace CLI { namespace detail { // On MSVC and possibly some other new compilers this can be a free standing function without the template // specialization but this is compiler dependent template <> bool lexical_cast>(std::string input, std::complex &output) { // regular expression to handle complex numbers of various formats static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); std::smatch m; double x = 0.0, y = 0.0; bool worked; std::regex_search(input, m, creg); if(m.size() == 9) { worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); if(worked) { if(*m[5].first == '-') { y = -y; } } } else { if((input.back() == 'j') || (input.back() == 'i')) { auto strval = input.substr(0, input.size() - 1); CLI::detail::trim(strval); worked = CLI::detail::lexical_cast(strval, y); } else { CLI::detail::trim(input); worked = CLI::detail::lexical_cast(input, x); } } if(worked) { output = cx{x, y}; } return worked; } } // namespace detail } // namespace CLI TEST_F(TApp, AddingComplexParserDetail) { bool skip_tests = false; try { // check if the library actually supports regex, it is possible to link against a non working regex in the // standard library std::smatch m; std::string input = "1.5+2.5j"; static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); auto rsearch = std::regex_search(input, m, creg); if(!rsearch) { skip_tests = true; } else { EXPECT_EQ(m.size(), (size_t)9); } } catch(...) { skip_tests = true; } if(!skip_tests) { cx comp{0, 0}; app.add_option("-c,--complex", comp, "add a complex number option"); args = {"-c", "1.5+2.5j"}; run(); EXPECT_DOUBLE_EQ(1.5, comp.real()); EXPECT_DOUBLE_EQ(2.5, comp.imag()); args = {"-c", "1.5-2.5j"}; run(); EXPECT_DOUBLE_EQ(1.5, comp.real()); EXPECT_DOUBLE_EQ(-2.5, comp.imag()); } } #endif