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 | 401 | |
| 402 | 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 | 406 | #### Example |
| 407 | 407 | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -339,7 +339,7 @@ TEST_F(TApp, doubleVectorFunction) { |
| 339 | 339 | }); |
| 340 | 340 | args = {"--val", "5", "--val", "6", "--val", "7"}; |
| 341 | 341 | run(); |
| 342 | - EXPECT_EQ(res.size(), 3); | |
| 342 | + EXPECT_EQ(res.size(), (size_t)3); | |
| 343 | 343 | EXPECT_EQ(res[0], 10.0); |
| 344 | 344 | EXPECT_EQ(res[2], 12.0); |
| 345 | 345 | } | ... | ... |
tests/NewParseTest.cpp
| ... | ... | @@ -97,3 +97,133 @@ TEST_F(TApp, BuiltinComplexFail) { |
| 97 | 97 | |
| 98 | 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 | ... | ... |