From b26894458bb691f178ffa3a0d3c30003ec800475 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Fri, 25 Jan 2019 06:49:47 -0800 Subject: [PATCH] add some more tests of custom parsers and adjustments to the README Add a test that creates and uses a custom parser to store a value add a check around regex to see if it is working fix warning in AppTest from gcc --- README.md | 2 +- tests/AppTest.cpp | 2 +- tests/NewParseTest.cpp | 130 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 935ef909..9e2865ad 100644 --- a/README.md +++ b/README.md @@ -401,7 +401,7 @@ Every `add_` option you have seen so far depends on one method that takes a lamb Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add an enum, for example, provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`). -If you wanted to extend this to support a completely new type, just use a lambda. An example of a new parser for `complex` that supports all of the features of a standard `add_options` call is in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: +If you wanted to extend this to support a completely new type, use a lambda or add a specialization of the lexical_cast function template in the namespace `CLI::detail` with the type you need to convert to. Some examples of some new parsers for `complex` that support all of the features of a standard `add_options` call are in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: #### Example diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index b40f1471..74fea4bf 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -339,7 +339,7 @@ TEST_F(TApp, doubleVectorFunction) { }); args = {"--val", "5", "--val", "6", "--val", "7"}; run(); - EXPECT_EQ(res.size(), 3); + EXPECT_EQ(res.size(), (size_t)3); EXPECT_EQ(res[0], 10.0); EXPECT_EQ(res[2], 12.0); } diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp index 68c6adfd..b98bf263 100644 --- a/tests/NewParseTest.cpp +++ b/tests/NewParseTest.cpp @@ -97,3 +97,133 @@ TEST_F(TApp, BuiltinComplexFail) { 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