Commit 571fb07cfb866d7754e0154655d5bbb97b1fc8ec
Committed by
Henry Schreiner
1 parent
546d5ec2
Add a ! operator to Validators for checking a false condition (#230)
Showing
5 changed files
with
57 additions
and
2 deletions
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; |