Commit 85857d99e137f8c718dfa28b205e4b6d63584345

Authored by Henry Fredrick Schreiner
Committed by Henry Schreiner
1 parent 71557335

Return string for error message in validators

include/CLI/Option.hpp
@@ -148,7 +148,7 @@ class Option : public OptionBase<Option> { @@ -148,7 +148,7 @@ class Option : public OptionBase<Option> {
148 bool changeable_{false}; 148 bool changeable_{false};
149 149
150 /// A list of validators to run on each value parsed 150 /// A list of validators to run on each value parsed
151 - std::vector<std::function<bool(std::string &)>> validators_; 151 + std::vector<std::function<std::string(std::string &)>> validators_;
152 152
153 /// A list of options that are required with this option 153 /// A list of options that are required with this option
154 std::set<Option *> requires_; 154 std::set<Option *> requires_;
@@ -220,16 +220,20 @@ class Option : public OptionBase&lt;Option&gt; { @@ -220,16 +220,20 @@ class Option : public OptionBase&lt;Option&gt; {
220 } 220 }
221 221
222 /// Adds a validator 222 /// Adds a validator
223 - Option *check(std::function<bool(const std::string &)> validator) { 223 + Option *check(std::function<std::string(const std::string &)> validator) {
224 validators_.emplace_back(validator); 224 validators_.emplace_back(validator);
225 return this; 225 return this;
226 } 226 }
227 227
228 /// Adds a validator-like function that can change result 228 /// Adds a validator-like function that can change result
229 Option *transform(std::function<std::string(std::string)> func) { 229 Option *transform(std::function<std::string(std::string)> func) {
230 - validators_.push_back([func](std::string &inout) {  
231 - inout = func(inout);  
232 - return true; 230 + validators_.emplace_back([func](std::string &inout) {
  231 + try {
  232 + inout = func(inout);
  233 + } catch(const ValidationError &e) {
  234 + return std::string(e.what());
  235 + }
  236 + return std::string();
233 }); 237 });
234 return this; 238 return this;
235 } 239 }
@@ -430,9 +434,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -430,9 +434,11 @@ class Option : public OptionBase&lt;Option&gt; {
430 // Run the validators (can change the string) 434 // Run the validators (can change the string)
431 if(!validators_.empty()) { 435 if(!validators_.empty()) {
432 for(std::string &result : results_) 436 for(std::string &result : results_)
433 - for(const std::function<bool(std::string &)> &vali : validators_)  
434 - if(!vali(result))  
435 - throw ValidationError("Failed validation: " + get_name() + "=" + result); 437 + for(const std::function<std::string(std::string &)> &vali : validators_) {
  438 + std::string err_msg = vali(result);
  439 + if(!err_msg.empty())
  440 + throw ValidationError(get_name() + ": " + err_msg);
  441 + }
436 } 442 }
437 443
438 bool local_result; 444 bool local_result;
include/CLI/Validators.hpp
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details. 4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
5 5
6 #include "CLI/TypeTools.hpp" 6 #include "CLI/TypeTools.hpp"
  7 +
7 #include <functional> 8 #include <functional>
8 #include <iostream> 9 #include <iostream>
9 #include <string> 10 #include <string>
@@ -19,64 +20,62 @@ namespace CLI { @@ -19,64 +20,62 @@ namespace CLI {
19 /// @defgroup validator_group Validators 20 /// @defgroup validator_group Validators
20 /// @brief Some validators that are provided 21 /// @brief Some validators that are provided
21 /// 22 ///
22 -/// These are simple `bool(std::string&)` validators that are useful. 23 +/// These are simple `void(std::string&)` validators that are useful. They throw
  24 +/// a ValidationError if they fail (or the normally expected error if the cast fails)
23 /// @{ 25 /// @{
24 26
25 /// Check for an existing file 27 /// Check for an existing file
26 -inline bool ExistingFile(const std::string &filename) { 28 +inline std::string ExistingFile(const std::string &filename) {
27 struct stat buffer; 29 struct stat buffer;
28 bool exist = stat(filename.c_str(), &buffer) == 0; 30 bool exist = stat(filename.c_str(), &buffer) == 0;
29 bool is_dir = (buffer.st_mode & S_IFDIR) != 0; 31 bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
30 if(!exist) { 32 if(!exist) {
31 - std::cerr << "File does not exist: " << filename << std::endl;  
32 - return false; 33 + return "File does not exist: " + filename;
33 } else if(is_dir) { 34 } else if(is_dir) {
34 - std::cerr << "File is actually a directory: " << filename << std::endl;  
35 - return false;  
36 - } else {  
37 - return true; 35 + return "File is actually a directory: " + filename;
38 } 36 }
  37 + return std::string();
39 } 38 }
40 39
41 /// Check for an existing directory 40 /// Check for an existing directory
42 -inline bool ExistingDirectory(const std::string &filename) { 41 +inline std::string ExistingDirectory(const std::string &filename) {
43 struct stat buffer; 42 struct stat buffer;
44 bool exist = stat(filename.c_str(), &buffer) == 0; 43 bool exist = stat(filename.c_str(), &buffer) == 0;
45 bool is_dir = (buffer.st_mode & S_IFDIR) != 0; 44 bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
46 if(!exist) { 45 if(!exist) {
47 - std::cerr << "Directory does not exist: " << filename << std::endl;  
48 - return false;  
49 - } else if(is_dir) {  
50 - return true;  
51 - } else {  
52 - std::cerr << "Directory is actually a file: " << filename << std::endl;  
53 - return false; 46 + return "Directory does not exist: " + filename;
  47 + } else if(!is_dir) {
  48 + return "Directory is actually a file: " + filename;
54 } 49 }
  50 + return std::string();
55 } 51 }
56 52
57 /// Check for a non-existing path 53 /// Check for a non-existing path
58 -inline bool NonexistentPath(const std::string &filename) { 54 +inline std::string NonexistentPath(const std::string &filename) {
59 struct stat buffer; 55 struct stat buffer;
60 bool exist = stat(filename.c_str(), &buffer) == 0; 56 bool exist = stat(filename.c_str(), &buffer) == 0;
61 - if(!exist) {  
62 - return true;  
63 - } else {  
64 - std::cerr << "Path exists: " << filename << std::endl;  
65 - return false; 57 + if(exist) {
  58 + return "Path already exists: " + filename;
66 } 59 }
  60 + return std::string();
67 } 61 }
68 62
69 /// Produce a range validator function 63 /// Produce a range validator function
70 -template <typename T> std::function<bool(const std::string &)> Range(T min, T max) { 64 +template <typename T> std::function<std::string(const std::string &)> Range(T min, T max) {
71 return [min, max](std::string input) { 65 return [min, max](std::string input) {
72 T val; 66 T val;
73 detail::lexical_cast(input, val); 67 detail::lexical_cast(input, val);
74 - return val >= min && val <= max; 68 + if(val < min || val > max)
  69 + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max);
  70 +
  71 + return std::string();
75 }; 72 };
76 } 73 }
77 74
78 /// Range of one value is 0 to value 75 /// Range of one value is 0 to value
79 -template <typename T> std::function<bool(const std::string &)> Range(T max) { return Range(static_cast<T>(0), max); } 76 +template <typename T> std::function<std::string(const std::string &)> Range(T max) {
  77 + return Range(static_cast<T>(0), max);
  78 +}
80 79
81 /// @} 80 /// @}
82 81
tests/AppTest.cpp
@@ -606,7 +606,7 @@ TEST_F(TApp, RemoveOption) { @@ -606,7 +606,7 @@ TEST_F(TApp, RemoveOption) {
606 606
607 TEST_F(TApp, FileNotExists) { 607 TEST_F(TApp, FileNotExists) {
608 std::string myfile{"TestNonFileNotUsed.txt"}; 608 std::string myfile{"TestNonFileNotUsed.txt"};
609 - EXPECT_TRUE(CLI::NonexistentPath(myfile)); 609 + EXPECT_NO_THROW(CLI::NonexistentPath(myfile));
610 610
611 std::string filename; 611 std::string filename;
612 app.add_option("--file", filename)->check(CLI::NonexistentPath); 612 app.add_option("--file", filename)->check(CLI::NonexistentPath);
@@ -622,12 +622,12 @@ TEST_F(TApp, FileNotExists) { @@ -622,12 +622,12 @@ TEST_F(TApp, FileNotExists) {
622 EXPECT_THROW(run(), CLI::ValidationError); 622 EXPECT_THROW(run(), CLI::ValidationError);
623 623
624 std::remove(myfile.c_str()); 624 std::remove(myfile.c_str());
625 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 625 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
626 } 626 }
627 627
628 TEST_F(TApp, FileExists) { 628 TEST_F(TApp, FileExists) {
629 std::string myfile{"TestNonFileNotUsed.txt"}; 629 std::string myfile{"TestNonFileNotUsed.txt"};
630 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 630 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
631 631
632 std::string filename = "Failed"; 632 std::string filename = "Failed";
633 app.add_option("--file", filename)->check(CLI::ExistingFile); 633 app.add_option("--file", filename)->check(CLI::ExistingFile);
@@ -643,7 +643,7 @@ TEST_F(TApp, FileExists) { @@ -643,7 +643,7 @@ TEST_F(TApp, FileExists) {
643 EXPECT_EQ(myfile, filename); 643 EXPECT_EQ(myfile, filename);
644 644
645 std::remove(myfile.c_str()); 645 std::remove(myfile.c_str());
646 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 646 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
647 } 647 }
648 648
649 TEST_F(TApp, InSet) { 649 TEST_F(TApp, InSet) {
tests/HelpersTest.cpp
@@ -87,50 +87,50 @@ TEST(Trim, TrimCopy) { @@ -87,50 +87,50 @@ TEST(Trim, TrimCopy) {
87 87
88 TEST(Validators, FileExists) { 88 TEST(Validators, FileExists) {
89 std::string myfile{"TestFileNotUsed.txt"}; 89 std::string myfile{"TestFileNotUsed.txt"};
90 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 90 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
91 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 91 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
92 EXPECT_TRUE(ok); 92 EXPECT_TRUE(ok);
93 - EXPECT_TRUE(CLI::ExistingFile(myfile)); 93 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
94 94
95 std::remove(myfile.c_str()); 95 std::remove(myfile.c_str());
96 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 96 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
97 } 97 }
98 98
99 TEST(Validators, FileNotExists) { 99 TEST(Validators, FileNotExists) {
100 std::string myfile{"TestFileNotUsed.txt"}; 100 std::string myfile{"TestFileNotUsed.txt"};
101 - EXPECT_TRUE(CLI::NonexistentPath(myfile)); 101 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
102 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 102 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
103 EXPECT_TRUE(ok); 103 EXPECT_TRUE(ok);
104 - EXPECT_FALSE(CLI::NonexistentPath(myfile)); 104 + EXPECT_FALSE(CLI::NonexistentPath(myfile).empty());
105 105
106 std::remove(myfile.c_str()); 106 std::remove(myfile.c_str());
107 - EXPECT_TRUE(CLI::NonexistentPath(myfile)); 107 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
108 } 108 }
109 109
110 TEST(Validators, FileIsDir) { 110 TEST(Validators, FileIsDir) {
111 std::string mydir{"../tests"}; 111 std::string mydir{"../tests"};
112 - EXPECT_FALSE(CLI::ExistingFile(mydir)); 112 + EXPECT_NE(CLI::ExistingFile(mydir), "");
113 } 113 }
114 114
115 TEST(Validators, DirectoryExists) { 115 TEST(Validators, DirectoryExists) {
116 std::string mydir{"../tests"}; 116 std::string mydir{"../tests"};
117 - EXPECT_TRUE(CLI::ExistingDirectory(mydir)); 117 + EXPECT_EQ(CLI::ExistingDirectory(mydir), "");
118 } 118 }
119 119
120 TEST(Validators, DirectoryNotExists) { 120 TEST(Validators, DirectoryNotExists) {
121 std::string mydir{"nondirectory"}; 121 std::string mydir{"nondirectory"};
122 - EXPECT_FALSE(CLI::ExistingDirectory(mydir)); 122 + EXPECT_NE(CLI::ExistingDirectory(mydir), "");
123 } 123 }
124 124
125 TEST(Validators, DirectoryIsFile) { 125 TEST(Validators, DirectoryIsFile) {
126 std::string myfile{"TestFileNotUsed.txt"}; 126 std::string myfile{"TestFileNotUsed.txt"};
127 - EXPECT_TRUE(CLI::NonexistentPath(myfile)); 127 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
128 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 128 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
129 EXPECT_TRUE(ok); 129 EXPECT_TRUE(ok);
130 - EXPECT_FALSE(CLI::ExistingDirectory(myfile)); 130 + EXPECT_FALSE(CLI::ExistingDirectory(myfile).empty());
131 131
132 std::remove(myfile.c_str()); 132 std::remove(myfile.c_str());
133 - EXPECT_TRUE(CLI::NonexistentPath(myfile)); 133 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
134 } 134 }
135 135
136 // Yes, this is testing an app_helper :) 136 // Yes, this is testing an app_helper :)
@@ -139,14 +139,14 @@ TEST(AppHelper, TempfileCreated) { @@ -139,14 +139,14 @@ TEST(AppHelper, TempfileCreated) {
139 { 139 {
140 TempFile myfile{name}; 140 TempFile myfile{name};
141 141
142 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 142 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
143 143
144 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file 144 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
145 EXPECT_TRUE(ok); 145 EXPECT_TRUE(ok);
146 - EXPECT_TRUE(CLI::ExistingFile(name)); 146 + EXPECT_TRUE(CLI::ExistingFile(name).empty());
147 EXPECT_THROW({ TempFile otherfile(name); }, std::runtime_error); 147 EXPECT_THROW({ TempFile otherfile(name); }, std::runtime_error);
148 } 148 }
149 - EXPECT_FALSE(CLI::ExistingFile(name)); 149 + EXPECT_FALSE(CLI::ExistingFile(name).empty());
150 } 150 }
151 151
152 TEST(AppHelper, TempfileNotCreated) { 152 TEST(AppHelper, TempfileNotCreated) {
@@ -154,9 +154,9 @@ TEST(AppHelper, TempfileNotCreated) { @@ -154,9 +154,9 @@ TEST(AppHelper, TempfileNotCreated) {
154 { 154 {
155 TempFile myfile{name}; 155 TempFile myfile{name};
156 156
157 - EXPECT_FALSE(CLI::ExistingFile(myfile)); 157 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
158 } 158 }
159 - EXPECT_FALSE(CLI::ExistingFile(name)); 159 + EXPECT_FALSE(CLI::ExistingFile(name).empty());
160 } 160 }
161 161
162 TEST(AppHelper, Ofstream) { 162 TEST(AppHelper, Ofstream) {
@@ -170,9 +170,9 @@ TEST(AppHelper, Ofstream) { @@ -170,9 +170,9 @@ TEST(AppHelper, Ofstream) {
170 out << "this is output" << std::endl; 170 out << "this is output" << std::endl;
171 } 171 }
172 172
173 - EXPECT_TRUE(CLI::ExistingFile(myfile)); 173 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
174 } 174 }
175 - EXPECT_FALSE(CLI::ExistingFile(name)); 175 + EXPECT_FALSE(CLI::ExistingFile(name).empty());
176 } 176 }
177 177
178 TEST(Split, StringList) { 178 TEST(Split, StringList) {
tests/app_helper.hpp
@@ -27,7 +27,7 @@ class TempFile { @@ -27,7 +27,7 @@ class TempFile {
27 27
28 public: 28 public:
29 TempFile(std::string name) : _name(name) { 29 TempFile(std::string name) : _name(name) {
30 - if(!CLI::NonexistentPath(_name)) 30 + if(!CLI::NonexistentPath(_name).empty())
31 throw std::runtime_error(_name); 31 throw std::runtime_error(_name);
32 } 32 }
33 33