Commit b26894458bb691f178ffa3a0d3c30003ec800475

Authored by Philip Top
Committed by Henry Schreiner
1 parent 7cd04e3b

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
@@ -401,7 +401,7 @@ Every `add_` option you have seen so far depends on one method that takes a lamb @@ -401,7 +401,7 @@ Every `add_` option you have seen so far depends on one method that takes a lamb
401 401
402 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>>`). 402 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>>`).
403 403
404 -If you wanted to extend this to support a completely new type, just use a lambda. An example of a new parser for `complex<double>` 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: 404 +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<double>` 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:
405 405
406 #### Example 406 #### Example
407 407
tests/AppTest.cpp
@@ -339,7 +339,7 @@ TEST_F(TApp, doubleVectorFunction) { @@ -339,7 +339,7 @@ TEST_F(TApp, doubleVectorFunction) {
339 }); 339 });
340 args = {"--val", "5", "--val", "6", "--val", "7"}; 340 args = {"--val", "5", "--val", "6", "--val", "7"};
341 run(); 341 run();
342 - EXPECT_EQ(res.size(), 3); 342 + EXPECT_EQ(res.size(), (size_t)3);
343 EXPECT_EQ(res[0], 10.0); 343 EXPECT_EQ(res[0], 10.0);
344 EXPECT_EQ(res[2], 12.0); 344 EXPECT_EQ(res[2], 12.0);
345 } 345 }
tests/NewParseTest.cpp
@@ -97,3 +97,133 @@ TEST_F(TApp, BuiltinComplexFail) { @@ -97,3 +97,133 @@ TEST_F(TApp, BuiltinComplexFail) {
97 97
98 EXPECT_THROW(run(), CLI::ArgumentMismatch); 98 EXPECT_THROW(run(), CLI::ArgumentMismatch);
99 } 99 }
  100 +
  101 +// an example of custom converter that can be used to add new parsing options
  102 +// On MSVC and possibly some other new compilers this can be a free standing function without the template
  103 +// specialization but this is compiler dependent
  104 +namespace CLI {
  105 +namespace detail {
  106 +
  107 +template <>
  108 +bool lexical_cast<std::pair<std::string, std::string>>(std::string input, std::pair<std::string, std::string> &output) {
  109 +
  110 + auto sep = input.find_first_of(':');
  111 + if((sep == std::string::npos) && (sep > 0)) {
  112 + return false;
  113 + }
  114 + output = {input.substr(0, sep), input.substr(sep + 1)};
  115 + return true;
  116 +}
  117 +} // namespace detail
  118 +} // namespace CLI
  119 +
  120 +TEST_F(TApp, custom_string_converter) {
  121 + std::pair<std::string, std::string> val;
  122 + app.add_option("-d,--dual_string", val);
  123 +
  124 + args = {"-d", "string1:string2"};
  125 +
  126 + run();
  127 + EXPECT_EQ(val.first, "string1");
  128 + EXPECT_EQ(val.second, "string2");
  129 +}
  130 +
  131 +TEST_F(TApp, custom_string_converterFail) {
  132 + std::pair<std::string, std::string> val;
  133 + app.add_option("-d,--dual_string", val);
  134 +
  135 + args = {"-d", "string2"};
  136 +
  137 + EXPECT_THROW(run(), CLI::ConversionError);
  138 +}
  139 +
  140 +// an example of custom complex number converter that can be used to add new parsing options
  141 +#if defined(__has_include)
  142 +#if __has_include(<regex>)
  143 +// an example of custom converter that can be used to add new parsing options
  144 +#define HAS_REGEX_INCLUDE
  145 +#endif
  146 +#endif
  147 +
  148 +#ifdef HAS_REGEX_INCLUDE
  149 +// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
  150 +// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
  151 +// simplify compilation and prevent a much more complicated #if expression
  152 +#include <regex>
  153 +namespace CLI {
  154 +namespace detail {
  155 +
  156 +// On MSVC and possibly some other new compilers this can be a free standing function without the template
  157 +// specialization but this is compiler dependent
  158 +template <> bool lexical_cast<std::complex<double>>(std::string input, std::complex<double> &output) {
  159 + // regular expression to handle complex numbers of various formats
  160 + static const std::regex creg(
  161 + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
  162 +
  163 + std::smatch m;
  164 + double x = 0.0, y = 0.0;
  165 + bool worked;
  166 + std::regex_search(input, m, creg);
  167 + if(m.size() == 9) {
  168 + worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
  169 + if(worked) {
  170 + if(*m[5].first == '-') {
  171 + y = -y;
  172 + }
  173 + }
  174 + } else {
  175 + if((input.back() == 'j') || (input.back() == 'i')) {
  176 + auto strval = input.substr(0, input.size() - 1);
  177 + CLI::detail::trim(strval);
  178 + worked = CLI::detail::lexical_cast(strval, y);
  179 + } else {
  180 + CLI::detail::trim(input);
  181 + worked = CLI::detail::lexical_cast(input, x);
  182 + }
  183 + }
  184 + if(worked) {
  185 + output = cx{x, y};
  186 + }
  187 + return worked;
  188 +}
  189 +} // namespace detail
  190 +} // namespace CLI
  191 +
  192 +TEST_F(TApp, AddingComplexParserDetail) {
  193 +
  194 + bool skip_tests = false;
  195 + try { // check if the library actually supports regex, it is possible to link against a non working regex in the
  196 + // standard library
  197 + std::smatch m;
  198 + std::string input = "1.5+2.5j";
  199 + static const std::regex creg(
  200 + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
  201 +
  202 + auto rsearch = std::regex_search(input, m, creg);
  203 + if(!rsearch) {
  204 + skip_tests = true;
  205 + } else {
  206 + EXPECT_EQ(m.size(), (size_t)9);
  207 + }
  208 +
  209 + } catch(...) {
  210 + skip_tests = true;
  211 + }
  212 + if(!skip_tests) {
  213 + cx comp{0, 0};
  214 + app.add_option("-c,--complex", comp, "add a complex number option");
  215 + args = {"-c", "1.5+2.5j"};
  216 +
  217 + run();
  218 +
  219 + EXPECT_DOUBLE_EQ(1.5, comp.real());
  220 + EXPECT_DOUBLE_EQ(2.5, comp.imag());
  221 + args = {"-c", "1.5-2.5j"};
  222 +
  223 + run();
  224 +
  225 + EXPECT_DOUBLE_EQ(1.5, comp.real());
  226 + EXPECT_DOUBLE_EQ(-2.5, comp.imag());
  227 + }
  228 +}
  229 +#endif