Commit b26894458bb691f178ffa3a0d3c30003ec800475
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
Showing
3 changed files
with
132 additions
and
2 deletions
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 |