Commit 15c6ee5f3db9bf83ebf738143f85fc8a7e79370d
1 parent
0c1aa2ab
Added Range and ValidationError, Refactor throwing errors to Option instead of App for Validation
Showing
5 changed files
with
79 additions
and
9 deletions
include/CLI/App.hpp
| @@ -550,8 +550,7 @@ protected: | @@ -550,8 +550,7 @@ protected: | ||
| 550 | // Process callbacks | 550 | // Process callbacks |
| 551 | for(const Option_p& opt : options) { | 551 | for(const Option_p& opt : options) { |
| 552 | if (opt->count() > 0) { | 552 | if (opt->count() > 0) { |
| 553 | - if(!opt->run_callback()) | ||
| 554 | - throw ConversionError(opt->get_name() + "=" + detail::join(opt->flatten_results())); | 553 | + opt->run_callback(); |
| 555 | } | 554 | } |
| 556 | } | 555 | } |
| 557 | 556 |
include/CLI/Error.hpp
| @@ -70,6 +70,11 @@ struct ConversionError : public ParseError { | @@ -70,6 +70,11 @@ struct ConversionError : public ParseError { | ||
| 70 | ConversionError(std::string name) : ParseError("ConversionError", name, 2) {} | 70 | ConversionError(std::string name) : ParseError("ConversionError", name, 2) {} |
| 71 | }; | 71 | }; |
| 72 | 72 | ||
| 73 | +/// Thrown when validation of results fails | ||
| 74 | +struct ValidationError : public ParseError { | ||
| 75 | + ValidationError(std::string name) : ParseError("ValidationError", name, 2) {} | ||
| 76 | +}; | ||
| 77 | + | ||
| 73 | /// Thrown when a required option is missing | 78 | /// Thrown when a required option is missing |
| 74 | struct RequiredError : public ParseError { | 79 | struct RequiredError : public ParseError { |
| 75 | RequiredError(std::string name) : ParseError("RequiredError", name, 5) {} | 80 | RequiredError(std::string name) : ParseError("RequiredError", name, 5) {} |
include/CLI/Option.hpp
| @@ -196,14 +196,15 @@ public: | @@ -196,14 +196,15 @@ public: | ||
| 196 | 196 | ||
| 197 | 197 | ||
| 198 | /// Process the callback | 198 | /// Process the callback |
| 199 | - bool run_callback() const { | 199 | + void run_callback() const { |
| 200 | + if(!callback(results)) | ||
| 201 | + throw ConversionError(get_name() + "=" + detail::join(flatten_results())); | ||
| 200 | if(_validators.size()>0) { | 202 | if(_validators.size()>0) { |
| 201 | for(const std::string & result : flatten_results()) | 203 | for(const std::string & result : flatten_results()) |
| 202 | for(const std::function<bool(std::string)> &vali : _validators) | 204 | for(const std::function<bool(std::string)> &vali : _validators) |
| 203 | if(!vali(result)) | 205 | if(!vali(result)) |
| 204 | - return false; | 206 | + throw ValidationError(get_name() + "=" + result); |
| 205 | } | 207 | } |
| 206 | - return callback(results); | ||
| 207 | } | 208 | } |
| 208 | 209 | ||
| 209 | /// If options share any of the same names, they are equal (not counting positional) | 210 | /// If options share any of the same names, they are equal (not counting positional) |
include/CLI/Validators.hpp
| @@ -5,7 +5,8 @@ | @@ -5,7 +5,8 @@ | ||
| 5 | 5 | ||
| 6 | #include <string> | 6 | #include <string> |
| 7 | #include <iostream> | 7 | #include <iostream> |
| 8 | - | 8 | +#include <functional> |
| 9 | +#include "CLI/TypeTools.hpp" | ||
| 9 | 10 | ||
| 10 | // C standard library | 11 | // C standard library |
| 11 | // Only needed for existence checking | 12 | // Only needed for existence checking |
| @@ -61,5 +62,20 @@ bool NonexistentPath(std::string filename) { | @@ -61,5 +62,20 @@ bool NonexistentPath(std::string filename) { | ||
| 61 | } | 62 | } |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 65 | +/// Produce a range validator function | ||
| 66 | +template<typename T> | ||
| 67 | +std::function<bool(std::string)> Range(T min, T max) { | ||
| 68 | + return [min, max](std::string input){ | ||
| 69 | + T val; | ||
| 70 | + detail::lexical_cast(input, val); | ||
| 71 | + return val >= min && val <= max; | ||
| 72 | + }; | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +/// Range of one value is 0 to value | ||
| 76 | +template<typename T> | ||
| 77 | +std::function<bool(std::string)> Range(T max) { | ||
| 78 | + return Range((T) 0, max); | ||
| 79 | +} | ||
| 64 | 80 | ||
| 65 | } | 81 | } |
tests/AppTest.cpp
| @@ -282,7 +282,7 @@ TEST_F(TApp, FileNotExists) { | @@ -282,7 +282,7 @@ TEST_F(TApp, FileNotExists) { | ||
| 282 | 282 | ||
| 283 | bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file | 283 | bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file |
| 284 | EXPECT_TRUE(ok); | 284 | EXPECT_TRUE(ok); |
| 285 | - EXPECT_THROW(run(), CLI::ConversionError); | 285 | + EXPECT_THROW(run(), CLI::ValidationError); |
| 286 | 286 | ||
| 287 | std::remove(myfile.c_str()); | 287 | std::remove(myfile.c_str()); |
| 288 | EXPECT_FALSE(CLI::ExistingFile(myfile)); | 288 | EXPECT_FALSE(CLI::ExistingFile(myfile)); |
| @@ -296,8 +296,7 @@ TEST_F(TApp, FileExists) { | @@ -296,8 +296,7 @@ TEST_F(TApp, FileExists) { | ||
| 296 | app.add_option("--file", filename)->check(CLI::ExistingFile); | 296 | app.add_option("--file", filename)->check(CLI::ExistingFile); |
| 297 | args = {"--file", myfile}; | 297 | args = {"--file", myfile}; |
| 298 | 298 | ||
| 299 | - EXPECT_THROW(run(), CLI::ConversionError); | ||
| 300 | - EXPECT_EQ("Failed", filename); | 299 | + EXPECT_THROW(run(), CLI::ValidationError); |
| 301 | 300 | ||
| 302 | app.reset(); | 301 | app.reset(); |
| 303 | 302 | ||
| @@ -509,3 +508,53 @@ TEST_F(TApp, Env) { | @@ -509,3 +508,53 @@ TEST_F(TApp, Env) { | ||
| 509 | EXPECT_THROW(run(), CLI::RequiredError); | 508 | EXPECT_THROW(run(), CLI::RequiredError); |
| 510 | } | 509 | } |
| 511 | 510 | ||
| 511 | +TEST_F(TApp, RangeInt) { | ||
| 512 | + int x=0; | ||
| 513 | + app.add_option("--one", x)->check(CLI::Range(3,6)); | ||
| 514 | + | ||
| 515 | + args = {"--one=1"}; | ||
| 516 | + EXPECT_THROW(run(), CLI::ValidationError); | ||
| 517 | + | ||
| 518 | + app.reset(); | ||
| 519 | + args = {"--one=7"}; | ||
| 520 | + EXPECT_THROW(run(), CLI::ValidationError); | ||
| 521 | + | ||
| 522 | + app.reset(); | ||
| 523 | + args = {"--one=3"}; | ||
| 524 | + EXPECT_NO_THROW(run()); | ||
| 525 | + | ||
| 526 | + app.reset(); | ||
| 527 | + args = {"--one=5"}; | ||
| 528 | + EXPECT_NO_THROW(run()); | ||
| 529 | + | ||
| 530 | + app.reset(); | ||
| 531 | + args = {"--one=6"}; | ||
| 532 | + EXPECT_NO_THROW(run()); | ||
| 533 | +} | ||
| 534 | + | ||
| 535 | +TEST_F(TApp, RangeDouble) { | ||
| 536 | + | ||
| 537 | + double x=0; | ||
| 538 | + /// Note that this must be a double in Range, too | ||
| 539 | + app.add_option("--one", x)->check(CLI::Range(3.0,6.0)); | ||
| 540 | + | ||
| 541 | + args = {"--one=1"}; | ||
| 542 | + EXPECT_THROW(run(), CLI::ValidationError); | ||
| 543 | + | ||
| 544 | + app.reset(); | ||
| 545 | + args = {"--one=7"}; | ||
| 546 | + EXPECT_THROW(run(), CLI::ValidationError); | ||
| 547 | + | ||
| 548 | + app.reset(); | ||
| 549 | + args = {"--one=3"}; | ||
| 550 | + EXPECT_NO_THROW(run()); | ||
| 551 | + | ||
| 552 | + app.reset(); | ||
| 553 | + args = {"--one=5"}; | ||
| 554 | + EXPECT_NO_THROW(run()); | ||
| 555 | + | ||
| 556 | + app.reset(); | ||
| 557 | + args = {"--one=6"}; | ||
| 558 | + EXPECT_NO_THROW(run()); | ||
| 559 | +} | ||
| 560 | + |