Commit 571fb07cfb866d7754e0154655d5bbb97b1fc8ec

Authored by Philip Top
Committed by Henry Schreiner
1 parent 546d5ec2

Add a ! operator to Validators for checking a false condition (#230)

README.md
@@ -274,7 +274,7 @@ Before parsing, you can set the following options: @@ -274,7 +274,7 @@ Before parsing, you can set the following options:
274 - `->configurable(false)`: Disable this option from being in a configuration file. 274 - `->configurable(false)`: Disable this option from being in a configuration file.
275 275
276 276
277 -These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort). 277 +These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort). Validators can also be inverted with `!` such as `->check(!CLI::ExistingFile)` which would check that a file doesn't exist.
278 278
279 The `IsMember` validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The type should be convertible from a string. You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set. 279 The `IsMember` validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this validator; the container just needs to be iterable and have a `::value_type`. The type should be convertible from a string. You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set.
280 After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`. 280 After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`.
include/CLI/App.hpp
@@ -292,7 +292,7 @@ class App { @@ -292,7 +292,7 @@ class App {
292 return this; 292 return this;
293 } 293 }
294 294
295 - /// specify that the positional arguments are only at the end of the sequence 295 + /// Specify that the positional arguments are only at the end of the sequence
296 App *positionals_at_end(bool value = true) { 296 App *positionals_at_end(bool value = true) {
297 positionals_at_end_ = value; 297 positionals_at_end_ = value;
298 return this; 298 return this;
include/CLI/Validators.hpp
@@ -100,6 +100,29 @@ class Validator { @@ -100,6 +100,29 @@ class Validator {
100 }; 100 };
101 return newval; 101 return newval;
102 } 102 }
  103 +
  104 + /// Create a validator that fails when a given validator succeeds
  105 + Validator operator!() const {
  106 + Validator newval;
  107 + std::string typestring = tname;
  108 + if(tname.empty()) {
  109 + typestring = tname_function();
  110 + }
  111 + newval.tname = "NOT " + typestring;
  112 +
  113 + std::string failString = "check " + typestring + " succeeded improperly";
  114 + // Give references (will make a copy in lambda function)
  115 + const std::function<std::string(std::string & res)> &f1 = func;
  116 +
  117 + newval.func = [f1, failString](std::string &test) -> std::string {
  118 + std::string s1 = f1(test);
  119 + if(s1.empty())
  120 + return failString;
  121 + else
  122 + return std::string();
  123 + };
  124 + return newval;
  125 + }
103 }; 126 };
104 127
105 // The implementation of the built in validators is using the Validator class; 128 // The implementation of the built in validators is using the Validator class;
tests/AppTest.cpp
@@ -1317,6 +1317,24 @@ TEST_F(TApp, FileExists) { @@ -1317,6 +1317,24 @@ TEST_F(TApp, FileExists) {
1317 EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); 1317 EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
1318 } 1318 }
1319 1319
  1320 +TEST_F(TApp, NotFileExists) {
  1321 + std::string myfile{"TestNonFileNotUsed.txt"};
  1322 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
  1323 +
  1324 + std::string filename = "Failed";
  1325 + app.add_option("--file", filename)->check(!CLI::ExistingFile);
  1326 + args = {"--file", myfile};
  1327 +
  1328 + EXPECT_NO_THROW(run());
  1329 +
  1330 + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
  1331 + EXPECT_TRUE(ok);
  1332 + EXPECT_THROW(run(), CLI::ValidationError);
  1333 +
  1334 + std::remove(myfile.c_str());
  1335 + EXPECT_FALSE(CLI::ExistingFile(myfile).empty());
  1336 +}
  1337 +
1320 TEST_F(TApp, VectorFixedString) { 1338 TEST_F(TApp, VectorFixedString) {
1321 std::vector<std::string> strvec; 1339 std::vector<std::string> strvec;
1322 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 1340 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
tests/SetTest.cpp
@@ -414,6 +414,20 @@ TEST_F(TApp, InSetIgnoreCasePointer) { @@ -414,6 +414,20 @@ TEST_F(TApp, InSetIgnoreCasePointer) {
414 EXPECT_THROW(run(), CLI::ArgumentMismatch); 414 EXPECT_THROW(run(), CLI::ArgumentMismatch);
415 } 415 }
416 416
  417 +TEST_F(TApp, NotInSetIgnoreCasePointer) {
  418 +
  419 + std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
  420 + std::string choice;
  421 + app.add_option("-q,--quick", choice)->check(!CLI::IsMember(*options, CLI::ignore_case));
  422 +
  423 + args = {"--quick", "One"};
  424 + EXPECT_THROW(run(), CLI::ValidationError);
  425 +
  426 + args = {"--quick", "four"};
  427 + run();
  428 + EXPECT_EQ(choice, "four");
  429 +}
  430 +
417 TEST_F(TApp, InSetIgnoreUnderscore) { 431 TEST_F(TApp, InSetIgnoreUnderscore) {
418 432
419 std::string choice; 433 std::string choice;