Skip to content

Commit 8c77664

Browse files
move some of the Validators to an ExtraValidators file (#1192)
Rework some of the validator locations, add documentation, and fix some lingering issues with validators. The extra will will enable additions of some new validators and reduce compile times for those that are not needed. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 1ab8646 commit 8c77664

37 files changed

+1821
-1227
lines changed

.codacy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ engines:
99
coverage:
1010
enabled: false
1111
cppcheck:
12+
enabled: false
1213
language: c++
1314
languages:
1415

.github/workflows/tests.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ jobs:
4141
-DCLI11_SINGLE_FILE_TESTS=OFF \
4242
-DCLI11_BUILD_EXAMPLES=OFF \
4343
-DCLI11_PRECOMPILED=${{matrix.precompile}} \
44-
-DCMAKE_BUILD_TYPE=Coverage
44+
-DCMAKE_BUILD_TYPE=Coverage \
45+
-DCLI11_ENABLE_EXTRA_VALIDATORS=ON
4546
4647
- name: Build
4748
run: cmake --build build -j4
@@ -233,7 +234,7 @@ jobs:
233234
with:
234235
submodules: true
235236
- name: Configure
236-
run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_PRECOMPILED=ON
237+
run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCLI11_ENABLE_EXTRA_VALIDATORS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_PRECOMPILED=ON
237238
- name: Build
238239
run: cmake --build build -j2
239240
- name: install
@@ -250,7 +251,7 @@ jobs:
250251
with:
251252
submodules: true
252253
- name: Configure
253-
run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_SINGLE_FILE=ON
254+
run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCLI11_ENABLE_EXTRA_VALIDATORS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_SINGLE_FILE=ON
254255
- name: Build
255256
run: cmake --build build -j2
256257
- name: install
@@ -274,7 +275,7 @@ jobs:
274275
- name: Check CMake 3.15
275276
uses: ./.github/actions/quick_cmake
276277
with:
277-
cmake-version: "3.15"
278+
cmake-version: "3.15.6"
278279
if: success() || failure()
279280

280281
- name: Check CMake 3.16

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cc_library(
22
name = "cli11",
33
srcs = glob(["src/**/*.cpp"]),
44
hdrs = glob(["include/**/*.hpp"]),
5-
local_defines = ["CLI11_COMPILE"],
5+
local_defines = ["CLI11_COMPILE", "CLI11_ENABLE_EXTRA_VALIDATORS=1"],
66
strip_include_prefix = "/include",
77
visibility = ["//visibility:public"],
88
)

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ set(CLI11_headers
137137
${CLI11_headerLoc}/StringTools.hpp
138138
${CLI11_headerLoc}/TypeTools.hpp
139139
${CLI11_headerLoc}/Validators.hpp
140+
${CLI11_headerLoc}/ExtraValidators.hpp
140141
${CLI11_headerLoc}/Version.hpp
141142
${CLI11_headerLoc}/Encoding.hpp
142143
${CLI11_headerLoc}/Argv.hpp)
@@ -149,6 +150,7 @@ set(CLI11_impl_headers
149150
${CLI11_headerLoc}/impl/Split_inl.hpp
150151
${CLI11_headerLoc}/impl/StringTools_inl.hpp
151152
${CLI11_headerLoc}/impl/Validators_inl.hpp
153+
${CLI11_headerLoc}/impl/ExtraValidators_inl.hpp
152154
${CLI11_headerLoc}/impl/Encoding_inl.hpp
153155
${CLI11_headerLoc}/impl/Argv_inl.hpp)
154156

README.md

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ set with a simple and intuitive interface.
3737
- [Example](#example)
3838
- [Option options](#option-options)
3939
- [Validators](#validators)
40+
- [Default Validators](#default-validators)
41+
- [Validatrs that may be disabled 🚧](#validatrs-that-may-be-disabled-)
42+
- [Extra Validators 🚧](#extra-validators-)
43+
- [Validator Usage](#validator-usage)
4044
- [Transforming Validators](#transforming-validators)
4145
- [Validator operations](#validator-operations)
4246
- [Custom Validators](#custom-validators)
@@ -561,7 +565,36 @@ are added through the `check` or `transform` functions. The differences between
561565
the two function are that checks do not modify the input whereas transforms can
562566
and are executed before any Validators added through `check`.
563567

564-
CLI11 has several Validators built-in that perform some common checks
568+
CLI11 has several Validators included that perform some common checks. By
569+
default the most commonly used ones are available. 🚧 If some are not needed
570+
they can be disabled by using
571+
572+
```c++
573+
#define CLI11_DISABLE_EXTRA_VALIDATORS 1
574+
```
575+
576+
#### Default Validators
577+
578+
These validators are always available regardless of definitions
579+
580+
- `CLI::ExistingFile`: Requires that the file exists if given.
581+
- `CLI::ExistingDirectory`: Requires that the directory exists.
582+
- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
583+
- `CLI::NonexistentPath`: Requires that the path does not exist.
584+
- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file
585+
exists either directly or in a default path and update the path appropriately.
586+
See [Transforming Validators](#transforming-validators) for more details
587+
- `CLI::Range(min,max)`: Requires that the option be between min and max (make
588+
sure to use floating point if needed). Min defaults to 0.
589+
- `CLI::PositiveNumber`: Requires the number be greater than 0
590+
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
591+
- `CLI::Number`: Requires the input be a number.
592+
593+
#### Validatrs that may be disabled 🚧
594+
595+
Validators that may be disabled by setting `CLI11_DISABLE_EXTRA_VALIDATORS` to 1
596+
or enabled by setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1. By default they are
597+
enabled.
565598
566599
- `CLI::IsMember(...)`: Require an option be a member of a given set. See
567600
[Transforming Validators](#transforming-validators) for more details.
@@ -577,30 +610,27 @@ CLI11 has several Validators built-in that perform some common checks
577610
- `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`,
578611
`11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3
579612
or 2^10.
580-
- `CLI::ExistingFile`: Requires that the file exists if given.
581-
- `CLI::ExistingDirectory`: Requires that the directory exists.
582-
- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
583-
- `CLI::NonexistentPath`: Requires that the path does not exist.
584-
- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file
585-
exists either directly or in a default path and update the path appropriately.
586-
See [Transforming Validators](#transforming-validators) for more details
587-
- `CLI::Range(min,max)`: Requires that the option be between min and max (make
588-
sure to use floating point if needed). Min defaults to 0.
613+
589614
- `CLI::Bounded(min,max)`: Modify the input such that it is always between min
590615
and max (make sure to use floating point if needed). Min defaults to 0. Will
591616
produce an error if conversion is not possible.
592-
- `CLI::PositiveNumber`: Requires the number be greater than 0
593-
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
594-
- `CLI::Number`: Requires the input be a number.
617+
595618
- `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g.
596619
`'255.255.255.255'`, `'10.1.1.7'`.
597620
- `CLI::TypeValidator<TYPE>`:Requires that the option be convertible to the
598621
specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that
599622
the input be convertible to an `unsigned int` regardless of the end
600623
conversion.
601624
602-
These Validators can be used by simply passing the name into the `check` or
603-
`transform` methods on an option
625+
#### Extra Validators 🚧
626+
627+
New validators will go into code sections that must be explicitly enabled by
628+
setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1
629+
630+
#### Validator Usage
631+
632+
These Validators once enabled can be used by simply passing the name into the
633+
`check` or `transform` methods on an option
604634
605635
```cpp
606636
->check(CLI::ExistingFile);
@@ -764,14 +794,22 @@ CLI::Validator(validator_description);
764794
```
765795
766796
It is also possible to create a subclass of `CLI::Validator`, in which case it
767-
can also set a custom description function, and operation function.
797+
can also set a custom description function, and operation function. One example
798+
of this is in the
799+
[custom validator example](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp).
800+
example. The `check` and `transform` operations can also take a shared_ptr 🚧 to
801+
a validator if you wish to reuse the validator in multiple locations or it is
802+
mutating and the check is dependent on other operations or is variable. Note
803+
that in this case it is not recommended to use the same object for both check
804+
and transform operations, the check will modify some internal flags on the
805+
object so it will not be usable for transform operations.
768806
769807
##### Querying Validators
770808
771809
Once loaded into an Option, a pointer to a named Validator can be retrieved via
772810
773811
```cpp
774-
opt->get_validator(name);
812+
auto *validator = opt->get_validator(name);
775813
```
776814

777815
This will retrieve a Validator with the given name or throw a
@@ -781,7 +819,7 @@ unnamed Validator will be returned or the first Validator if there is only one.
781819
or
782820

783821
```cpp
784-
opt->get_validator(index);
822+
auto *validator = opt->get_validator(index);
785823
```
786824
787825
Which will return a validator in the index it is applied which isn't necessarily
@@ -1669,6 +1707,10 @@ brief description of each is included here
16691707
Short example of subcommands
16701708
- [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp):
16711709
Example illustrating use of validators
1710+
- [custom validators](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validators.cpp):
1711+
Example illustrating use of validators
1712+
- [date validators](https://github.com/CLIUtils/CLI11/blob/main/examples/date_validators.cpp):
1713+
Example illustrating use of validators
16721714
16731715
## Contribute
16741716

azure-pipelines.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ jobs:
7878
Windows20:
7979
vmImage: "windows-2025"
8080
cli11.std: 20
81-
cli11.options: -DCMAKE_CXX_FLAGS="/EHsc"
81+
cli11.options:
82+
-DCMAKE_CXX_FLAGS="/EHsc" -DCLI11_DISABLE_EXTRA_VALIDATORS=1
8283
WindowsLatest:
8384
vmImage: "windows-2025"
8485
cli11.std: 23
@@ -129,7 +130,9 @@ jobs:
129130
gcc11:
130131
containerImage: gcc:11
131132
cli11.std: 20
132-
cli11.options: -DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion"
133+
cli11.options:
134+
-DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion"
135+
-DCLI11_DISABLE_EXTRA_VALIDATORS=1
133136
gcc7:
134137
containerImage: gcc:7
135138
cli11.std: 14

book/chapters/validators.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ The built-in validators for CLI11 are:
7171
| `ExistingPath` | Check for an existing path |
7272
| `NonexistentPath` | Check for an non-existing path |
7373
| `Range(min=0, max)` | Produce a range (factory). Min and max are inclusive. |
74+
| `NonNegativeNumber` | Range(0,max<double>) |
75+
| `PositiveNumber` | Range(epsilon,max<double>) |
76+
77+
A few built-in transformers are also available
78+
79+
| Transformer | Description |
80+
| ------------------- | ---------------------------------------------------------- |
81+
| `EscapedString` | modify a string using defined escape characters |
82+
| `FileOnDefaultPath` | Modify a path if the file is a particular default location |
7483

7584
And, the protected members that you can set when you make your own are:
7685

@@ -82,3 +91,141 @@ And, the protected members that you can set when you make your own are:
8291
| `int` (`-1`) | `application_index_` | The element this validator applies to (-1 for all) |
8392
| `bool` (`true`) | `active_` | This can be disabled |
8493
| `bool` (`false`) | `non_modifying_` | Specify that this is a Validator instead of a Transformer |
94+
95+
## Extra Validators
96+
97+
Until CLI11 v3.0 these validators will be available by default. They can be
98+
disabled at compilation time by defining CLI11_DISABLE_EXTRA_VALIDATORS to 1.
99+
After version 3.0 they can be enabled by defining CLI11_ENABLE_EXTRA_VALIDATORS
100+
to 1. Some of the Validators are template heavy so if they are not needed and
101+
compilation time is a concern they can be disabled.
102+
103+
| Validator | Description |
104+
| -------------------- | ------------------------------------------------------------------ |
105+
| `ValidIPV4` | check for valid IPV4 address XX.XX.XX.XX |
106+
| `TypeValidator<T>` | template for checking that a value can convert to a specific type |
107+
| `Number` | Check that a value can convert to a number |
108+
| `IsMember` | Check that a value is one of a set of values |
109+
| `CheckedTransformer` | Values must be one of the transformed set or the result |
110+
| `AsNumberWithUnit` | checks for numbers with a unit as part of a specified set of units |
111+
| `AsSizeValue` | As Number with Unit with support for SI prefixes |
112+
113+
| Transformer | Description |
114+
| ---------------------- | --------------------------------------------------- |
115+
| `Bound<T>(min=0, max)` | Force a range (factory). Min and max are inclusive. |
116+
| `Transformer` | Modify values in a set to the matching pair value |
117+
118+
## New Extra Validators
119+
120+
Some additional validators can be enabled by using CLI11_ENABLE_EXTRA_VALIDATORS
121+
to 1. These validators are disabled by default.
122+
123+
## Custom Validators
124+
125+
CLI11 also supports the use of custom validators, this includes using the
126+
Validator class constructor with a custom function calls or subclassing
127+
Validator to define a new class.
128+
129+
### Custom Validator operation
130+
131+
The simplest way to make a new Validator is to mimic how many of the existing
132+
Validators are created. Take for example the `IPV4Validator`
133+
134+
```cpp
135+
class IPV4Validator : public Validator {
136+
public:
137+
IPV4Validator();
138+
};
139+
140+
CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") {
141+
func_ = [](std::string &ip_addr) {
142+
auto cdot = std::count(ip_addr.begin(), ip_addr.end(), '.');
143+
if(cdot != 3u) {
144+
return std::string("Invalid IPV4 address: must have 3 separators");
145+
}
146+
auto result = CLI::detail::split(ip_addr, '.');
147+
if(result.size() != 4) {
148+
return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')';
149+
}
150+
int num = 0;
151+
for(const auto &var : result) {
152+
using CLI::detail::lexical_cast;
153+
bool retval = lexical_cast(var, num);
154+
if(!retval) {
155+
return std::string("Failed parsing number (") + var + ')';
156+
}
157+
if(num < 0 || num > 255) {
158+
return std::string("Each IP number must be between 0 and 255 ") + var;
159+
}
160+
}
161+
return std::string{};
162+
};
163+
}
164+
```
165+
166+
The `IPV4Validator` class inherits from `Validator` and creates a new
167+
constructor. In that constructor it defines the lambda function that does the
168+
checking. Then IPV4 can be used like any other Validator. One specific item of
169+
note is that the class does not define any new member variables, so the class if
170+
copyable to a Validator, only the constructor is different.
171+
172+
If additional members are needed, then the `check` and `transform` overloads
173+
that use shared pointers need to be used. The other overloads pass by value so
174+
polymorphism doesn't work. The custom_validator example shows a case like this.
175+
176+
```cpp
177+
template <typename T> class DeltaRange : public CLI::Validator {
178+
public:
179+
T center_point;
180+
T delta;
181+
DeltaRange(const T &center, const T &range)
182+
: CLI::Validator(
183+
[this](const std::string &value) -> std::string {
184+
T newValue;
185+
auto result = CLI::detail::lexical_cast(value, newValue);
186+
if(!(result && this->check(newValue))) {
187+
return std::string("value not within range");
188+
}
189+
return std::string{};
190+
},
191+
"RANGE"),
192+
center_point(center), delta(range) {}
193+
194+
CLI11_NODISCARD bool check(const T &test) const { return (test >= (center_point - delta)) && (test <= (center_point + delta)); }
195+
CLI11_NODISCARD T center() const { return center_point; }
196+
CLI11_NODISCARD T range() const { return delta; }
197+
void center(const T &value) { center_point = value; }
198+
void range(const T &value) { delta = value; }
199+
};
200+
201+
int main(int argc, char **argv) {
202+
/* this application creates custom validator which is a range center+/- range The center and range can be defined by
203+
* other command line options and are updated dynamically
204+
*/
205+
CLI::App app("custom range validator");
206+
207+
std::string value;
208+
auto dr = std::make_shared<DeltaRange<int>>(7, 3);
209+
app.add_option("--number", value, "enter value in the related range")->check(dr)->required();
210+
211+
app.add_option_function<int>("--center", [&dr](int new_center) { dr->center(new_center); })->trigger_on_parse();
212+
app.add_option_function<int>("--range", [&dr](int new_range) { dr->range(new_range); })->trigger_on_parse();
213+
214+
CLI11_PARSE(app, argc, argv);
215+
216+
std::cout << "number " << value << " in range = " << dr->center() << " +/- " << dr->range() << '\n';
217+
218+
return 0;
219+
}
220+
```
221+
222+
The Validator defines some new operations, and in the use case the Validator is
223+
constructed using shared_ptrs. This allows polymorphism to work and the
224+
Validator instance to be shared across multiple options, and as in this example
225+
adapted during the parsing and checking.
226+
227+
There are a few limitation in this, single instances should not be used with
228+
both transform and check. Check modifies some flags in the Validator to prevent
229+
value modification, so that would prevent its use as a transform. Which could be
230+
user modified later but that would potentially allow the check to modify the
231+
value unintentionally.

0 commit comments

Comments
 (0)