Skip to content

move some of the Validators to an ExtraValidators file #1192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
945f38c
move some of the Validators to an ExtraValidators file, and add compi…
phlptp Aug 10, 2025
3ac94f0
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 10, 2025
7039c46
get the single header stuff working with the ExtraValidators
phlptp Aug 10, 2025
ccff293
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 10, 2025
a60098a
reorder compile condition
phlptp Aug 11, 2025
091505a
update some test cases
phlptp Aug 11, 2025
105ba41
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 11, 2025
c1c7bd3
move some more validators around
phlptp Aug 12, 2025
8313968
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 12, 2025
7374fdc
more moving validators around
phlptp Aug 13, 2025
be9fa64
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 13, 2025
0bc38ed
update BAZEL test and add a test to setTest
phlptp Aug 13, 2025
c8e2711
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 13, 2025
39548ab
update the tests
phlptp Aug 13, 2025
8073f05
update more tests
phlptp Aug 13, 2025
a0e03da
add custom validator example
phlptp Aug 14, 2025
0699f2e
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 14, 2025
394638f
move validators to use shared_ptrs internally to allow external coord…
phlptp Aug 16, 2025
d487abf
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 16, 2025
58e0f45
add additional tests and fix the custom_validator tests
phlptp Aug 16, 2025
d67766b
exclude an example for GCC 4.8 and 4.9
phlptp Aug 16, 2025
24e83de
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 16, 2025
b350539
fix some clang-tidy issues
phlptp Aug 16, 2025
d33feb6
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 16, 2025
a975555
start work on the documentation for validators
phlptp Aug 17, 2025
03ce593
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 17, 2025
d5cb058
add some more tests for coverage
phlptp Aug 17, 2025
46d1242
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 17, 2025
2cf2797
add some more documentation and a few other fixes
phlptp Aug 18, 2025
d4f4578
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 18, 2025
0d06599
add example custom Validators to the book
phlptp Aug 19, 2025
4b5af60
style: pre-commit.ci fixes
pre-commit-ci[bot] Aug 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ engines:
coverage:
enabled: false
cppcheck:
enabled: false
language: c++
languages:

Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ jobs:
-DCLI11_SINGLE_FILE_TESTS=OFF \
-DCLI11_BUILD_EXAMPLES=OFF \
-DCLI11_PRECOMPILED=${{matrix.precompile}} \
-DCMAKE_BUILD_TYPE=Coverage
-DCMAKE_BUILD_TYPE=Coverage \
-DCLI11_ENABLE_EXTRA_VALIDATORS=ON

- name: Build
run: cmake --build build -j4
Expand Down Expand Up @@ -233,7 +234,7 @@ jobs:
with:
submodules: true
- name: Configure
run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_PRECOMPILED=ON
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
- name: Build
run: cmake --build build -j2
- name: install
Expand All @@ -250,7 +251,7 @@ jobs:
with:
submodules: true
- name: Configure
run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_SINGLE_FILE=ON
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
- name: Build
run: cmake --build build -j2
- name: install
Expand All @@ -274,7 +275,7 @@ jobs:
- name: Check CMake 3.15
uses: ./.github/actions/quick_cmake
with:
cmake-version: "3.15"
cmake-version: "3.15.6"
if: success() || failure()

- name: Check CMake 3.16
Expand Down
2 changes: 1 addition & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cc_library(
name = "cli11",
srcs = glob(["src/**/*.cpp"]),
hdrs = glob(["include/**/*.hpp"]),
local_defines = ["CLI11_COMPILE"],
local_defines = ["CLI11_COMPILE", "CLI11_ENABLE_EXTRA_VALIDATORS=1"],
strip_include_prefix = "/include",
visibility = ["//visibility:public"],
)
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ set(CLI11_headers
${CLI11_headerLoc}/StringTools.hpp
${CLI11_headerLoc}/TypeTools.hpp
${CLI11_headerLoc}/Validators.hpp
${CLI11_headerLoc}/ExtraValidators.hpp
${CLI11_headerLoc}/Version.hpp
${CLI11_headerLoc}/Encoding.hpp
${CLI11_headerLoc}/Argv.hpp)
Expand All @@ -149,6 +150,7 @@ set(CLI11_impl_headers
${CLI11_headerLoc}/impl/Split_inl.hpp
${CLI11_headerLoc}/impl/StringTools_inl.hpp
${CLI11_headerLoc}/impl/Validators_inl.hpp
${CLI11_headerLoc}/impl/ExtraValidators_inl.hpp
${CLI11_headerLoc}/impl/Encoding_inl.hpp
${CLI11_headerLoc}/impl/Argv_inl.hpp)

Expand Down
78 changes: 60 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ set with a simple and intuitive interface.
- [Example](#example)
- [Option options](#option-options)
- [Validators](#validators)
- [Default Validators](#default-validators)
- [Validatrs that may be disabled 🚧](#validatrs-that-may-be-disabled-)
- [Extra Validators 🚧](#extra-validators-)
- [Validator Usage](#validator-usage)
- [Transforming Validators](#transforming-validators)
- [Validator operations](#validator-operations)
- [Custom Validators](#custom-validators)
Expand Down Expand Up @@ -561,7 +565,36 @@ are added through the `check` or `transform` functions. The differences between
the two function are that checks do not modify the input whereas transforms can
and are executed before any Validators added through `check`.

CLI11 has several Validators built-in that perform some common checks
CLI11 has several Validators included that perform some common checks. By
default the most commonly used ones are available. 🚧 If some are not needed
they can be disabled by using

```c++
#define CLI11_DISABLE_EXTRA_VALIDATORS 1
```

#### Default Validators

These validators are always available regardless of definitions

- `CLI::ExistingFile`: Requires that the file exists if given.
- `CLI::ExistingDirectory`: Requires that the directory exists.
- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
- `CLI::NonexistentPath`: Requires that the path does not exist.
- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file
exists either directly or in a default path and update the path appropriately.
See [Transforming Validators](#transforming-validators) for more details
- `CLI::Range(min,max)`: Requires that the option be between min and max (make
sure to use floating point if needed). Min defaults to 0.
- `CLI::PositiveNumber`: Requires the number be greater than 0
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
- `CLI::Number`: Requires the input be a number.

#### Validatrs that may be disabled 🚧

Validators that may be disabled by setting `CLI11_DISABLE_EXTRA_VALIDATORS` to 1
or enabled by setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1. By default they are
enabled.

- `CLI::IsMember(...)`: Require an option be a member of a given set. See
[Transforming Validators](#transforming-validators) for more details.
Expand All @@ -577,30 +610,27 @@ CLI11 has several Validators built-in that perform some common checks
- `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`,
`11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3
or 2^10.
- `CLI::ExistingFile`: Requires that the file exists if given.
- `CLI::ExistingDirectory`: Requires that the directory exists.
- `CLI::ExistingPath`: Requires that the path (file or directory) exists.
- `CLI::NonexistentPath`: Requires that the path does not exist.
- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file
exists either directly or in a default path and update the path appropriately.
See [Transforming Validators](#transforming-validators) for more details
- `CLI::Range(min,max)`: Requires that the option be between min and max (make
sure to use floating point if needed). Min defaults to 0.

- `CLI::Bounded(min,max)`: Modify the input such that it is always between min
and max (make sure to use floating point if needed). Min defaults to 0. Will
produce an error if conversion is not possible.
- `CLI::PositiveNumber`: Requires the number be greater than 0
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
- `CLI::Number`: Requires the input be a number.

- `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g.
`'255.255.255.255'`, `'10.1.1.7'`.
- `CLI::TypeValidator<TYPE>`:Requires that the option be convertible to the
specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that
the input be convertible to an `unsigned int` regardless of the end
conversion.

These Validators can be used by simply passing the name into the `check` or
`transform` methods on an option
#### Extra Validators 🚧

New validators will go into code sections that must be explicitly enabled by
setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1

#### Validator Usage

These Validators once enabled can be used by simply passing the name into the
`check` or `transform` methods on an option

```cpp
->check(CLI::ExistingFile);
Expand Down Expand Up @@ -764,14 +794,22 @@ CLI::Validator(validator_description);
```

It is also possible to create a subclass of `CLI::Validator`, in which case it
can also set a custom description function, and operation function.
can also set a custom description function, and operation function. One example
of this is in the
[custom validator example](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp).
example. The `check` and `transform` operations can also take a shared_ptr 🚧 to
a validator if you wish to reuse the validator in multiple locations or it is
mutating and the check is dependent on other operations or is variable. Note
that in this case it is not recommended to use the same object for both check
and transform operations, the check will modify some internal flags on the
object so it will not be usable for transform operations.

##### Querying Validators

Once loaded into an Option, a pointer to a named Validator can be retrieved via

```cpp
opt->get_validator(name);
auto *validator = opt->get_validator(name);
```

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

```cpp
opt->get_validator(index);
auto *validator = opt->get_validator(index);
```

Which will return a validator in the index it is applied which isn't necessarily
Expand Down Expand Up @@ -1669,6 +1707,10 @@ brief description of each is included here
Short example of subcommands
- [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp):
Example illustrating use of validators
- [custom validators](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validators.cpp):
Example illustrating use of validators
- [date validators](https://github.com/CLIUtils/CLI11/blob/main/examples/date_validators.cpp):
Example illustrating use of validators

## Contribute

Expand Down
7 changes: 5 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ jobs:
Windows20:
vmImage: "windows-2025"
cli11.std: 20
cli11.options: -DCMAKE_CXX_FLAGS="/EHsc"
cli11.options:
-DCMAKE_CXX_FLAGS="/EHsc" -DCLI11_DISABLE_EXTRA_VALIDATORS=1
WindowsLatest:
vmImage: "windows-2025"
cli11.std: 23
Expand Down Expand Up @@ -129,7 +130,9 @@ jobs:
gcc11:
containerImage: gcc:11
cli11.std: 20
cli11.options: -DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion"
cli11.options:
-DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion"
-DCLI11_DISABLE_EXTRA_VALIDATORS=1
gcc7:
containerImage: gcc:7
cli11.std: 14
Expand Down
147 changes: 147 additions & 0 deletions book/chapters/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ The built-in validators for CLI11 are:
| `ExistingPath` | Check for an existing path |
| `NonexistentPath` | Check for an non-existing path |
| `Range(min=0, max)` | Produce a range (factory). Min and max are inclusive. |
| `NonNegativeNumber` | Range(0,max<double>) |
| `PositiveNumber` | Range(epsilon,max<double>) |

A few built-in transformers are also available

| Transformer | Description |
| ------------------- | ---------------------------------------------------------- |
| `EscapedString` | modify a string using defined escape characters |
| `FileOnDefaultPath` | Modify a path if the file is a particular default location |

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

Expand All @@ -82,3 +91,141 @@ And, the protected members that you can set when you make your own are:
| `int` (`-1`) | `application_index_` | The element this validator applies to (-1 for all) |
| `bool` (`true`) | `active_` | This can be disabled |
| `bool` (`false`) | `non_modifying_` | Specify that this is a Validator instead of a Transformer |

## Extra Validators

Until CLI11 v3.0 these validators will be available by default. They can be
disabled at compilation time by defining CLI11_DISABLE_EXTRA_VALIDATORS to 1.
After version 3.0 they can be enabled by defining CLI11_ENABLE_EXTRA_VALIDATORS
to 1. Some of the Validators are template heavy so if they are not needed and
compilation time is a concern they can be disabled.

| Validator | Description |
| -------------------- | ------------------------------------------------------------------ |
| `ValidIPV4` | check for valid IPV4 address XX.XX.XX.XX |
| `TypeValidator<T>` | template for checking that a value can convert to a specific type |
| `Number` | Check that a value can convert to a number |
| `IsMember` | Check that a value is one of a set of values |
| `CheckedTransformer` | Values must be one of the transformed set or the result |
| `AsNumberWithUnit` | checks for numbers with a unit as part of a specified set of units |
| `AsSizeValue` | As Number with Unit with support for SI prefixes |

| Transformer | Description |
| ---------------------- | --------------------------------------------------- |
| `Bound<T>(min=0, max)` | Force a range (factory). Min and max are inclusive. |
| `Transformer` | Modify values in a set to the matching pair value |

## New Extra Validators

Some additional validators can be enabled by using CLI11_ENABLE_EXTRA_VALIDATORS
to 1. These validators are disabled by default.

## Custom Validators

CLI11 also supports the use of custom validators, this includes using the
Validator class constructor with a custom function calls or subclassing
Validator to define a new class.

### Custom Validator operation

The simplest way to make a new Validator is to mimic how many of the existing
Validators are created. Take for example the `IPV4Validator`

```cpp
class IPV4Validator : public Validator {
public:
IPV4Validator();
};

CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") {
func_ = [](std::string &ip_addr) {
auto cdot = std::count(ip_addr.begin(), ip_addr.end(), '.');
if(cdot != 3u) {
return std::string("Invalid IPV4 address: must have 3 separators");
}
auto result = CLI::detail::split(ip_addr, '.');
if(result.size() != 4) {
return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')';
}
int num = 0;
for(const auto &var : result) {
using CLI::detail::lexical_cast;
bool retval = lexical_cast(var, num);
if(!retval) {
return std::string("Failed parsing number (") + var + ')';
}
if(num < 0 || num > 255) {
return std::string("Each IP number must be between 0 and 255 ") + var;
}
}
return std::string{};
};
}
```

The `IPV4Validator` class inherits from `Validator` and creates a new
constructor. In that constructor it defines the lambda function that does the
checking. Then IPV4 can be used like any other Validator. One specific item of
note is that the class does not define any new member variables, so the class if
copyable to a Validator, only the constructor is different.

If additional members are needed, then the `check` and `transform` overloads
that use shared pointers need to be used. The other overloads pass by value so
polymorphism doesn't work. The custom_validator example shows a case like this.

```cpp
template <typename T> class DeltaRange : public CLI::Validator {
public:
T center_point;
T delta;
DeltaRange(const T &center, const T &range)
: CLI::Validator(
[this](const std::string &value) -> std::string {
T newValue;
auto result = CLI::detail::lexical_cast(value, newValue);
if(!(result && this->check(newValue))) {
return std::string("value not within range");
}
return std::string{};
},
"RANGE"),
center_point(center), delta(range) {}

CLI11_NODISCARD bool check(const T &test) const { return (test >= (center_point - delta)) && (test <= (center_point + delta)); }
CLI11_NODISCARD T center() const { return center_point; }
CLI11_NODISCARD T range() const { return delta; }
void center(const T &value) { center_point = value; }
void range(const T &value) { delta = value; }
};

int main(int argc, char **argv) {
/* this application creates custom validator which is a range center+/- range The center and range can be defined by
* other command line options and are updated dynamically
*/
CLI::App app("custom range validator");

std::string value;
auto dr = std::make_shared<DeltaRange<int>>(7, 3);
app.add_option("--number", value, "enter value in the related range")->check(dr)->required();

app.add_option_function<int>("--center", [&dr](int new_center) { dr->center(new_center); })->trigger_on_parse();
app.add_option_function<int>("--range", [&dr](int new_range) { dr->range(new_range); })->trigger_on_parse();

CLI11_PARSE(app, argc, argv);

std::cout << "number " << value << " in range = " << dr->center() << " +/- " << dr->range() << '\n';

return 0;
}
```

The Validator defines some new operations, and in the use case the Validator is
constructed using shared_ptrs. This allows polymorphism to work and the
Validator instance to be shared across multiple options, and as in this example
adapted during the parsing and checking.

There are a few limitation in this, single instances should not be used with
both transform and check. Check modifies some flags in the Validator to prevent
value modification, so that would prevent its use as a transform. Which could be
user modified later but that would potentially allow the check to modify the
value unintentionally.
Loading
Loading