Commit 494a65f8120145d5e923c8c62668d9e4d1f414d3
1 parent
c7dadfc5
Dropping Make syntax, moving to pointers from combiners, structured errors.
This mostly is cleanup, with fewer alternative methods and more standard syntax, avoiding the use of the namespace all the time. Validators are simpler and are added through `->check()`. Defaults are automatic, and can be specified with a final arg to the options. Expected arguments and required arguments are now accessed through a pointer to option. Option now can be checked as a bool to see if the argument was passed. Errors have better organisation.
Showing
11 changed files
with
181 additions
and
664 deletions
CMakeLists.txt
| ... | ... | @@ -20,14 +20,15 @@ add_compile_options(-pedantic -Wall -Wextra) |
| 20 | 20 | add_library(CLI INTERFACE) |
| 21 | 21 | target_include_directories(CLI INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") |
| 22 | 22 | |
| 23 | +file(GLOB CLI_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*") | |
| 24 | + | |
| 23 | 25 | # Single file test |
| 24 | 26 | option(CLI_SINGLE_FILE "Generate a single header file (and test)" ${CUR_PROJ}) |
| 25 | 27 | if(CLI_SINGLE_FILE) |
| 26 | 28 | file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") |
| 27 | 29 | add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" |
| 28 | 30 | COMMAND python "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" |
| 29 | - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" | |
| 30 | - IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" | |
| 31 | + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI_headers} | |
| 31 | 32 | ) |
| 32 | 33 | add_custom_target(generate_cli_single_file |
| 33 | 34 | DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") | ... | ... |
examples/try.cpp
| ... | ... | @@ -12,7 +12,7 @@ int main (int argc, char** argv) { |
| 12 | 12 | app.add_flag("-c,--count", count, "Counter"); |
| 13 | 13 | |
| 14 | 14 | double value = 3.14; |
| 15 | - app.add_option("-d,--double", value, "Some Value", CLI::Default); | |
| 15 | + app.add_option("-d,--double", value, "Some Value", false); | |
| 16 | 16 | |
| 17 | 17 | try { |
| 18 | 18 | app.run(argc, argv); | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -20,9 +20,7 @@ |
| 20 | 20 | #include "CLI/TypeTools.hpp" |
| 21 | 21 | #include "CLI/StringTools.hpp" |
| 22 | 22 | #include "CLI/Split.hpp" |
| 23 | -#include "CLI/Combiner.hpp" | |
| 24 | 23 | #include "CLI/Option.hpp" |
| 25 | -#include "CLI/Value.hpp" | |
| 26 | 24 | |
| 27 | 25 | namespace CLI { |
| 28 | 26 | |
| ... | ... | @@ -92,8 +90,6 @@ public: |
| 92 | 90 | } |
| 93 | 91 | |
| 94 | 92 | |
| 95 | - //------------ ADD STYLE ---------// | |
| 96 | - | |
| 97 | 93 | /// Add an option, will automatically understand the type for common types. |
| 98 | 94 | /** To use, create a variable with the expected type, and pass it in after the name. |
| 99 | 95 | * After start is called, you can use count to see if the value was passed, and |
| ... | ... | @@ -111,9 +107,9 @@ public: |
| 111 | 107 | std::string name, |
| 112 | 108 | callback_t callback, |
| 113 | 109 | std::string description="", |
| 114 | - detail::Combiner opts=Validators | |
| 110 | + bool defaulted=true | |
| 115 | 111 | ) { |
| 116 | - Option myopt{name, description, opts, callback}; | |
| 112 | + Option myopt{name, description, callback, defaulted}; | |
| 117 | 113 | if(std::find(std::begin(options), std::end(options), myopt) == std::end(options)) |
| 118 | 114 | options.push_back(myopt); |
| 119 | 115 | else |
| ... | ... | @@ -128,12 +124,10 @@ public: |
| 128 | 124 | std::string name, |
| 129 | 125 | T &variable, ///< The variable to set |
| 130 | 126 | std::string description="", |
| 131 | - detail::Combiner opts=Validators | |
| 127 | + bool defaulted=true | |
| 132 | 128 | ) { |
| 133 | 129 | |
| 134 | 130 | |
| 135 | - if(opts.num!=1) | |
| 136 | - throw IncorrectConstruction("Must have Args(1) or be a vector."); | |
| 137 | 131 | CLI::callback_t fun = [&variable](CLI::results_t res){ |
| 138 | 132 | if(res.size()!=1) { |
| 139 | 133 | return false; |
| ... | ... | @@ -144,9 +138,9 @@ public: |
| 144 | 138 | return detail::lexical_cast(res[0][0], variable); |
| 145 | 139 | }; |
| 146 | 140 | |
| 147 | - Option* retval = add_option(name, fun, description, opts); | |
| 141 | + Option* retval = add_option(name, fun, description, defaulted); | |
| 148 | 142 | retval->typeval = detail::type_name<T>(); |
| 149 | - if(opts.defaulted) { | |
| 143 | + if(defaulted) { | |
| 150 | 144 | std::stringstream out; |
| 151 | 145 | out << variable; |
| 152 | 146 | retval->defaultval = out.str(); |
| ... | ... | @@ -160,11 +154,9 @@ public: |
| 160 | 154 | std::string name, |
| 161 | 155 | std::vector<T> &variable, ///< The variable vector to set |
| 162 | 156 | std::string description="", |
| 163 | - detail::Combiner opts=Args | |
| 157 | + bool defaulted=true | |
| 164 | 158 | ) { |
| 165 | 159 | |
| 166 | - if(opts.num==0) | |
| 167 | - throw IncorrectConstruction("Must have Args or be a vector."); | |
| 168 | 160 | CLI::callback_t fun = [&variable](CLI::results_t res){ |
| 169 | 161 | bool retval = true; |
| 170 | 162 | variable.clear(); |
| ... | ... | @@ -176,27 +168,16 @@ public: |
| 176 | 168 | return variable.size() > 0 && retval; |
| 177 | 169 | }; |
| 178 | 170 | |
| 179 | - Option* retval = add_option(name, fun, description, opts); | |
| 171 | + Option* retval = add_option(name, fun, description, defaulted); | |
| 172 | + retval->allow_vector = true; | |
| 173 | + retval->_expected = -1; | |
| 180 | 174 | retval->typeval = detail::type_name<T>(); |
| 181 | - if(opts.defaulted) { | |
| 175 | + if(defaulted) | |
| 182 | 176 | retval->defaultval = "[" + detail::join(variable) + "]"; |
| 183 | - } | |
| 184 | 177 | return retval; |
| 185 | 178 | } |
| 186 | 179 | |
| 187 | 180 | |
| 188 | - /// Multiple options are supported | |
| 189 | - template<typename T, typename... Args> | |
| 190 | - Option* add_option( | |
| 191 | - std::string name, | |
| 192 | - T &variable, ///< The variable to set | |
| 193 | - std::string description, | |
| 194 | - detail::Combiner opts, | |
| 195 | - detail::Combiner opts2, | |
| 196 | - Args... args ///< More options | |
| 197 | - ) { | |
| 198 | - return add_option(name, variable, description, opts|opts2, args...); | |
| 199 | - } | |
| 200 | 181 | /// Add option for flag |
| 201 | 182 | Option* add_flag( |
| 202 | 183 | std::string name, |
| ... | ... | @@ -206,9 +187,10 @@ public: |
| 206 | 187 | return true; |
| 207 | 188 | }; |
| 208 | 189 | |
| 209 | - Option* opt = add_option(name, fun, description, Nothing); | |
| 210 | - if(opt->positional()) | |
| 190 | + Option* opt = add_option(name, fun, description, false); | |
| 191 | + if(opt->get_positional()) | |
| 211 | 192 | throw IncorrectConstruction("Flags cannot be positional"); |
| 193 | + opt->_expected = 0; | |
| 212 | 194 | return opt; |
| 213 | 195 | } |
| 214 | 196 | |
| ... | ... | @@ -227,9 +209,10 @@ public: |
| 227 | 209 | return true; |
| 228 | 210 | }; |
| 229 | 211 | |
| 230 | - Option* opt = add_option(name, fun, description, Nothing); | |
| 231 | - if(opt->positional()) | |
| 212 | + Option* opt = add_option(name, fun, description, false); | |
| 213 | + if(opt->get_positional()) | |
| 232 | 214 | throw IncorrectConstruction("Flags cannot be positional"); |
| 215 | + opt->_expected = 0; | |
| 233 | 216 | return opt; |
| 234 | 217 | } |
| 235 | 218 | |
| ... | ... | @@ -248,9 +231,10 @@ public: |
| 248 | 231 | return res.size() == 1; |
| 249 | 232 | }; |
| 250 | 233 | |
| 251 | - Option* opt = add_option(name, fun, description, Nothing); | |
| 252 | - if(opt->positional()) | |
| 234 | + Option* opt = add_option(name, fun, description, false); | |
| 235 | + if(opt->get_positional()) | |
| 253 | 236 | throw IncorrectConstruction("Flags cannot be positional"); |
| 237 | + opt->_expected = 0; | |
| 254 | 238 | return opt; |
| 255 | 239 | } |
| 256 | 240 | |
| ... | ... | @@ -262,12 +246,9 @@ public: |
| 262 | 246 | T &member, ///< The selected member of the set |
| 263 | 247 | std::set<T> options, ///< The set of posibilities |
| 264 | 248 | std::string description="", |
| 265 | - detail::Combiner opts=Validators | |
| 249 | + bool defaulted=true | |
| 266 | 250 | ) { |
| 267 | 251 | |
| 268 | - if(opts.num!=1) | |
| 269 | - throw IncorrectConstruction("Must have Args(1)."); | |
| 270 | - | |
| 271 | 252 | CLI::callback_t fun = [&member, options](CLI::results_t res){ |
| 272 | 253 | if(res.size()!=1) { |
| 273 | 254 | return false; |
| ... | ... | @@ -281,223 +262,16 @@ public: |
| 281 | 262 | return std::find(std::begin(options), std::end(options), member) != std::end(options); |
| 282 | 263 | }; |
| 283 | 264 | |
| 284 | - Option* retval = add_option(name, fun, description, opts); | |
| 265 | + Option* retval = add_option(name, fun, description, defaulted); | |
| 285 | 266 | retval->typeval = detail::type_name<T>(); |
| 286 | 267 | retval->typeval += " in {" + detail::join(options) + "}"; |
| 287 | - if(opts.defaulted) { | |
| 288 | - std::stringstream out; | |
| 289 | - out << member; | |
| 290 | - retval->defaultval = out.str(); | |
| 291 | - } | |
| 268 | + std::stringstream out; | |
| 269 | + out << member; | |
| 270 | + retval->defaultval = out.str(); | |
| 292 | 271 | return retval; |
| 293 | 272 | } |
| 294 | 273 | |
| 295 | 274 | |
| 296 | - template<typename T, typename... Args> | |
| 297 | - Option* add_set( | |
| 298 | - std::string name, | |
| 299 | - T &member, | |
| 300 | - std::set<T> options, ///< The set of posibilities | |
| 301 | - std::string description, | |
| 302 | - detail::Combiner opts, | |
| 303 | - detail::Combiner opts2, | |
| 304 | - Args... args | |
| 305 | - ) { | |
| 306 | - return add_set(name, member, options, description, opts|opts2, args...); | |
| 307 | - } | |
| 308 | - | |
| 309 | - | |
| 310 | - //------------ MAKE STYLE ---------// | |
| 311 | - | |
| 312 | - /// Prototype for new output style | |
| 313 | - template<typename T = std::string, | |
| 314 | - enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 315 | - Value<T> make_option( | |
| 316 | - std::string name, | |
| 317 | - std::string description="", | |
| 318 | - detail::Combiner opts=Validators | |
| 319 | - ) { | |
| 320 | - | |
| 321 | - if(opts.num!=1) | |
| 322 | - throw IncorrectConstruction("Must have Args(1)."); | |
| 323 | - | |
| 324 | - Value<T> out(name); | |
| 325 | - std::shared_ptr<std::unique_ptr<T>> ptr = out.value; | |
| 326 | - | |
| 327 | - CLI::callback_t fun = [ptr](CLI::results_t res){ | |
| 328 | - if(res.size()!=1) { | |
| 329 | - return false; | |
| 330 | - } | |
| 331 | - if(res[0].size()!=1) { | |
| 332 | - return false; | |
| 333 | - } | |
| 334 | - ptr->reset(new T()); // resets the internal ptr | |
| 335 | - return detail::lexical_cast(res[0][0], **ptr); | |
| 336 | - }; | |
| 337 | - Option* retval = add_option(name, fun, description, opts); | |
| 338 | - retval->typeval = detail::type_name<T>(); | |
| 339 | - return out; | |
| 340 | - } | |
| 341 | - | |
| 342 | - template<typename T = std::string, typename... Args> | |
| 343 | - Value<T> make_option( | |
| 344 | - std::string name, | |
| 345 | - std::string description, | |
| 346 | - detail::Combiner opts, | |
| 347 | - detail::Combiner opts2, | |
| 348 | - Args... args | |
| 349 | - ) { | |
| 350 | - return make_option(name, description, opts|opts2, args...); | |
| 351 | - } | |
| 352 | - | |
| 353 | - /// Prototype for new output style with default | |
| 354 | - template<typename T, | |
| 355 | - enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 356 | - Value<T> make_option( | |
| 357 | - std::string name, | |
| 358 | - const T& default_value, | |
| 359 | - std::string description="", | |
| 360 | - detail::Combiner opts=Validators | |
| 361 | - ) { | |
| 362 | - | |
| 363 | - if(opts.num!=1) | |
| 364 | - throw IncorrectConstruction("Must have Args(1)."); | |
| 365 | - | |
| 366 | - Value<T> out(name); | |
| 367 | - std::shared_ptr<std::unique_ptr<T>> ptr = out.value; | |
| 368 | - ptr->reset(new T(default_value)); // resets the internal ptr | |
| 369 | - | |
| 370 | - CLI::callback_t fun = [ptr](CLI::results_t res){ | |
| 371 | - if(res.size()!=1) { | |
| 372 | - return false; | |
| 373 | - } | |
| 374 | - if(res[0].size()!=1) { | |
| 375 | - return false; | |
| 376 | - } | |
| 377 | - ptr->reset(new T()); // resets the internal ptr | |
| 378 | - return detail::lexical_cast(res[0][0], **ptr); | |
| 379 | - }; | |
| 380 | - Option* retval = add_option(name, fun, description, opts); | |
| 381 | - retval->typeval = detail::type_name<T>(); | |
| 382 | - std::stringstream ot; | |
| 383 | - ot << default_value; | |
| 384 | - retval->defaultval = ot.str(); | |
| 385 | - return out; | |
| 386 | - } | |
| 387 | - | |
| 388 | - /// Prototype for new output style, vector | |
| 389 | - template<typename T, | |
| 390 | - enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
| 391 | - Value<T> make_option( | |
| 392 | - std::string name, | |
| 393 | - std::string description="", | |
| 394 | - detail::Combiner opts=Args | |
| 395 | - ) { | |
| 396 | - | |
| 397 | - if(opts.num==0) | |
| 398 | - throw IncorrectConstruction("Must have Args or be a vector."); | |
| 399 | - | |
| 400 | - Value<T> out(name); | |
| 401 | - std::shared_ptr<std::unique_ptr<T>> ptr = out.value; | |
| 402 | - | |
| 403 | - CLI::callback_t fun = [ptr](CLI::results_t res){ | |
| 404 | - ptr->reset(new T()); // resets the internal ptr | |
| 405 | - bool retval = true; | |
| 406 | - for(const auto &a : res) | |
| 407 | - for(const auto &b : a) { | |
| 408 | - (*ptr)->emplace_back(); | |
| 409 | - retval &= detail::lexical_cast(b, (*ptr)->back()); | |
| 410 | - } | |
| 411 | - return (*ptr)->size() > 0 && retval; | |
| 412 | - }; | |
| 413 | - Option* retval = add_option(name, fun, description, opts); | |
| 414 | - retval->typeval = detail::type_name<T>(); | |
| 415 | - return out; | |
| 416 | - } | |
| 417 | - | |
| 418 | - | |
| 419 | - template<typename T, typename... Args> | |
| 420 | - Value<T> make_option( | |
| 421 | - std::string name, | |
| 422 | - const T& default_value, | |
| 423 | - std::string description, | |
| 424 | - detail::Combiner opts, | |
| 425 | - detail::Combiner opts2, | |
| 426 | - Args... args | |
| 427 | - ) { | |
| 428 | - return make_option(name, default_value, description, opts|opts2, args...); | |
| 429 | - } | |
| 430 | - | |
| 431 | - /// Prototype for new output style: flag | |
| 432 | - Value<int> make_flag( | |
| 433 | - std::string name, | |
| 434 | - std::string description="" | |
| 435 | - ) { | |
| 436 | - | |
| 437 | - Value<int> out(name); | |
| 438 | - std::shared_ptr<std::unique_ptr<int>> ptr = out.value; | |
| 439 | - ptr->reset(new int()); // resets the internal ptr | |
| 440 | - **ptr = 0; | |
| 441 | - | |
| 442 | - CLI::callback_t fun = [ptr](CLI::results_t res){ | |
| 443 | - **ptr = (int) res.size(); | |
| 444 | - return true; | |
| 445 | - }; | |
| 446 | - | |
| 447 | - Option* opt = add_option(name, fun, description, Nothing); | |
| 448 | - if(opt->positional()) | |
| 449 | - throw IncorrectConstruction("Flags cannot be positional"); | |
| 450 | - return out; | |
| 451 | - } | |
| 452 | - | |
| 453 | - /// Add set of options | |
| 454 | - template<typename T> | |
| 455 | - Value<T> make_set( | |
| 456 | - std::string name, | |
| 457 | - std::set<T> options, ///< The set of posibilities | |
| 458 | - std::string description="", | |
| 459 | - detail::Combiner opts=Validators | |
| 460 | - ) { | |
| 461 | - | |
| 462 | - Value<T> out(name); | |
| 463 | - std::shared_ptr<std::unique_ptr<T>> ptr = out.value; | |
| 464 | - | |
| 465 | - if(opts.num!=1) | |
| 466 | - throw IncorrectConstruction("Must have Args(1)."); | |
| 467 | - | |
| 468 | - CLI::callback_t fun = [ptr, options](CLI::results_t res){ | |
| 469 | - if(res.size()!=1) { | |
| 470 | - return false; | |
| 471 | - } | |
| 472 | - if(res[0].size()!=1) { | |
| 473 | - return false; | |
| 474 | - } | |
| 475 | - ptr->reset(new T()); | |
| 476 | - bool retval = detail::lexical_cast(res[0][0], **ptr); | |
| 477 | - if(!retval) | |
| 478 | - return false; | |
| 479 | - return std::find(std::begin(options), std::end(options), **ptr) != std::end(options); | |
| 480 | - }; | |
| 481 | - | |
| 482 | - Option* retval = add_option(name, fun, description, opts); | |
| 483 | - retval->typeval = detail::type_name<T>(); | |
| 484 | - retval->typeval += " in {" + detail::join(options) + "}"; | |
| 485 | - return out; | |
| 486 | - } | |
| 487 | - | |
| 488 | - | |
| 489 | - template<typename T, typename... Args> | |
| 490 | - Value<T> make_set( | |
| 491 | - std::string name, | |
| 492 | - std::set<T> options, | |
| 493 | - std::string description, | |
| 494 | - detail::Combiner opts, | |
| 495 | - detail::Combiner opts2, | |
| 496 | - Args... args | |
| 497 | - ) { | |
| 498 | - return make_set(name, options, description, opts|opts2, args...); | |
| 499 | - } | |
| 500 | - | |
| 501 | 275 | /// This allows subclasses to inject code before callbacks but after parse |
| 502 | 276 | virtual void pre_callback() {} |
| 503 | 277 | |
| ... | ... | @@ -510,6 +284,7 @@ public: |
| 510 | 284 | parse(args); |
| 511 | 285 | } |
| 512 | 286 | |
| 287 | + /// The real work is done here. Expects a reversed vector | |
| 513 | 288 | void parse(std::vector<std::string> & args) { |
| 514 | 289 | parsed = true; |
| 515 | 290 | |
| ... | ... | @@ -546,16 +321,16 @@ public: |
| 546 | 321 | |
| 547 | 322 | |
| 548 | 323 | for(Option& opt : options) { |
| 549 | - while (opt.positional() && opt.count() < opt.expected() && positionals.size() > 0) { | |
| 324 | + while (opt.get_positional() && opt.count() < opt.get_expected() && positionals.size() > 0) { | |
| 550 | 325 | opt.get_new(); |
| 551 | 326 | opt.add_result(0, positionals.front()); |
| 552 | 327 | positionals.pop_front(); |
| 553 | 328 | } |
| 554 | - if (opt.required() && opt.count() < opt.expected()) | |
| 329 | + if (opt.get_required() && opt.count() < opt.get_expected()) | |
| 555 | 330 | throw RequiredError(opt.get_name()); |
| 556 | 331 | if (opt.count() > 0) { |
| 557 | 332 | if(!opt.run_callback()) |
| 558 | - throw ParseError(opt.get_name()); | |
| 333 | + throw ConversionError(opt.get_name()); | |
| 559 | 334 | } |
| 560 | 335 | |
| 561 | 336 | } |
| ... | ... | @@ -595,7 +370,7 @@ public: |
| 595 | 370 | } |
| 596 | 371 | |
| 597 | 372 | int vnum = op->get_new(); |
| 598 | - int num = op->expected(); | |
| 373 | + int num = op->get_expected(); | |
| 599 | 374 | |
| 600 | 375 | if(num == 0) |
| 601 | 376 | op->add_result(vnum, ""); |
| ... | ... | @@ -660,7 +435,7 @@ public: |
| 660 | 435 | |
| 661 | 436 | |
| 662 | 437 | int vnum = op->get_new(); |
| 663 | - int num = op->expected(); | |
| 438 | + int num = op->get_expected(); | |
| 664 | 439 | |
| 665 | 440 | |
| 666 | 441 | if(value != "") { |
| ... | ... | @@ -742,7 +517,7 @@ public: |
| 742 | 517 | // Positionals |
| 743 | 518 | bool pos=false; |
| 744 | 519 | for(const Option &opt : options) |
| 745 | - if(opt.positional()) { | |
| 520 | + if(opt.get_positional()) { | |
| 746 | 521 | out << " " << opt.help_positional(); |
| 747 | 522 | if(opt.has_description()) |
| 748 | 523 | pos=true; |
| ... | ... | @@ -754,7 +529,7 @@ public: |
| 754 | 529 | if(pos) { |
| 755 | 530 | out << "Positionals:" << std::endl; |
| 756 | 531 | for(const Option &opt : options) |
| 757 | - if(opt.positional() && opt.has_description()) | |
| 532 | + if(opt.get_positional() && opt.has_description()) | |
| 758 | 533 | detail::format_help(out, opt.get_pname(), opt.get_description(), wid); |
| 759 | 534 | out << std::endl; |
| 760 | 535 | ... | ... |
include/CLI/CLI.hpp
include/CLI/Error.hpp
| ... | ... | @@ -11,54 +11,74 @@ namespace CLI { |
| 11 | 11 | |
| 12 | 12 | // Error definitions |
| 13 | 13 | |
| 14 | - | |
| 14 | +/// All errors derive from this one | |
| 15 | 15 | struct Error : public std::runtime_error { |
| 16 | 16 | int exit_code; |
| 17 | 17 | bool print_help; |
| 18 | 18 | Error(std::string parent, std::string name, int exit_code=255, bool print_help=true) : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {} |
| 19 | 19 | }; |
| 20 | 20 | |
| 21 | +/// This is a successful completion on parsing, supposed to exit | |
| 21 | 22 | struct Success : public Error { |
| 22 | 23 | Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {} |
| 23 | 24 | }; |
| 24 | 25 | |
| 26 | +/// -h or --help on command line | |
| 25 | 27 | struct CallForHelp : public Error { |
| 26 | 28 | CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 0) {} |
| 27 | 29 | }; |
| 28 | 30 | |
| 29 | -struct BadNameString : public Error { | |
| 30 | - BadNameString(std::string name) : Error("BadNameString", name, 1) {} | |
| 31 | +// Construction errors (not in parsing) | |
| 32 | + | |
| 33 | +struct ConstructionError : public Error { | |
| 34 | + using Error::Error; | |
| 31 | 35 | }; |
| 32 | 36 | |
| 37 | +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) | |
| 38 | +struct IncorrectConstruction : public ConstructionError { | |
| 39 | + IncorrectConstruction(std::string name) : ConstructionError("ConstructionError", name, 8) {} | |
| 40 | +}; | |
| 33 | 41 | |
| 34 | -struct ParseError : public Error { | |
| 35 | - ParseError(std::string name) : Error("ParseError", name, 2) {} | |
| 42 | +/// Thrown on construction of a bad name | |
| 43 | +struct BadNameString : public ConstructionError { | |
| 44 | + BadNameString(std::string name) : ConstructionError("BadNameString", name, 1) {} | |
| 36 | 45 | }; |
| 37 | 46 | |
| 38 | -struct OptionAlreadyAdded : public Error { | |
| 39 | - OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {} | |
| 47 | +/// Thrown when an option already exists | |
| 48 | +struct OptionAlreadyAdded : public ConstructionError { | |
| 49 | + OptionAlreadyAdded(std::string name) : ConstructionError("OptionAlreadyAdded", name, 3) {} | |
| 40 | 50 | }; |
| 41 | 51 | |
| 42 | -struct OptionNotFound : public Error { | |
| 43 | - OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} | |
| 52 | +// Parsing errors | |
| 53 | + | |
| 54 | +struct ParseError : public Error { | |
| 55 | + using Error::Error; | |
| 44 | 56 | }; |
| 45 | 57 | |
| 46 | -struct RequiredError : public Error { | |
| 47 | - RequiredError(std::string name) : Error("RequiredError", name, 5) {} | |
| 58 | +/// Thrown when conversion call back fails, such as when an int fails to coerse to a string | |
| 59 | +struct ConversionError : public ParseError { | |
| 60 | + ConversionError(std::string name) : ParseError("ConversionError", name, 2) {} | |
| 48 | 61 | }; |
| 49 | 62 | |
| 50 | -struct PositionalError : public Error { | |
| 51 | - PositionalError(std::string name) : Error("PositionalError", name, 6) {} | |
| 63 | +/// Thrown when a required option is missing | |
| 64 | +struct RequiredError : public ParseError { | |
| 65 | + RequiredError(std::string name) : ParseError("RequiredError", name, 5) {} | |
| 52 | 66 | }; |
| 53 | 67 | |
| 54 | -struct HorribleError : public Error { | |
| 55 | - HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {} | |
| 68 | +/// Thrown when too many positionals are found | |
| 69 | +struct PositionalError : public ParseError { | |
| 70 | + PositionalError(std::string name) : ParseError("PositionalError", name, 6) {} | |
| 56 | 71 | }; |
| 57 | -struct IncorrectConstruction : public Error { | |
| 58 | - IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {} | |
| 72 | + | |
| 73 | +/// This is just a safety check to verify selection and parsing match | |
| 74 | +struct HorribleError : public ParseError { | |
| 75 | + HorribleError(std::string name) : ParseError("HorribleError", "(You should never see this error) " + name, 7) {} | |
| 59 | 76 | }; |
| 60 | -struct EmptyError : public Error { | |
| 61 | - EmptyError(std::string name) : Error("EmptyError", name, 9) {} | |
| 77 | + | |
| 78 | +/// Thrown when counting a non-existent option | |
| 79 | +struct OptionNotFound : public Error { | |
| 80 | + OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {} | |
| 62 | 81 | }; |
| 63 | 82 | |
| 83 | + | |
| 64 | 84 | } | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -9,16 +9,15 @@ |
| 9 | 9 | #include <tuple> |
| 10 | 10 | #include <algorithm> |
| 11 | 11 | |
| 12 | +#include "CLI/Error.hpp" | |
| 12 | 13 | #include "CLI/StringTools.hpp" |
| 13 | 14 | #include "CLI/Split.hpp" |
| 14 | -#include "CLI/Combiner.hpp" | |
| 15 | 15 | |
| 16 | 16 | namespace CLI { |
| 17 | 17 | |
| 18 | 18 | typedef std::vector<std::vector<std::string>> results_t; |
| 19 | 19 | typedef std::function<bool(results_t)> callback_t; |
| 20 | 20 | |
| 21 | - | |
| 22 | 21 | class App; |
| 23 | 22 | |
| 24 | 23 | class Option { |
| ... | ... | @@ -29,7 +28,6 @@ protected: |
| 29 | 28 | std::vector<std::string> lnames; |
| 30 | 29 | std::string pname; |
| 31 | 30 | |
| 32 | - detail::Combiner opts; | |
| 33 | 31 | std::string description; |
| 34 | 32 | callback_t callback; |
| 35 | 33 | |
| ... | ... | @@ -37,33 +35,66 @@ protected: |
| 37 | 35 | std::string defaultval; |
| 38 | 36 | std::string typeval; |
| 39 | 37 | |
| 38 | + | |
| 39 | + bool _default {false}; | |
| 40 | + bool _required {false}; | |
| 41 | + int _expected {1}; | |
| 42 | + bool allow_vector {false}; | |
| 43 | + std::vector<std::function<bool(std::string)>> _validators; | |
| 44 | + | |
| 40 | 45 | // Results |
| 41 | 46 | results_t results {}; |
| 42 | 47 | |
| 43 | 48 | |
| 44 | 49 | public: |
| 45 | - Option(std::string name, std::string description = "", detail::Combiner opts=Nothing, std::function<bool(results_t)> callback=[](results_t){return true;}) : | |
| 46 | - opts(opts), description(description), callback(callback){ | |
| 50 | + Option(std::string name, std::string description = "", std::function<bool(results_t)> callback=[](results_t){return true;}, bool _default=true) : | |
| 51 | + description(description), callback(callback), _default(_default) { | |
| 47 | 52 | std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name)); |
| 48 | 53 | } |
| 49 | 54 | |
| 55 | + | |
| 56 | + // This class is "true" if optio passed. | |
| 57 | + operator bool() const { | |
| 58 | + return results.size() > 0; | |
| 59 | + } | |
| 60 | + | |
| 50 | 61 | /// Clear the parsed results (mostly for testing) |
| 51 | 62 | void clear() { |
| 52 | 63 | results.clear(); |
| 53 | 64 | } |
| 54 | 65 | |
| 55 | - /// True if option is required | |
| 56 | - bool required() const { | |
| 57 | - return opts.required; | |
| 66 | + /// Set the option as required | |
| 67 | + Option* required(bool value = true) { | |
| 68 | + _required = value; | |
| 69 | + return this; | |
| 70 | + } | |
| 71 | + | |
| 72 | + bool get_required() const { | |
| 73 | + return _required; | |
| 74 | + } | |
| 75 | + | |
| 76 | + /// Set the number of expected arguments (Flags bypass this) | |
| 77 | + Option* expected(int value) { | |
| 78 | + if(value == 0) | |
| 79 | + throw IncorrectConstruction("Cannot set 0 expected, use a flag instead"); | |
| 80 | + if(!allow_vector && value != 1) | |
| 81 | + throw IncorrectConstruction("You can only change the Expected arguments for vectors"); | |
| 82 | + _expected = value; | |
| 83 | + return this; | |
| 58 | 84 | } |
| 59 | 85 | |
| 60 | 86 | /// The number of arguments the option expects |
| 61 | - int expected() const { | |
| 62 | - return opts.num; | |
| 87 | + int get_expected() const { | |
| 88 | + return _expected; | |
| 89 | + } | |
| 90 | + | |
| 91 | + /// True if this has a default value | |
| 92 | + int get_default() const { | |
| 93 | + return _default; | |
| 63 | 94 | } |
| 64 | 95 | |
| 65 | 96 | /// True if the argument can be given directly |
| 66 | - bool positional() const { | |
| 97 | + bool get_positional() const { | |
| 67 | 98 | return pname.length() > 0; |
| 68 | 99 | } |
| 69 | 100 | |
| ... | ... | @@ -72,16 +103,18 @@ public: |
| 72 | 103 | return (snames.size() + lnames.size()) > 0; |
| 73 | 104 | } |
| 74 | 105 | |
| 75 | - /// True if this should print the default string | |
| 76 | - bool defaulted() const { | |
| 77 | - return opts.defaulted; | |
| 78 | - } | |
| 79 | - | |
| 80 | 106 | /// True if option has description |
| 81 | 107 | bool has_description() const { |
| 82 | 108 | return description.length() > 0; |
| 83 | 109 | } |
| 84 | 110 | |
| 111 | + /// Adds a validator | |
| 112 | + Option* check(std::function<bool(std::string)> validator) { | |
| 113 | + | |
| 114 | + _validators.push_back(validator); | |
| 115 | + return this; | |
| 116 | + } | |
| 117 | + | |
| 85 | 118 | /// Get the description |
| 86 | 119 | const std::string& get_description() const { |
| 87 | 120 | return description; |
| ... | ... | @@ -90,11 +123,11 @@ public: |
| 90 | 123 | /// The name and any extras needed for positionals |
| 91 | 124 | std::string help_positional() const { |
| 92 | 125 | std::string out = pname; |
| 93 | - if(expected()<1) | |
| 94 | - out = out + "x" + std::to_string(expected()); | |
| 95 | - else if(expected()==-1) | |
| 126 | + if(get_expected()<1) | |
| 127 | + out = out + "x" + std::to_string(get_expected()); | |
| 128 | + else if(get_expected()==-1) | |
| 96 | 129 | out = out + "..."; |
| 97 | - out = required() ? out : "["+out+"]"; | |
| 130 | + out = get_required() ? out : "["+out+"]"; | |
| 98 | 131 | return out; |
| 99 | 132 | } |
| 100 | 133 | |
| ... | ... | @@ -105,9 +138,9 @@ public: |
| 105 | 138 | |
| 106 | 139 | /// Process the callback |
| 107 | 140 | bool run_callback() const { |
| 108 | - if(opts.validators.size()>0) { | |
| 141 | + if(_validators.size()>0) { | |
| 109 | 142 | for(const std::string & result : flatten_results()) |
| 110 | - for(const std::function<bool(std::string)> &vali : opts.validators) | |
| 143 | + for(const std::function<bool(std::string)> &vali : _validators) | |
| 111 | 144 | if(!vali(result)) |
| 112 | 145 | return false; |
| 113 | 146 | } |
| ... | ... | @@ -196,14 +229,14 @@ public: |
| 196 | 229 | std::string help_name() const { |
| 197 | 230 | std::stringstream out; |
| 198 | 231 | out << get_name(); |
| 199 | - if(expected() != 0) { | |
| 232 | + if(get_expected() != 0) { | |
| 200 | 233 | if(typeval != "") |
| 201 | 234 | out << " " << typeval; |
| 202 | 235 | if(defaultval != "") |
| 203 | 236 | out << "=" << defaultval; |
| 204 | - if(expected() > 1) | |
| 205 | - out << " x " << expected(); | |
| 206 | - if(expected() == -1) | |
| 237 | + if(get_expected() > 1) | |
| 238 | + out << " x " << get_expected(); | |
| 239 | + if(get_expected() == -1) | |
| 207 | 240 | out << " ..."; |
| 208 | 241 | } |
| 209 | 242 | return out.str(); | ... | ... |
include/CLI/Combiner.hpp renamed to include/CLI/Validators.hpp
| ... | ... | @@ -4,8 +4,6 @@ |
| 4 | 4 | // file LICENSE or https://github.com/henryiii/CLI11 for details. |
| 5 | 5 | |
| 6 | 6 | #include <string> |
| 7 | -#include <functional> | |
| 8 | -#include <vector> | |
| 9 | 7 | |
| 10 | 8 | |
| 11 | 9 | // C standard library |
| ... | ... | @@ -16,42 +14,9 @@ |
| 16 | 14 | |
| 17 | 15 | namespace CLI { |
| 18 | 16 | |
| 19 | -namespace detail { | |
| 20 | - | |
| 21 | -struct Combiner { | |
| 22 | - int num; | |
| 23 | - bool required; | |
| 24 | - bool defaulted; | |
| 25 | - std::vector<std::function<bool(std::string)>> validators; | |
| 26 | - | |
| 27 | - /// Can be or-ed together | |
| 28 | - Combiner operator | (Combiner b) const { | |
| 29 | - Combiner self; | |
| 30 | - self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num); | |
| 31 | - self.required = required || b.required; | |
| 32 | - self.defaulted = defaulted || b.defaulted; | |
| 33 | - self.validators.reserve(validators.size() + b.validators.size()); | |
| 34 | - self.validators.insert(self.validators.end(), validators.begin(), validators.end()); | |
| 35 | - self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end()); | |
| 36 | - return self; | |
| 37 | - } | |
| 38 | - | |
| 39 | - /// Call to give the number of arguments expected on cli | |
| 40 | - Combiner operator() (int n) const { | |
| 41 | - Combiner self = *this; | |
| 42 | - self.num = n; | |
| 43 | - return self; | |
| 44 | - } | |
| 45 | - /// Call to give a validator | |
| 46 | - Combiner operator() (std::function<bool(std::string)> func) const { | |
| 47 | - Combiner self = *this; | |
| 48 | - self.validators.push_back(func); | |
| 49 | - return self; | |
| 50 | - } | |
| 51 | -}; | |
| 52 | 17 | |
| 53 | 18 | /// Check for an existing file |
| 54 | -bool _ExistingFile(std::string filename) { | |
| 19 | +bool ExistingFile(std::string filename) { | |
| 55 | 20 | // std::fstream f(name.c_str()); |
| 56 | 21 | // return f.good(); |
| 57 | 22 | // Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c |
| ... | ... | @@ -60,7 +25,7 @@ bool _ExistingFile(std::string filename) { |
| 60 | 25 | } |
| 61 | 26 | |
| 62 | 27 | /// Check for an existing directory |
| 63 | -bool _ExistingDirectory(std::string filename) { | |
| 28 | +bool ExistingDirectory(std::string filename) { | |
| 64 | 29 | struct stat buffer; |
| 65 | 30 | if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) ) |
| 66 | 31 | return true; |
| ... | ... | @@ -68,32 +33,10 @@ bool _ExistingDirectory(std::string filename) { |
| 68 | 33 | } |
| 69 | 34 | |
| 70 | 35 | /// Check for a non-existing path |
| 71 | -bool _NonexistentPath(std::string filename) { | |
| 36 | +bool NonexistentPath(std::string filename) { | |
| 72 | 37 | struct stat buffer; |
| 73 | 38 | return stat(filename.c_str(), &buffer) != 0; |
| 74 | 39 | } |
| 75 | 40 | |
| 76 | 41 | |
| 77 | - | |
| 78 | - | |
| 79 | -} | |
| 80 | - | |
| 81 | - | |
| 82 | - | |
| 83 | -// Defines for common Combiners (don't use combiners directly) | |
| 84 | - | |
| 85 | -const detail::Combiner Nothing {0, false, false, {}}; | |
| 86 | -const detail::Combiner Required {1, true, false, {}}; | |
| 87 | -const detail::Combiner Default {1, false, true, {}}; | |
| 88 | -const detail::Combiner Args {-1, false, false, {}}; | |
| 89 | -const detail::Combiner Validators {1, false, false, {}}; | |
| 90 | - | |
| 91 | -// Warning about using these validators: | |
| 92 | -// The files could be added/deleted after the validation. This is not common, | |
| 93 | -// but if this is a possibility, check the file you open afterwards | |
| 94 | -const detail::Combiner ExistingFile {1, false, false, {detail::_ExistingFile}}; | |
| 95 | -const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}}; | |
| 96 | -const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}}; | |
| 97 | - | |
| 98 | - | |
| 99 | 42 | } | ... | ... |
include/CLI/Value.hpp deleted
| 1 | -#pragma once | |
| 2 | - | |
| 3 | -// Distributed under the LGPL version 3.0 license. See accompanying | |
| 4 | -// file LICENSE or https://github.com/henryiii/CLI11 for details. | |
| 5 | - | |
| 6 | -#include <string> | |
| 7 | -#include <memory> | |
| 8 | - | |
| 9 | -#include "CLI/Error.hpp" | |
| 10 | - | |
| 11 | -namespace CLI { | |
| 12 | - | |
| 13 | -class App; | |
| 14 | - | |
| 15 | -// Prototype return value test | |
| 16 | -template <typename T> | |
| 17 | -class Value { | |
| 18 | - friend App; | |
| 19 | -protected: | |
| 20 | - std::shared_ptr<std::unique_ptr<T>> value {new std::unique_ptr<T>()}; | |
| 21 | - std::string name; | |
| 22 | -public: | |
| 23 | - Value(std::string name) : name(name) {} | |
| 24 | - | |
| 25 | - operator bool() const {return (bool) *value;} | |
| 26 | - | |
| 27 | - T& get() const { | |
| 28 | - if(*value) | |
| 29 | - return **value; | |
| 30 | - else | |
| 31 | - throw EmptyError(name); | |
| 32 | - } | |
| 33 | - /// Note this does not throw on assignment, though | |
| 34 | - /// afterwards it seems to work fine. Best to use | |
| 35 | - /// explicit * notation. | |
| 36 | - T& operator *() const { | |
| 37 | - return get(); | |
| 38 | - } | |
| 39 | -}; | |
| 40 | - | |
| 41 | -} |
include/Program.hpp deleted
| 1 | -#pragma once | |
| 2 | - | |
| 3 | -#include <string> | |
| 4 | - | |
| 5 | -#include <boost/program_options.hpp> | |
| 6 | - | |
| 7 | - | |
| 8 | -// This is unreachable outside this file; you should not use Combiner directly | |
| 9 | -namespace { | |
| 10 | - | |
| 11 | -struct Combiner { | |
| 12 | - int positional; | |
| 13 | - bool required; | |
| 14 | - bool defaulted; | |
| 15 | - | |
| 16 | - /// Can be or-ed together | |
| 17 | - Combiner operator | (Combiner b) const { | |
| 18 | - Combiner self; | |
| 19 | - self.positional = positional + b.positional; | |
| 20 | - self.required = required || b.required; | |
| 21 | - self.defaulted = defaulted || b.defaulted; | |
| 22 | - return self; | |
| 23 | - } | |
| 24 | - | |
| 25 | - /// Call to give the number of arguments expected on cli | |
| 26 | - Combiner operator() (int n) const { | |
| 27 | - return Combiner{n, required, defaulted}; | |
| 28 | - } | |
| 29 | - Combiner operator, (Combiner b) const { | |
| 30 | - return *this | b; | |
| 31 | - } | |
| 32 | -}; | |
| 33 | -} | |
| 34 | - | |
| 35 | - | |
| 36 | - | |
| 37 | -/// Creates a command line program, with very few defaults. | |
| 38 | -/** To use, create a new Program() instance with argc, argv, and a help description. The templated | |
| 39 | -* add_option methods make it easy to prepare options. Remember to call `.start` before starting your | |
| 40 | -* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ | |
| 41 | -class Program { | |
| 42 | -public: | |
| 43 | - static constexpr Combiner REQUIRED{0,true,false}; | |
| 44 | - static constexpr Combiner DEFAULT{0,false,true}; | |
| 45 | - static constexpr Combiner POSITIONAL{1,false,false}; | |
| 46 | - | |
| 47 | -protected: | |
| 48 | - boost::program_options::options_description desc; | |
| 49 | - boost::program_options::positional_options_description p; | |
| 50 | - boost::program_options::variables_map vm; | |
| 51 | - | |
| 52 | - int argc; | |
| 53 | - char **argv; | |
| 54 | - | |
| 55 | - /// Parses the command line (internal function) | |
| 56 | - void parse() { | |
| 57 | - try { | |
| 58 | - boost::program_options::store(boost::program_options::command_line_parser(argc, argv) | |
| 59 | - .options(desc).positional(p).run(), vm); | |
| 60 | - | |
| 61 | - if(vm.count("help")){ | |
| 62 | - std::cout << desc; | |
| 63 | - exit(0); | |
| 64 | - } | |
| 65 | - | |
| 66 | - boost::program_options::notify(vm); | |
| 67 | - } catch(const boost::program_options::error& e) { | |
| 68 | - std::cerr << "ERROR: " << e.what() << std::endl << std::endl; | |
| 69 | - std::cerr << desc << std::endl; | |
| 70 | - exit(1); | |
| 71 | - } | |
| 72 | - } | |
| 73 | - | |
| 74 | - | |
| 75 | -public: | |
| 76 | - | |
| 77 | - /// Create a new program. Pass in the same arguments as main(), along with a help string. | |
| 78 | - Program(int argc, char** argv, std::string discription) | |
| 79 | - : argc(argc), argv(argv), desc(discription) { | |
| 80 | - desc.add_options() | |
| 81 | - ("help,h", "Display this help message"); | |
| 82 | - } | |
| 83 | - | |
| 84 | - /// Allows you to manually add options in the boost style. | |
| 85 | - /** Usually the specialized methods are easier, but this remains for people used to Boost and for | |
| 86 | - * unusual situations. */ | |
| 87 | - boost::program_options::options_description_easy_init add_options() { | |
| 88 | - return desc.add_options(); | |
| 89 | - } | |
| 90 | - | |
| 91 | - /// Add an option, will automatically understand the type for common types. | |
| 92 | - /** To use, create a variable with the expected type, and pass it in after the name. | |
| 93 | - * After start is called, you can use count to see if the value was passed, and | |
| 94 | - * the value will be initialized properly. | |
| 95 | - * | |
| 96 | - * Program::REQUIRED, Program::DEFAULT, and Program::POSITIONAL are options, and can be `|` | |
| 97 | - * together. The positional options take an optional number of arguments. | |
| 98 | - * | |
| 99 | - * For example, | |
| 100 | - * | |
| 101 | - * std::string filename | |
| 102 | - * program.add_option("filename", filename, "description of filename"); | |
| 103 | - */ | |
| 104 | - template<typename T> | |
| 105 | - void add_option( | |
| 106 | - std::string name, ///< The name, long,short | |
| 107 | - T &value, ///< The value | |
| 108 | - std::string description, ///< Discription string | |
| 109 | - Combiner options ///< The options (REQUIRED, DEFAULT, POSITIONAL) | |
| 110 | - ) { | |
| 111 | - auto po_value = boost::program_options::value<T>(&value); | |
| 112 | - if(options.defaulted) | |
| 113 | - po_value = po_value->default_value(value); | |
| 114 | - if(options.required) | |
| 115 | - po_value = po_value->required(); | |
| 116 | - desc.add_options()(name.c_str(),po_value,description.c_str()); | |
| 117 | - if(options.positional!=0) | |
| 118 | - p.add(name.c_str(), options.positional); | |
| 119 | - } | |
| 120 | - | |
| 121 | - /// Adds a flag style option | |
| 122 | - void add_option(std::string name, std::string description) { | |
| 123 | - desc.add_options()(name.c_str(),description.c_str()); | |
| 124 | - } | |
| 125 | - | |
| 126 | - | |
| 127 | - /// This must be called after the options are in but before the rest of the program. | |
| 128 | - /** Calls the Boost boost::program_options initialization, causing the program to exit | |
| 129 | - * if -h or an invalid option is passed. */ | |
| 130 | - void start() { | |
| 131 | - parse(); | |
| 132 | - } | |
| 133 | - | |
| 134 | - /// Counts the number of times the given option was passed. | |
| 135 | - int count(std::string name) const { | |
| 136 | - return vm.count(name.c_str()); | |
| 137 | - } | |
| 138 | - | |
| 139 | - | |
| 140 | -}; |
tests/CLITest.cpp
| ... | ... | @@ -165,7 +165,7 @@ TEST_F(TApp, BoolAndIntFlags) { |
| 165 | 165 | app.reset(); |
| 166 | 166 | |
| 167 | 167 | args = {"-b", "-b"}; |
| 168 | - EXPECT_THROW(run(), CLI::ParseError); | |
| 168 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 169 | 169 | |
| 170 | 170 | app.reset(); |
| 171 | 171 | bflag = false; |
| ... | ... | @@ -199,8 +199,8 @@ TEST_F(TApp, Flags) { |
| 199 | 199 | int i = 3; |
| 200 | 200 | std::string s = "HI"; |
| 201 | 201 | |
| 202 | - app.add_option("-i,i", i, "", CLI::Default); | |
| 203 | - app.add_option("-s,s", s, "", CLI::Default); | |
| 202 | + app.add_option("-i,i", i, "", false); | |
| 203 | + app.add_option("-s,s", s, "", true); | |
| 204 | 204 | |
| 205 | 205 | args = {"-i2", "9"}; |
| 206 | 206 | |
| ... | ... | @@ -276,10 +276,10 @@ TEST_F(TApp, Reset) { |
| 276 | 276 | |
| 277 | 277 | TEST_F(TApp, FileNotExists) { |
| 278 | 278 | std::string myfile{"TestNonFileNotUsed.txt"}; |
| 279 | - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile)); | |
| 279 | + EXPECT_TRUE(CLI::NonexistentPath(myfile)); | |
| 280 | 280 | |
| 281 | 281 | std::string filename; |
| 282 | - app.add_option("--file", filename, "", CLI::NonexistentPath); | |
| 282 | + app.add_option("--file", filename)->check(CLI::NonexistentPath); | |
| 283 | 283 | args = {"--file", myfile}; |
| 284 | 284 | |
| 285 | 285 | EXPECT_NO_THROW(run()); |
| ... | ... | @@ -290,21 +290,21 @@ TEST_F(TApp, FileNotExists) { |
| 290 | 290 | |
| 291 | 291 | bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file |
| 292 | 292 | EXPECT_TRUE(ok); |
| 293 | - EXPECT_THROW(run(), CLI::ParseError); | |
| 293 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 294 | 294 | |
| 295 | 295 | std::remove(myfile.c_str()); |
| 296 | - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); | |
| 296 | + EXPECT_FALSE(CLI::ExistingFile(myfile)); | |
| 297 | 297 | } |
| 298 | 298 | |
| 299 | 299 | TEST_F(TApp, FileExists) { |
| 300 | 300 | std::string myfile{"TestNonFileNotUsed.txt"}; |
| 301 | - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); | |
| 301 | + EXPECT_FALSE(CLI::ExistingFile(myfile)); | |
| 302 | 302 | |
| 303 | 303 | std::string filename = "Failed"; |
| 304 | - app.add_option("--file", filename, "", CLI::ExistingFile); | |
| 304 | + app.add_option("--file", filename)->check(CLI::ExistingFile); | |
| 305 | 305 | args = {"--file", myfile}; |
| 306 | 306 | |
| 307 | - EXPECT_THROW(run(), CLI::ParseError); | |
| 307 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 308 | 308 | EXPECT_EQ("Failed", filename); |
| 309 | 309 | |
| 310 | 310 | app.reset(); |
| ... | ... | @@ -315,7 +315,7 @@ TEST_F(TApp, FileExists) { |
| 315 | 315 | EXPECT_EQ(myfile, filename); |
| 316 | 316 | |
| 317 | 317 | std::remove(myfile.c_str()); |
| 318 | - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); | |
| 318 | + EXPECT_FALSE(CLI::ExistingFile(myfile)); | |
| 319 | 319 | } |
| 320 | 320 | |
| 321 | 321 | TEST_F(TApp, InSet) { |
| ... | ... | @@ -331,15 +331,15 @@ TEST_F(TApp, InSet) { |
| 331 | 331 | app.reset(); |
| 332 | 332 | |
| 333 | 333 | args = {"--quick", "four"}; |
| 334 | - EXPECT_THROW(run(), CLI::ParseError); | |
| 334 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 335 | 335 | } |
| 336 | 336 | |
| 337 | 337 | TEST_F(TApp, VectorFixedString) { |
| 338 | 338 | std::vector<std::string> strvec; |
| 339 | 339 | std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; |
| 340 | 340 | |
| 341 | - CLI::Option* opt = app.add_option("-s,--string", strvec, "", CLI::Args(3)); | |
| 342 | - EXPECT_EQ(3, opt->expected()); | |
| 341 | + CLI::Option* opt = app.add_option("-s,--string", strvec)->expected(3); | |
| 342 | + EXPECT_EQ(3, opt->get_expected()); | |
| 343 | 343 | |
| 344 | 344 | args = {"--string", "mystring", "mystring2", "mystring3"}; |
| 345 | 345 | run(); |
| ... | ... | @@ -354,7 +354,7 @@ TEST_F(TApp, VectorUnlimString) { |
| 354 | 354 | std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; |
| 355 | 355 | |
| 356 | 356 | CLI::Option* opt = app.add_option("-s,--string", strvec); |
| 357 | - EXPECT_EQ(-1, opt->expected()); | |
| 357 | + EXPECT_EQ(-1, opt->get_expected()); | |
| 358 | 358 | |
| 359 | 359 | args = {"--string", "mystring", "mystring2", "mystring3"}; |
| 360 | 360 | EXPECT_NO_THROW(run()); |
| ... | ... | @@ -363,6 +363,27 @@ TEST_F(TApp, VectorUnlimString) { |
| 363 | 363 | } |
| 364 | 364 | |
| 365 | 365 | |
| 366 | +TEST_F(TApp, VectorFancyOpts) { | |
| 367 | + std::vector<std::string> strvec; | |
| 368 | + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; | |
| 369 | + | |
| 370 | + CLI::Option* opt = app.add_option("-s,--string", strvec)->required()->expected(3); | |
| 371 | + EXPECT_EQ(3, opt->get_expected()); | |
| 372 | + | |
| 373 | + args = {"--string", "mystring", "mystring2", "mystring3"}; | |
| 374 | + EXPECT_NO_THROW(run()); | |
| 375 | + EXPECT_EQ(3, app.count("--string")); | |
| 376 | + EXPECT_EQ(answer, strvec); | |
| 377 | + | |
| 378 | + app.reset(); | |
| 379 | + args = {"one", "two"}; | |
| 380 | + EXPECT_THROW(run(), CLI::RequiredError); | |
| 381 | + | |
| 382 | + app.reset(); | |
| 383 | + EXPECT_THROW(run(), CLI::ConversionError); | |
| 384 | +} | |
| 385 | + | |
| 386 | + | |
| 366 | 387 | |
| 367 | 388 | TEST_F(TApp, BasicSubcommands) { |
| 368 | 389 | auto sub1 = app.add_subcommand("sub1"); |
| ... | ... | @@ -450,99 +471,5 @@ TEST_F(SubcommandProgram, SpareSub) { |
| 450 | 471 | EXPECT_THROW(run(), CLI::PositionalError); |
| 451 | 472 | } |
| 452 | 473 | |
| 453 | -class TAppValue : public TApp {}; | |
| 454 | - | |
| 455 | -TEST_F(TAppValue, OneString) { | |
| 456 | - auto str = app.make_option("-s,--string"); | |
| 457 | - std::string v; | |
| 458 | - args = {"--string", "mystring"}; | |
| 459 | - EXPECT_FALSE((bool) str); | |
| 460 | - EXPECT_THROW(v = *str, CLI::EmptyError); | |
| 461 | - //EXPECT_THROW(v = str, CLI::EmptyError); | |
| 462 | - EXPECT_FALSE((bool) str); | |
| 463 | - EXPECT_NO_THROW(run()); | |
| 464 | - EXPECT_TRUE((bool) str); | |
| 465 | - EXPECT_NO_THROW(v = *str); | |
| 466 | - EXPECT_NO_THROW(v = str); | |
| 467 | - | |
| 468 | - EXPECT_EQ(1, app.count("-s")); | |
| 469 | - EXPECT_EQ(1, app.count("--string")); | |
| 470 | - EXPECT_EQ(*str, "mystring"); | |
| 471 | - | |
| 472 | -} | |
| 473 | - | |
| 474 | -TEST_F(TAppValue, SeveralInts) { | |
| 475 | - auto value = app.make_option<int>("--first"); | |
| 476 | - CLI::Value<int> value2 = app.make_option<int>("-s"); | |
| 477 | - int v; | |
| 478 | - args = {"--first", "12", "-s", "19"}; | |
| 479 | - EXPECT_FALSE((bool) value); | |
| 480 | - EXPECT_FALSE((bool) value2); | |
| 481 | - | |
| 482 | - EXPECT_THROW(v = *value, CLI::EmptyError); | |
| 483 | - //EXPECT_THROW(v = str, CLI::EmptyError); | |
| 484 | - EXPECT_NO_THROW(run()); | |
| 485 | - EXPECT_TRUE((bool) value); | |
| 486 | - EXPECT_NO_THROW(v = *value); | |
| 487 | - EXPECT_NO_THROW(v = value); | |
| 488 | - | |
| 489 | - EXPECT_EQ(1, app.count("-s")); | |
| 490 | - EXPECT_EQ(1, app.count("--first")); | |
| 491 | - EXPECT_EQ(*value, 12); | |
| 492 | - EXPECT_EQ(*value2, 19); | |
| 493 | - | |
| 494 | -} | |
| 495 | - | |
| 496 | -TEST_F(TAppValue, Vector) { | |
| 497 | - auto value = app.make_option<std::vector<int>>("--first", "", CLI::Args); | |
| 498 | - auto value2 = app.make_option<std::vector<std::string>>("--second"); | |
| 499 | - | |
| 500 | - std::vector<int> i; | |
| 501 | - std::vector<std::string> s; | |
| 502 | - | |
| 503 | - args = {"--first", "12", "3", "9", "--second", "thing", "try"}; | |
| 504 | - | |
| 505 | - EXPECT_FALSE((bool) value); | |
| 506 | - EXPECT_FALSE((bool) value2); | |
| 507 | - | |
| 508 | - EXPECT_THROW(i = *value, CLI::EmptyError); | |
| 509 | - EXPECT_THROW(s = *value2, CLI::EmptyError); | |
| 510 | - | |
| 511 | - EXPECT_NO_THROW(run()); | |
| 512 | - | |
| 513 | - EXPECT_TRUE((bool) value); | |
| 514 | - EXPECT_TRUE((bool) value2); | |
| 515 | - | |
| 516 | - EXPECT_NO_THROW(i = *value); | |
| 517 | - //EXPECT_NO_THROW(i = value); | |
| 518 | - | |
| 519 | - EXPECT_NO_THROW(s = *value2); | |
| 520 | - //EXPECT_NO_THROW(s = value2); | |
| 521 | - | |
| 522 | - EXPECT_EQ(3, app.count("--first")); | |
| 523 | - EXPECT_EQ(2, app.count("--second")); | |
| 524 | - | |
| 525 | - EXPECT_EQ(std::vector<int>({12,3,9}), *value); | |
| 526 | - EXPECT_EQ(std::vector<std::string>({"thing", "try"}), *value2); | |
| 527 | - | |
| 528 | -} | |
| 529 | - | |
| 530 | -TEST_F(TAppValue, DoubleVector) { | |
| 531 | - auto value = app.make_option<std::vector<double>>("--simple"); | |
| 532 | - std::vector<double> d; | |
| 533 | - | |
| 534 | - args = {"--simple", "1.2", "3.4", "-1"}; | |
| 535 | - | |
| 536 | - EXPECT_THROW(d = *value, CLI::EmptyError); | |
| 537 | - | |
| 538 | - EXPECT_NO_THROW(run()); | |
| 539 | - | |
| 540 | - EXPECT_NO_THROW(d = *value); | |
| 541 | - | |
| 542 | - EXPECT_EQ(3, app.count("--simple")); | |
| 543 | - EXPECT_EQ(std::vector<double>({1.2, 3.4, -1}), *value); | |
| 544 | -} | |
| 545 | 474 | |
| 546 | -// TODO: Check help output, better formatting | |
| 547 | -// TODO: Add default/type info to help | |
| 548 | -// TODO: Add README | |
| 475 | +// TODO: Check help output and formatting | ... | ... |
tests/SmallTest.cpp
| ... | ... | @@ -11,24 +11,24 @@ |
| 11 | 11 | |
| 12 | 12 | TEST(Validators, FileExists) { |
| 13 | 13 | std::string myfile{"TestFileNotUsed.txt"}; |
| 14 | - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); | |
| 14 | + EXPECT_FALSE(CLI::ExistingFile(myfile)); | |
| 15 | 15 | bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file |
| 16 | 16 | EXPECT_TRUE(ok); |
| 17 | - EXPECT_TRUE(CLI::detail::_ExistingFile(myfile)); | |
| 17 | + EXPECT_TRUE(CLI::ExistingFile(myfile)); | |
| 18 | 18 | |
| 19 | 19 | std::remove(myfile.c_str()); |
| 20 | - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile)); | |
| 20 | + EXPECT_FALSE(CLI::ExistingFile(myfile)); | |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | 23 | TEST(Validators, FileNotExists) { |
| 24 | 24 | std::string myfile{"TestFileNotUsed.txt"}; |
| 25 | - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile)); | |
| 25 | + EXPECT_TRUE(CLI::NonexistentPath(myfile)); | |
| 26 | 26 | bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file |
| 27 | 27 | EXPECT_TRUE(ok); |
| 28 | - EXPECT_FALSE(CLI::detail::_NonexistentPath(myfile)); | |
| 28 | + EXPECT_FALSE(CLI::NonexistentPath(myfile)); | |
| 29 | 29 | |
| 30 | 30 | std::remove(myfile.c_str()); |
| 31 | - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile)); | |
| 31 | + EXPECT_TRUE(CLI::NonexistentPath(myfile)); | |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | TEST(Split, StringList) { | ... | ... |