Commit 15c6ee5f3db9bf83ebf738143f85fc8a7e79370d

Authored by Henry Fredrick Schreiner
1 parent 0c1aa2ab

Added Range and ValidationError, Refactor throwing errors to Option instead of App for Validation

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 +