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 148 bool changeable_{false};
149 149  
150 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 153 /// A list of options that are required with this option
154 154 std::set<Option *> requires_;
... ... @@ -220,16 +220,20 @@ class Option : public OptionBase&lt;Option&gt; {
220 220 }
221 221  
222 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 224 validators_.emplace_back(validator);
225 225 return this;
226 226 }
227 227  
228 228 /// Adds a validator-like function that can change result
229 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 238 return this;
235 239 }
... ... @@ -430,9 +434,11 @@ class Option : public OptionBase&lt;Option&gt; {
430 434 // Run the validators (can change the string)
431 435 if(!validators_.empty()) {
432 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 444 bool local_result;
... ...
include/CLI/Validators.hpp
... ... @@ -4,6 +4,7 @@
4 4 // file LICENSE or https://github.com/CLIUtils/CLI11 for details.
5 5  
6 6 #include "CLI/TypeTools.hpp"
  7 +
7 8 #include <functional>
8 9 #include <iostream>
9 10 #include <string>
... ... @@ -19,64 +20,62 @@ namespace CLI {
19 20 /// @defgroup validator_group Validators
20 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 27 /// Check for an existing file
26   -inline bool ExistingFile(const std::string &filename) {
  28 +inline std::string ExistingFile(const std::string &filename) {
27 29 struct stat buffer;
28 30 bool exist = stat(filename.c_str(), &buffer) == 0;
29 31 bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
30 32 if(!exist) {
31   - std::cerr << "File does not exist: " << filename << std::endl;
32   - return false;
  33 + return "File does not exist: " + filename;
33 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 40 /// Check for an existing directory
42   -inline bool ExistingDirectory(const std::string &filename) {
  41 +inline std::string ExistingDirectory(const std::string &filename) {
43 42 struct stat buffer;
44 43 bool exist = stat(filename.c_str(), &buffer) == 0;
45 44 bool is_dir = (buffer.st_mode & S_IFDIR) != 0;
46 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 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 55 struct stat buffer;
60 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 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 65 return [min, max](std::string input) {
72 66 T val;
73 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 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 606  
607 607 TEST_F(TApp, FileNotExists) {
608 608 std::string myfile{"TestNonFileNotUsed.txt"};
609   - EXPECT_TRUE(CLI::NonexistentPath(myfile));
  609 + EXPECT_NO_THROW(CLI::NonexistentPath(myfile));
610 610  
611 611 std::string filename;
612 612 app.add_option("--file", filename)->check(CLI::NonexistentPath);
... ... @@ -622,12 +622,12 @@ TEST_F(TApp, FileNotExists) {
622 622 EXPECT_THROW(run(), CLI::ValidationError);
623 623  
624 624 std::remove(myfile.c_str());
625   - EXPECT_FALSE(CLI::ExistingFile(myfile));
  625 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
626 626 }
627 627  
628 628 TEST_F(TApp, FileExists) {
629 629 std::string myfile{"TestNonFileNotUsed.txt"};
630   - EXPECT_FALSE(CLI::ExistingFile(myfile));
  630 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
631 631  
632 632 std::string filename = "Failed";
633 633 app.add_option("--file", filename)->check(CLI::ExistingFile);
... ... @@ -643,7 +643,7 @@ TEST_F(TApp, FileExists) {
643 643 EXPECT_EQ(myfile, filename);
644 644  
645 645 std::remove(myfile.c_str());
646   - EXPECT_FALSE(CLI::ExistingFile(myfile));
  646 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
647 647 }
648 648  
649 649 TEST_F(TApp, InSet) {
... ...
tests/HelpersTest.cpp
... ... @@ -87,50 +87,50 @@ TEST(Trim, TrimCopy) {
87 87  
88 88 TEST(Validators, FileExists) {
89 89 std::string myfile{"TestFileNotUsed.txt"};
90   - EXPECT_FALSE(CLI::ExistingFile(myfile));
  90 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
91 91 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
92 92 EXPECT_TRUE(ok);
93   - EXPECT_TRUE(CLI::ExistingFile(myfile));
  93 + EXPECT_TRUE(CLI::ExistingFile(myfile).empty());
94 94  
95 95 std::remove(myfile.c_str());
96   - EXPECT_FALSE(CLI::ExistingFile(myfile));
  96 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
97 97 }
98 98  
99 99 TEST(Validators, FileNotExists) {
100 100 std::string myfile{"TestFileNotUsed.txt"};
101   - EXPECT_TRUE(CLI::NonexistentPath(myfile));
  101 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
102 102 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
103 103 EXPECT_TRUE(ok);
104   - EXPECT_FALSE(CLI::NonexistentPath(myfile));
  104 + EXPECT_FALSE(CLI::NonexistentPath(myfile).empty());
105 105  
106 106 std::remove(myfile.c_str());
107   - EXPECT_TRUE(CLI::NonexistentPath(myfile));
  107 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
108 108 }
109 109  
110 110 TEST(Validators, FileIsDir) {
111 111 std::string mydir{"../tests"};
112   - EXPECT_FALSE(CLI::ExistingFile(mydir));
  112 + EXPECT_NE(CLI::ExistingFile(mydir), "");
113 113 }
114 114  
115 115 TEST(Validators, DirectoryExists) {
116 116 std::string mydir{"../tests"};
117   - EXPECT_TRUE(CLI::ExistingDirectory(mydir));
  117 + EXPECT_EQ(CLI::ExistingDirectory(mydir), "");
118 118 }
119 119  
120 120 TEST(Validators, DirectoryNotExists) {
121 121 std::string mydir{"nondirectory"};
122   - EXPECT_FALSE(CLI::ExistingDirectory(mydir));
  122 + EXPECT_NE(CLI::ExistingDirectory(mydir), "");
123 123 }
124 124  
125 125 TEST(Validators, DirectoryIsFile) {
126 126 std::string myfile{"TestFileNotUsed.txt"};
127   - EXPECT_TRUE(CLI::NonexistentPath(myfile));
  127 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
128 128 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
129 129 EXPECT_TRUE(ok);
130   - EXPECT_FALSE(CLI::ExistingDirectory(myfile));
  130 + EXPECT_FALSE(CLI::ExistingDirectory(myfile).empty());
131 131  
132 132 std::remove(myfile.c_str());
133   - EXPECT_TRUE(CLI::NonexistentPath(myfile));
  133 + EXPECT_TRUE(CLI::NonexistentPath(myfile).empty());
134 134 }
135 135  
136 136 // Yes, this is testing an app_helper :)
... ... @@ -139,14 +139,14 @@ TEST(AppHelper, TempfileCreated) {
139 139 {
140 140 TempFile myfile{name};
141 141  
142   - EXPECT_FALSE(CLI::ExistingFile(myfile));
  142 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
143 143  
144 144 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
145 145 EXPECT_TRUE(ok);
146   - EXPECT_TRUE(CLI::ExistingFile(name));
  146 + EXPECT_TRUE(CLI::ExistingFile(name).empty());
147 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 152 TEST(AppHelper, TempfileNotCreated) {
... ... @@ -154,9 +154,9 @@ TEST(AppHelper, TempfileNotCreated) {
154 154 {
155 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 162 TEST(AppHelper, Ofstream) {
... ... @@ -170,9 +170,9 @@ TEST(AppHelper, Ofstream) {
170 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 178 TEST(Split, StringList) {
... ...
tests/app_helper.hpp
... ... @@ -27,7 +27,7 @@ class TempFile {
27 27  
28 28 public:
29 29 TempFile(std::string name) : _name(name) {
30   - if(!CLI::NonexistentPath(_name))
  30 + if(!CLI::NonexistentPath(_name).empty())
31 31 throw std::runtime_error(_name);
32 32 }
33 33  
... ...