mirror of
https://github.com/CLIUtils/CLI11.git
synced 2026-01-19 04:52:08 +00:00
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>
This commit is contained in:
@@ -9,6 +9,7 @@ engines:
|
||||
coverage:
|
||||
enabled: false
|
||||
cppcheck:
|
||||
enabled: false
|
||||
language: c++
|
||||
languages:
|
||||
|
||||
|
||||
9
.github/workflows/tests.yml
vendored
9
.github/workflows/tests.yml
vendored
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
78
README.md
78
README.md
@@ -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)
|
||||
@@ -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.
|
||||
@@ -577,21 +610,11 @@ 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
|
||||
@@ -599,8 +622,15 @@ CLI11 has several Validators built-in that perform some common checks
|
||||
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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 ¢er, 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.
|
||||
|
||||
@@ -122,6 +122,21 @@ set_property(TEST positional_validation3 PROPERTY PASS_REGULAR_EXPRESSION "File
|
||||
add_test(NAME positional_validation4 COMMAND positional_validation 1 one two 2)
|
||||
set_property(TEST positional_validation4 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one"
|
||||
"File 2 = two")
|
||||
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
||||
OR NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
#this test doesn't work without get_time which isn't available until GCC 5.0
|
||||
add_cli_exe(date_validator date_validator.cpp)
|
||||
add_test(NAME date_validator1 COMMAND date_validator --time 2025-08-14T14:30:00)
|
||||
set_property(TEST date_validator1 PROPERTY PASS_REGULAR_EXPRESSION "date given")
|
||||
add_test(NAME date_validator2 COMMAND date_validator --time 8/17/2025T14:30:00)
|
||||
set_property(TEST date_validator2 PROPERTY PASS_REGULAR_EXPRESSION "Failed to parse time string")
|
||||
endif()
|
||||
|
||||
add_cli_exe(custom_validator custom_validator.cpp)
|
||||
add_test(NAME custom_validator1 COMMAND custom_validator --number 12)
|
||||
set_property(TEST custom_validator1 PROPERTY PASS_REGULAR_EXPRESSION "value not within range")
|
||||
add_test(NAME custom_validator2 COMMAND custom_validator --number 12 --center 10 --range 3)
|
||||
set_property(TEST custom_validator2 PROPERTY PASS_REGULAR_EXPRESSION "number 12 in range")
|
||||
|
||||
add_cli_exe(shapes shapes.cpp)
|
||||
add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle
|
||||
|
||||
56
examples/custom_validator.cpp
Normal file
56
examples/custom_validator.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
template <typename T> class DeltaRange : public CLI::Validator {
|
||||
public:
|
||||
T center_point;
|
||||
T delta;
|
||||
DeltaRange(const T ¢er, 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;
|
||||
}
|
||||
40
examples/date_validator.cpp
Normal file
40
examples/date_validator.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
// Custom validator is an alias of Validator, the constructor takes a function that takes as input and returns a string
|
||||
const CLI::CustomValidator ISO8601(
|
||||
[](std::string &input) {
|
||||
std::tm tm = {};
|
||||
std::istringstream ss(input);
|
||||
|
||||
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
||||
if(ss.fail()) {
|
||||
return std::string("Failed to parse time string");
|
||||
}
|
||||
return std::string{};
|
||||
},
|
||||
"datetime[%Y-%m-%dT%H:%M:%S]");
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
CLI::App app("custom validator testing");
|
||||
|
||||
std::string value;
|
||||
app.add_option("--time", value, "enter a date in iso8601 format")->check(ISO8601)->required();
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
std::cout << "date given = " << value << '\n';
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
|
||||
|
||||
#include "fuzzApp.hpp"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -150,6 +152,7 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
|
||||
->check(CLI::Range(std::string("aa"), std::string("zz"), "string range"));
|
||||
vldtr->add_option("--vdtr6", validator_strings[5])->join()->check(CLI::TypeValidator<double>());
|
||||
vldtr->add_option("--vdtr7", validator_strings[6])->join()->check(CLI::TypeValidator<bool>());
|
||||
|
||||
vldtr->add_option("--vdtr8", validator_strings[7])->join()->check(CLI::ValidIPV4);
|
||||
vldtr->add_option("--vdtr9", validator_strings[8])->join()->transform(CLI::Bound(2, 255));
|
||||
return fApp;
|
||||
|
||||
@@ -41,4 +41,6 @@
|
||||
|
||||
#include "Formatter.hpp"
|
||||
|
||||
#include "ExtraValidators.hpp"
|
||||
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
603
include/CLI/ExtraValidators.hpp
Normal file
603
include/CLI/ExtraValidators.hpp
Normal file
@@ -0,0 +1,603 @@
|
||||
// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
// IWYU pragma: private, include "CLI/CLI.hpp"
|
||||
|
||||
#include "Error.hpp"
|
||||
#include "Macros.hpp"
|
||||
#include "StringTools.hpp"
|
||||
#include "Validators.hpp"
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:extra_validators_hpp:verbatim]
|
||||
// The implementation of the extra validators is using the Validator class;
|
||||
// the user is only expected to use the const (static) versions (since there's no setup).
|
||||
// Therefore, this is in detail.
|
||||
namespace detail {
|
||||
|
||||
/// Validate the given string is a legal ipv4 address
|
||||
class IPV4Validator : public Validator {
|
||||
public:
|
||||
IPV4Validator();
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Validate the input as a particular type
|
||||
template <typename DesiredType> class TypeValidator : public Validator {
|
||||
public:
|
||||
explicit TypeValidator(const std::string &validator_name)
|
||||
: Validator(validator_name, [](std::string &input_string) {
|
||||
using CLI::detail::lexical_cast;
|
||||
auto val = DesiredType();
|
||||
if(!lexical_cast(input_string, val)) {
|
||||
return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
|
||||
}
|
||||
return std::string{};
|
||||
}) {}
|
||||
TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
|
||||
};
|
||||
|
||||
/// Check for a number
|
||||
const TypeValidator<double> Number("NUMBER");
|
||||
|
||||
/// Produce a bounded range (factory). Min and max are inclusive.
|
||||
class Bound : public Validator {
|
||||
public:
|
||||
/// This bounds a value with min and max inclusive.
|
||||
///
|
||||
/// Note that the constructor is templated, but the struct is not, so C++17 is not
|
||||
/// needed to provide nice syntax for Range(a,b).
|
||||
template <typename T> Bound(T min_val, T max_val) {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]";
|
||||
description(out.str());
|
||||
|
||||
func_ = [min_val, max_val](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
T val;
|
||||
bool converted = lexical_cast(input, val);
|
||||
if(!converted) {
|
||||
return std::string("Value ") + input + " could not be converted";
|
||||
}
|
||||
if(val < min_val)
|
||||
input = detail::to_string(min_val);
|
||||
else if(val > max_val)
|
||||
input = detail::to_string(max_val);
|
||||
|
||||
return std::string{};
|
||||
};
|
||||
}
|
||||
|
||||
/// Range of one value is 0 to value
|
||||
template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {}
|
||||
};
|
||||
|
||||
// Static is not needed here, because global const implies static.
|
||||
|
||||
/// Check for an IP4 address
|
||||
const detail::IPV4Validator ValidIPV4;
|
||||
|
||||
namespace detail {
|
||||
template <typename T,
|
||||
enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
|
||||
auto smart_deref(T value) -> decltype(*value) {
|
||||
return *value;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
|
||||
typename std::remove_reference<T>::type &smart_deref(T &value) {
|
||||
// NOLINTNEXTLINE
|
||||
return value;
|
||||
}
|
||||
/// Generate a string representation of a set
|
||||
template <typename T> std::string generate_set(const T &set) {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
std::string out(1, '{');
|
||||
out.append(detail::join(
|
||||
detail::smart_deref(set),
|
||||
[](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
|
||||
","));
|
||||
out.push_back('}');
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Generate a string representation of a map
|
||||
template <typename T> std::string generate_map(const T &map, bool key_only = false) {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
std::string out(1, '{');
|
||||
out.append(detail::join(
|
||||
detail::smart_deref(map),
|
||||
[key_only](const iteration_type_t &v) {
|
||||
std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
|
||||
|
||||
if(!key_only) {
|
||||
res.append("->");
|
||||
res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
","));
|
||||
out.push_back('}');
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename C, typename V> struct has_find {
|
||||
template <typename CC, typename VV>
|
||||
static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
|
||||
template <typename, typename> static auto test(...) -> decltype(std::false_type());
|
||||
|
||||
static const auto value = decltype(test<C, V>(0))::value;
|
||||
using type = std::integral_constant<bool, value>;
|
||||
};
|
||||
|
||||
/// A search function
|
||||
template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
|
||||
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
|
||||
return (detail::pair_adaptor<element_t>::first(v) == val);
|
||||
});
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// A search function that uses the built in find function
|
||||
template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
|
||||
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = setref.find(val);
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// A search function with a filter function
|
||||
template <typename T, typename V>
|
||||
auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
|
||||
-> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
// do the potentially faster first search
|
||||
auto res = search(set, val);
|
||||
if((res.first) || (!(filter_function))) {
|
||||
return res;
|
||||
}
|
||||
// if we haven't found it do the longer linear search with all the element translations
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
|
||||
V a{detail::pair_adaptor<element_t>::first(v)};
|
||||
a = filter_function(a);
|
||||
return (a == val);
|
||||
});
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
/// Verify items are in a set
|
||||
class IsMember : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction using an initializer list
|
||||
template <typename T, typename... Args>
|
||||
IsMember(std::initializer_list<T> values, Args &&...args)
|
||||
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// This checks to see if an item is in a set (empty function)
|
||||
template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit IsMember(T set, F filter_function) {
|
||||
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
|
||||
|
||||
// This is the function that validates
|
||||
// It stores a copy of the set pointer-like, so shared_ptr will stay alive
|
||||
func_ = [set, filter_fn](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
local_item_t b;
|
||||
if(!lexical_cast(input, b)) {
|
||||
throw ValidationError(input); // name is added later
|
||||
}
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(set, b, filter_fn);
|
||||
if(res.first) {
|
||||
// Make sure the version in the input string is identical to the one in the set
|
||||
if(filter_fn) {
|
||||
input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
|
||||
}
|
||||
|
||||
// Return empty error string (success)
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
// If you reach this point, the result was not found
|
||||
return input + " not in " + detail::generate_set(detail::smart_deref(set));
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest (string only currently)
|
||||
template <typename T, typename... Args>
|
||||
IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
|
||||
: IsMember(
|
||||
std::forward<T>(set),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// definition of the default transformation object
|
||||
template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
|
||||
|
||||
/// Translate named items to other or a value set
|
||||
class Transformer : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction
|
||||
template <typename... Args>
|
||||
Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
|
||||
: Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// direct map of std::string to std::string
|
||||
template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
|
||||
|
||||
static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
|
||||
"mapping must produce value pairs");
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
|
||||
|
||||
func_ = [mapping, filter_fn](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
local_item_t b;
|
||||
if(!lexical_cast(input, b)) {
|
||||
return std::string();
|
||||
// there is no possible way we can match anything in the mapping if we can't convert so just return
|
||||
}
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(mapping, b, filter_fn);
|
||||
if(res.first) {
|
||||
input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||
}
|
||||
return std::string{};
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
template <typename T, typename... Args>
|
||||
Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
|
||||
: Transformer(
|
||||
std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// translate named items to other or a value set
|
||||
class CheckedTransformer : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction
|
||||
template <typename... Args>
|
||||
CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
|
||||
: CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// direct map of std::string to std::string
|
||||
template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
|
||||
|
||||
static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
|
||||
"mapping must produce value pairs");
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
auto tfunc = [mapping]() {
|
||||
std::string out("value in ");
|
||||
out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
|
||||
out += detail::join(
|
||||
detail::smart_deref(mapping),
|
||||
[](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
|
||||
",");
|
||||
out.push_back('}');
|
||||
return out;
|
||||
};
|
||||
|
||||
desc_function_ = tfunc;
|
||||
|
||||
func_ = [mapping, tfunc, filter_fn](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
local_item_t b;
|
||||
bool converted = lexical_cast(input, b);
|
||||
if(converted) {
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(mapping, b, filter_fn);
|
||||
if(res.first) {
|
||||
input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||
return std::string{};
|
||||
}
|
||||
}
|
||||
for(const auto &v : detail::smart_deref(mapping)) {
|
||||
auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
|
||||
if(output_string == input) {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
return "Check " + input + " " + tfunc() + " FAILED";
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
template <typename T, typename... Args>
|
||||
CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
|
||||
: CheckedTransformer(
|
||||
std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// Helper function to allow ignore_case to be passed to IsMember or Transform
|
||||
inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
|
||||
|
||||
/// Helper function to allow ignore_underscore to be passed to IsMember or Transform
|
||||
inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
|
||||
|
||||
/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform
|
||||
inline std::string ignore_space(std::string item) {
|
||||
item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
|
||||
item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
|
||||
return item;
|
||||
}
|
||||
|
||||
/// Multiply a number by a factor using given mapping.
|
||||
/// Can be used to write transforms for SIZE or DURATION inputs.
|
||||
///
|
||||
/// Example:
|
||||
/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}`
|
||||
/// one can recognize inputs like "100", "12kb", "100 MB",
|
||||
/// that will be automatically transformed to 100, 14448, 104857600.
|
||||
///
|
||||
/// Output number type matches the type in the provided mapping.
|
||||
/// Therefore, if it is required to interpret real inputs like "0.42 s",
|
||||
/// the mapping should be of a type <string, float> or <string, double>.
|
||||
class AsNumberWithUnit : public Validator {
|
||||
public:
|
||||
/// Adjust AsNumberWithUnit behavior.
|
||||
/// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched.
|
||||
/// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError
|
||||
/// if UNIT_REQUIRED is set and unit literal is not found.
|
||||
enum Options : std::uint8_t {
|
||||
CASE_SENSITIVE = 0,
|
||||
CASE_INSENSITIVE = 1,
|
||||
UNIT_OPTIONAL = 0,
|
||||
UNIT_REQUIRED = 2,
|
||||
DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
|
||||
};
|
||||
|
||||
template <typename Number>
|
||||
explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
|
||||
Options opts = DEFAULT,
|
||||
const std::string &unit_name = "UNIT") {
|
||||
description(generate_description<Number>(unit_name, opts));
|
||||
validate_mapping(mapping, opts);
|
||||
|
||||
// transform function
|
||||
func_ = [mapping, opts](std::string &input) -> std::string {
|
||||
Number num{};
|
||||
|
||||
detail::rtrim(input);
|
||||
if(input.empty()) {
|
||||
throw ValidationError("Input is empty");
|
||||
}
|
||||
|
||||
// Find split position between number and prefix
|
||||
auto unit_begin = input.end();
|
||||
while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) {
|
||||
--unit_begin;
|
||||
}
|
||||
|
||||
std::string unit{unit_begin, input.end()};
|
||||
input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
|
||||
detail::trim(input);
|
||||
|
||||
if(opts & UNIT_REQUIRED && unit.empty()) {
|
||||
throw ValidationError("Missing mandatory unit");
|
||||
}
|
||||
if(opts & CASE_INSENSITIVE) {
|
||||
unit = detail::to_lower(unit);
|
||||
}
|
||||
if(unit.empty()) {
|
||||
using CLI::detail::lexical_cast;
|
||||
if(!lexical_cast(input, num)) {
|
||||
throw ValidationError(std::string("Value ") + input + " could not be converted to " +
|
||||
detail::type_name<Number>());
|
||||
}
|
||||
// No need to modify input if no unit passed
|
||||
return {};
|
||||
}
|
||||
|
||||
// find corresponding factor
|
||||
auto it = mapping.find(unit);
|
||||
if(it == mapping.end()) {
|
||||
throw ValidationError(unit +
|
||||
" unit not recognized. "
|
||||
"Allowed values: " +
|
||||
detail::generate_map(mapping, true));
|
||||
}
|
||||
|
||||
if(!input.empty()) {
|
||||
using CLI::detail::lexical_cast;
|
||||
bool converted = lexical_cast(input, num);
|
||||
if(!converted) {
|
||||
throw ValidationError(std::string("Value ") + input + " could not be converted to " +
|
||||
detail::type_name<Number>());
|
||||
}
|
||||
// perform safe multiplication
|
||||
bool ok = detail::checked_multiply(num, it->second);
|
||||
if(!ok) {
|
||||
throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
|
||||
" factor would cause number overflow. Use smaller value.");
|
||||
}
|
||||
} else {
|
||||
num = static_cast<Number>(it->second);
|
||||
}
|
||||
|
||||
input = detail::to_string(num);
|
||||
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
/// Check that mapping contains valid units.
|
||||
/// Update mapping for CASE_INSENSITIVE mode.
|
||||
template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
|
||||
for(auto &kv : mapping) {
|
||||
if(kv.first.empty()) {
|
||||
throw ValidationError("Unit must not be empty.");
|
||||
}
|
||||
if(!detail::isalpha(kv.first)) {
|
||||
throw ValidationError("Unit must contain only letters.");
|
||||
}
|
||||
}
|
||||
|
||||
// make all units lowercase if CASE_INSENSITIVE
|
||||
if(opts & CASE_INSENSITIVE) {
|
||||
std::map<std::string, Number> lower_mapping;
|
||||
for(auto &kv : mapping) {
|
||||
auto s = detail::to_lower(kv.first);
|
||||
if(lower_mapping.count(s)) {
|
||||
throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
|
||||
s);
|
||||
}
|
||||
lower_mapping[detail::to_lower(kv.first)] = kv.second;
|
||||
}
|
||||
mapping = std::move(lower_mapping);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate description like this: NUMBER [UNIT]
|
||||
template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<Number>() << ' ';
|
||||
if(opts & UNIT_REQUIRED) {
|
||||
out << name;
|
||||
} else {
|
||||
out << '[' << name << ']';
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
};
|
||||
|
||||
inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) {
|
||||
return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
/// Converts a human-readable size string (with unit literal) to uin64_t size.
|
||||
/// Example:
|
||||
/// "100" => 100
|
||||
/// "1 b" => 100
|
||||
/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024)
|
||||
/// "10 KB" => 10240
|
||||
/// "10 kb" => 10240
|
||||
/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024)
|
||||
/// "10kb" => 10240
|
||||
/// "2 MB" => 2097152
|
||||
/// "2 EiB" => 2^61 // Units up to exibyte are supported
|
||||
class AsSizeValue : public AsNumberWithUnit {
|
||||
public:
|
||||
using result_t = std::uint64_t;
|
||||
|
||||
/// If kb_is_1000 is true,
|
||||
/// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024
|
||||
/// (same applies to higher order units as well).
|
||||
/// Otherwise, interpret all literals as factors of 1024.
|
||||
/// The first option is formally correct, but
|
||||
/// the second interpretation is more wide-spread
|
||||
/// (see https://en.wikipedia.org/wiki/Binary_prefix).
|
||||
explicit AsSizeValue(bool kb_is_1000);
|
||||
|
||||
private:
|
||||
/// Get <size unit, factor> mapping
|
||||
static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
|
||||
|
||||
/// Cache calculated mapping
|
||||
static std::map<std::string, result_t> get_mapping(bool kb_is_1000);
|
||||
};
|
||||
|
||||
#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0
|
||||
// new extra validators
|
||||
|
||||
#endif
|
||||
// [CLI11:extra_validators_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
#ifndef CLI11_COMPILE
|
||||
#include "impl/ExtraValidators_inl.hpp" // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -37,6 +37,8 @@ class App;
|
||||
class ConfigBase;
|
||||
|
||||
using Option_p = std::unique_ptr<Option>;
|
||||
using Validator_p = std::shared_ptr<Validator>;
|
||||
|
||||
/// Enumeration of the multiOption Policy selection
|
||||
enum class MultiOptionPolicy : char {
|
||||
Throw, //!< Throw an error if any extra arguments were given
|
||||
@@ -294,7 +296,7 @@ class Option : public OptionBase<Option> {
|
||||
int expected_max_{1};
|
||||
|
||||
/// A list of Validators to run on each value parsed
|
||||
std::vector<Validator> validators_{};
|
||||
std::vector<Validator_p> validators_{};
|
||||
|
||||
/// A list of options that are required with this option
|
||||
std::set<Option *> needs_{};
|
||||
@@ -418,19 +420,25 @@ class Option : public OptionBase<Option> {
|
||||
/// Get the current value of run_callback_for_default
|
||||
CLI11_NODISCARD bool get_run_callback_for_default() const { return run_callback_for_default_; }
|
||||
|
||||
/// Adds a shared validator
|
||||
Option *check(Validator_p validator);
|
||||
|
||||
/// Adds a Validator with a built in type name
|
||||
Option *check(Validator validator, const std::string &validator_name = "");
|
||||
|
||||
/// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
|
||||
Option *check(std::function<std::string(const std::string &)> Validator,
|
||||
std::string Validator_description = "",
|
||||
std::string Validator_name = "");
|
||||
Option *check(std::function<std::string(const std::string &)> validator_func,
|
||||
std::string validator_description = "",
|
||||
std::string validator_name = "");
|
||||
|
||||
/// Adds a shared Validator
|
||||
Option *transform(Validator_p validator);
|
||||
|
||||
/// Adds a transforming Validator with a built in type name
|
||||
Option *transform(Validator Validator, const std::string &Validator_name = "");
|
||||
Option *transform(Validator validator, const std::string &transform_name = "");
|
||||
|
||||
/// Adds a Validator-like function that can change result
|
||||
Option *transform(const std::function<std::string(std::string)> &func,
|
||||
Option *transform(const std::function<std::string(std::string)> &transform_func,
|
||||
std::string transform_description = "",
|
||||
std::string transform_name = "");
|
||||
|
||||
@@ -438,7 +446,7 @@ class Option : public OptionBase<Option> {
|
||||
Option *each(const std::function<void(std::string)> &func);
|
||||
|
||||
/// Get a named Validator
|
||||
Validator *get_validator(const std::string &Validator_name = "");
|
||||
Validator *get_validator(const std::string &validator_name = "");
|
||||
|
||||
/// Get a Validator by index NOTE: this may not be the order of definition
|
||||
Validator *get_validator(int index);
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -175,10 +174,9 @@ class Validator {
|
||||
void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger);
|
||||
};
|
||||
|
||||
/// Class wrapping some of the accessors of Validator
|
||||
class CustomValidator : public Validator {
|
||||
public:
|
||||
};
|
||||
/// Alias for Validator for custom Validator for clarity
|
||||
using CustomValidator = Validator;
|
||||
|
||||
// The implementation of the built in validators is using the Validator class;
|
||||
// the user is only expected to use the const (static) versions (since there's no setup).
|
||||
// Therefore, this is in detail.
|
||||
@@ -190,6 +188,8 @@ enum class path_type : std::uint8_t { nonexistent, file, directory };
|
||||
/// get the type of the path from a file name
|
||||
CLI11_INLINE path_type check_path(const char *file) noexcept;
|
||||
|
||||
// Static is not needed here, because global const implies static.
|
||||
|
||||
/// Check for an existing file (returns error message if check fails)
|
||||
class ExistingFileValidator : public Validator {
|
||||
public:
|
||||
@@ -214,12 +214,6 @@ class NonexistentPathValidator : public Validator {
|
||||
NonexistentPathValidator();
|
||||
};
|
||||
|
||||
/// Validate the given string is a legal ipv4 address
|
||||
class IPV4Validator : public Validator {
|
||||
public:
|
||||
IPV4Validator();
|
||||
};
|
||||
|
||||
class EscapedStringTransformer : public Validator {
|
||||
public:
|
||||
EscapedStringTransformer();
|
||||
@@ -227,8 +221,6 @@ class EscapedStringTransformer : public Validator {
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Static is not needed here, because global const implies static.
|
||||
|
||||
/// Check for existing file (returns error message if check fails)
|
||||
const detail::ExistingFileValidator ExistingFile;
|
||||
|
||||
@@ -241,30 +233,9 @@ const detail::ExistingPathValidator ExistingPath;
|
||||
/// Check for an non-existing path
|
||||
const detail::NonexistentPathValidator NonexistentPath;
|
||||
|
||||
/// Check for an IP4 address
|
||||
const detail::IPV4Validator ValidIPV4;
|
||||
|
||||
/// convert escaped characters into their associated values
|
||||
const detail::EscapedStringTransformer EscapedString;
|
||||
|
||||
/// Validate the input as a particular type
|
||||
template <typename DesiredType> class TypeValidator : public Validator {
|
||||
public:
|
||||
explicit TypeValidator(const std::string &validator_name)
|
||||
: Validator(validator_name, [](std::string &input_string) {
|
||||
using CLI::detail::lexical_cast;
|
||||
auto val = DesiredType();
|
||||
if(!lexical_cast(input_string, val)) {
|
||||
return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
|
||||
}
|
||||
return std::string();
|
||||
}) {}
|
||||
TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
|
||||
};
|
||||
|
||||
/// Check for a number
|
||||
const TypeValidator<double> Number("NUMBER");
|
||||
|
||||
/// Modify a path if the file is a particular default location, can be used as Check or transform
|
||||
/// with the error return optionally disabled
|
||||
class FileOnDefaultPath : public Validator {
|
||||
@@ -313,134 +284,7 @@ const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE
|
||||
/// Check for a positive valued number (val>0.0), <double>::min here is the smallest positive number
|
||||
const Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE");
|
||||
|
||||
/// Produce a bounded range (factory). Min and max are inclusive.
|
||||
class Bound : public Validator {
|
||||
public:
|
||||
/// This bounds a value with min and max inclusive.
|
||||
///
|
||||
/// Note that the constructor is templated, but the struct is not, so C++17 is not
|
||||
/// needed to provide nice syntax for Range(a,b).
|
||||
template <typename T> Bound(T min_val, T max_val) {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]";
|
||||
description(out.str());
|
||||
|
||||
func_ = [min_val, max_val](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
T val;
|
||||
bool converted = lexical_cast(input, val);
|
||||
if(!converted) {
|
||||
return std::string("Value ") + input + " could not be converted";
|
||||
}
|
||||
if(val < min_val)
|
||||
input = detail::to_string(min_val);
|
||||
else if(val > max_val)
|
||||
input = detail::to_string(max_val);
|
||||
|
||||
return std::string{};
|
||||
};
|
||||
}
|
||||
|
||||
/// Range of one value is 0 to value
|
||||
template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T,
|
||||
enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
|
||||
auto smart_deref(T value) -> decltype(*value) {
|
||||
return *value;
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
|
||||
typename std::remove_reference<T>::type &smart_deref(T &value) {
|
||||
// NOLINTNEXTLINE
|
||||
return value;
|
||||
}
|
||||
/// Generate a string representation of a set
|
||||
template <typename T> std::string generate_set(const T &set) {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
std::string out(1, '{');
|
||||
out.append(detail::join(
|
||||
detail::smart_deref(set),
|
||||
[](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
|
||||
","));
|
||||
out.push_back('}');
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Generate a string representation of a map
|
||||
template <typename T> std::string generate_map(const T &map, bool key_only = false) {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
std::string out(1, '{');
|
||||
out.append(detail::join(
|
||||
detail::smart_deref(map),
|
||||
[key_only](const iteration_type_t &v) {
|
||||
std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
|
||||
|
||||
if(!key_only) {
|
||||
res.append("->");
|
||||
res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
","));
|
||||
out.push_back('}');
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename C, typename V> struct has_find {
|
||||
template <typename CC, typename VV>
|
||||
static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
|
||||
template <typename, typename> static auto test(...) -> decltype(std::false_type());
|
||||
|
||||
static const auto value = decltype(test<C, V>(0))::value;
|
||||
using type = std::integral_constant<bool, value>;
|
||||
};
|
||||
|
||||
/// A search function
|
||||
template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
|
||||
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
|
||||
return (detail::pair_adaptor<element_t>::first(v) == val);
|
||||
});
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// A search function that uses the built in find function
|
||||
template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
|
||||
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = setref.find(val);
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// A search function with a filter function
|
||||
template <typename T, typename V>
|
||||
auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
|
||||
-> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
|
||||
using element_t = typename detail::element_type<T>::type;
|
||||
// do the potentially faster first search
|
||||
auto res = search(set, val);
|
||||
if((res.first) || (!(filter_function))) {
|
||||
return res;
|
||||
}
|
||||
// if we haven't found it do the longer linear search with all the element translations
|
||||
auto &setref = detail::smart_deref(set);
|
||||
auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
|
||||
V a{detail::pair_adaptor<element_t>::first(v)};
|
||||
a = filter_function(a);
|
||||
return (a == val);
|
||||
});
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
// the following suggestion was made by Nikita Ofitserov(@himikof)
|
||||
// done in templates to prevent compiler warnings on negation of unsigned numbers
|
||||
|
||||
@@ -484,404 +328,6 @@ typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_mu
|
||||
a = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
/// Verify items are in a set
|
||||
class IsMember : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction using an initializer list
|
||||
template <typename T, typename... Args>
|
||||
IsMember(std::initializer_list<T> values, Args &&...args)
|
||||
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// This checks to see if an item is in a set (empty function)
|
||||
template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit IsMember(T set, F filter_function) {
|
||||
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
|
||||
|
||||
// This is the function that validates
|
||||
// It stores a copy of the set pointer-like, so shared_ptr will stay alive
|
||||
func_ = [set, filter_fn](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
local_item_t b;
|
||||
if(!lexical_cast(input, b)) {
|
||||
throw ValidationError(input); // name is added later
|
||||
}
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(set, b, filter_fn);
|
||||
if(res.first) {
|
||||
// Make sure the version in the input string is identical to the one in the set
|
||||
if(filter_fn) {
|
||||
input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
|
||||
}
|
||||
|
||||
// Return empty error string (success)
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
// If you reach this point, the result was not found
|
||||
return input + " not in " + detail::generate_set(detail::smart_deref(set));
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest (string only currently)
|
||||
template <typename T, typename... Args>
|
||||
IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
|
||||
: IsMember(
|
||||
std::forward<T>(set),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// definition of the default transformation object
|
||||
template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
|
||||
|
||||
/// Translate named items to other or a value set
|
||||
class Transformer : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction
|
||||
template <typename... Args>
|
||||
Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
|
||||
: Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// direct map of std::string to std::string
|
||||
template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
|
||||
|
||||
static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
|
||||
"mapping must produce value pairs");
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
// This is the type name for help, it will take the current version of the set contents
|
||||
desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
|
||||
|
||||
func_ = [mapping, filter_fn](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
local_item_t b;
|
||||
if(!lexical_cast(input, b)) {
|
||||
return std::string();
|
||||
// there is no possible way we can match anything in the mapping if we can't convert so just return
|
||||
}
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(mapping, b, filter_fn);
|
||||
if(res.first) {
|
||||
input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||
}
|
||||
return std::string{};
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
template <typename T, typename... Args>
|
||||
Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
|
||||
: Transformer(
|
||||
std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// translate named items to other or a value set
|
||||
class CheckedTransformer : public Validator {
|
||||
public:
|
||||
using filter_fn_t = std::function<std::string(std::string)>;
|
||||
|
||||
/// This allows in-place construction
|
||||
template <typename... Args>
|
||||
CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
|
||||
: CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
|
||||
|
||||
/// direct map of std::string to std::string
|
||||
template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
|
||||
|
||||
/// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
|
||||
/// both sides of the comparison before computing the comparison.
|
||||
template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
|
||||
|
||||
static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
|
||||
"mapping must produce value pairs");
|
||||
// Get the type of the contained item - requires a container have ::value_type
|
||||
// if the type does not have first_type and second_type, these are both value_type
|
||||
using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
|
||||
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
|
||||
using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
|
||||
// (const char * to std::string)
|
||||
using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
|
||||
|
||||
// Make a local copy of the filter function, using a std::function if not one already
|
||||
std::function<local_item_t(local_item_t)> filter_fn = filter_function;
|
||||
|
||||
auto tfunc = [mapping]() {
|
||||
std::string out("value in ");
|
||||
out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
|
||||
out += detail::join(
|
||||
detail::smart_deref(mapping),
|
||||
[](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
|
||||
",");
|
||||
out.push_back('}');
|
||||
return out;
|
||||
};
|
||||
|
||||
desc_function_ = tfunc;
|
||||
|
||||
func_ = [mapping, tfunc, filter_fn](std::string &input) {
|
||||
using CLI::detail::lexical_cast;
|
||||
local_item_t b;
|
||||
bool converted = lexical_cast(input, b);
|
||||
if(converted) {
|
||||
if(filter_fn) {
|
||||
b = filter_fn(b);
|
||||
}
|
||||
auto res = detail::search(mapping, b, filter_fn);
|
||||
if(res.first) {
|
||||
input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
|
||||
return std::string{};
|
||||
}
|
||||
}
|
||||
for(const auto &v : detail::smart_deref(mapping)) {
|
||||
auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
|
||||
if(output_string == input) {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
return "Check " + input + " " + tfunc() + " FAILED";
|
||||
};
|
||||
}
|
||||
|
||||
/// You can pass in as many filter functions as you like, they nest
|
||||
template <typename T, typename... Args>
|
||||
CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
|
||||
: CheckedTransformer(
|
||||
std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
};
|
||||
|
||||
/// Helper function to allow ignore_case to be passed to IsMember or Transform
|
||||
inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
|
||||
|
||||
/// Helper function to allow ignore_underscore to be passed to IsMember or Transform
|
||||
inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
|
||||
|
||||
/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform
|
||||
inline std::string ignore_space(std::string item) {
|
||||
item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
|
||||
item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
|
||||
return item;
|
||||
}
|
||||
|
||||
/// Multiply a number by a factor using given mapping.
|
||||
/// Can be used to write transforms for SIZE or DURATION inputs.
|
||||
///
|
||||
/// Example:
|
||||
/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}`
|
||||
/// one can recognize inputs like "100", "12kb", "100 MB",
|
||||
/// that will be automatically transformed to 100, 14448, 104857600.
|
||||
///
|
||||
/// Output number type matches the type in the provided mapping.
|
||||
/// Therefore, if it is required to interpret real inputs like "0.42 s",
|
||||
/// the mapping should be of a type <string, float> or <string, double>.
|
||||
class AsNumberWithUnit : public Validator {
|
||||
public:
|
||||
/// Adjust AsNumberWithUnit behavior.
|
||||
/// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched.
|
||||
/// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError
|
||||
/// if UNIT_REQUIRED is set and unit literal is not found.
|
||||
enum Options : std::uint8_t {
|
||||
CASE_SENSITIVE = 0,
|
||||
CASE_INSENSITIVE = 1,
|
||||
UNIT_OPTIONAL = 0,
|
||||
UNIT_REQUIRED = 2,
|
||||
DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
|
||||
};
|
||||
|
||||
template <typename Number>
|
||||
explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
|
||||
Options opts = DEFAULT,
|
||||
const std::string &unit_name = "UNIT") {
|
||||
description(generate_description<Number>(unit_name, opts));
|
||||
validate_mapping(mapping, opts);
|
||||
|
||||
// transform function
|
||||
func_ = [mapping, opts](std::string &input) -> std::string {
|
||||
Number num{};
|
||||
|
||||
detail::rtrim(input);
|
||||
if(input.empty()) {
|
||||
throw ValidationError("Input is empty");
|
||||
}
|
||||
|
||||
// Find split position between number and prefix
|
||||
auto unit_begin = input.end();
|
||||
while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) {
|
||||
--unit_begin;
|
||||
}
|
||||
|
||||
std::string unit{unit_begin, input.end()};
|
||||
input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
|
||||
detail::trim(input);
|
||||
|
||||
if(opts & UNIT_REQUIRED && unit.empty()) {
|
||||
throw ValidationError("Missing mandatory unit");
|
||||
}
|
||||
if(opts & CASE_INSENSITIVE) {
|
||||
unit = detail::to_lower(unit);
|
||||
}
|
||||
if(unit.empty()) {
|
||||
using CLI::detail::lexical_cast;
|
||||
if(!lexical_cast(input, num)) {
|
||||
throw ValidationError(std::string("Value ") + input + " could not be converted to " +
|
||||
detail::type_name<Number>());
|
||||
}
|
||||
// No need to modify input if no unit passed
|
||||
return {};
|
||||
}
|
||||
|
||||
// find corresponding factor
|
||||
auto it = mapping.find(unit);
|
||||
if(it == mapping.end()) {
|
||||
throw ValidationError(unit +
|
||||
" unit not recognized. "
|
||||
"Allowed values: " +
|
||||
detail::generate_map(mapping, true));
|
||||
}
|
||||
|
||||
if(!input.empty()) {
|
||||
using CLI::detail::lexical_cast;
|
||||
bool converted = lexical_cast(input, num);
|
||||
if(!converted) {
|
||||
throw ValidationError(std::string("Value ") + input + " could not be converted to " +
|
||||
detail::type_name<Number>());
|
||||
}
|
||||
// perform safe multiplication
|
||||
bool ok = detail::checked_multiply(num, it->second);
|
||||
if(!ok) {
|
||||
throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
|
||||
" factor would cause number overflow. Use smaller value.");
|
||||
}
|
||||
} else {
|
||||
num = static_cast<Number>(it->second);
|
||||
}
|
||||
|
||||
input = detail::to_string(num);
|
||||
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
/// Check that mapping contains valid units.
|
||||
/// Update mapping for CASE_INSENSITIVE mode.
|
||||
template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
|
||||
for(auto &kv : mapping) {
|
||||
if(kv.first.empty()) {
|
||||
throw ValidationError("Unit must not be empty.");
|
||||
}
|
||||
if(!detail::isalpha(kv.first)) {
|
||||
throw ValidationError("Unit must contain only letters.");
|
||||
}
|
||||
}
|
||||
|
||||
// make all units lowercase if CASE_INSENSITIVE
|
||||
if(opts & CASE_INSENSITIVE) {
|
||||
std::map<std::string, Number> lower_mapping;
|
||||
for(auto &kv : mapping) {
|
||||
auto s = detail::to_lower(kv.first);
|
||||
if(lower_mapping.count(s)) {
|
||||
throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
|
||||
s);
|
||||
}
|
||||
lower_mapping[detail::to_lower(kv.first)] = kv.second;
|
||||
}
|
||||
mapping = std::move(lower_mapping);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate description like this: NUMBER [UNIT]
|
||||
template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
|
||||
std::stringstream out;
|
||||
out << detail::type_name<Number>() << ' ';
|
||||
if(opts & UNIT_REQUIRED) {
|
||||
out << name;
|
||||
} else {
|
||||
out << '[' << name << ']';
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
};
|
||||
|
||||
inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) {
|
||||
return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
/// Converts a human-readable size string (with unit literal) to uin64_t size.
|
||||
/// Example:
|
||||
/// "100" => 100
|
||||
/// "1 b" => 100
|
||||
/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024)
|
||||
/// "10 KB" => 10240
|
||||
/// "10 kb" => 10240
|
||||
/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024)
|
||||
/// "10kb" => 10240
|
||||
/// "2 MB" => 2097152
|
||||
/// "2 EiB" => 2^61 // Units up to exibyte are supported
|
||||
class AsSizeValue : public AsNumberWithUnit {
|
||||
public:
|
||||
using result_t = std::uint64_t;
|
||||
|
||||
/// If kb_is_1000 is true,
|
||||
/// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024
|
||||
/// (same applies to higher order units as well).
|
||||
/// Otherwise, interpret all literals as factors of 1024.
|
||||
/// The first option is formally correct, but
|
||||
/// the second interpretation is more wide-spread
|
||||
/// (see https://en.wikipedia.org/wiki/Binary_prefix).
|
||||
explicit AsSizeValue(bool kb_is_1000);
|
||||
|
||||
private:
|
||||
/// Get <size unit, factor> mapping
|
||||
static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
|
||||
|
||||
/// Cache calculated mapping
|
||||
static std::map<std::string, result_t> get_mapping(bool kb_is_1000);
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
/// Split a string into a program name and command line arguments
|
||||
/// the string is assumed to contain a file name followed by other arguments
|
||||
/// the return value contains is a pair with the first argument containing the program name and the second
|
||||
|
||||
100
include/CLI/impl/ExtraValidators_inl.hpp
Normal file
100
include/CLI/impl/ExtraValidators_inl.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// IWYU pragma: private, include "CLI/CLI.hpp"
|
||||
#include "../ExtraValidators.hpp"
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
#include "../Encoding.hpp"
|
||||
#include "../Macros.hpp"
|
||||
#include "../StringTools.hpp"
|
||||
#include "../TypeTools.hpp"
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
// [CLI11:public_includes:end]
|
||||
|
||||
namespace CLI {
|
||||
// [CLI11:extra_validators_inl_hpp:verbatim]
|
||||
|
||||
namespace detail {
|
||||
|
||||
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{};
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
|
||||
if(kb_is_1000) {
|
||||
description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
|
||||
} else {
|
||||
description("SIZE [b, kb(=1024b), ...]");
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::init_mapping(bool kb_is_1000) {
|
||||
std::map<std::string, result_t> m;
|
||||
result_t k_factor = kb_is_1000 ? 1000 : 1024;
|
||||
result_t ki_factor = 1024;
|
||||
result_t k = 1;
|
||||
result_t ki = 1;
|
||||
m["b"] = 1;
|
||||
for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
|
||||
k *= k_factor;
|
||||
ki *= ki_factor;
|
||||
m[p] = k;
|
||||
m[p + "b"] = k;
|
||||
m[p + "i"] = ki;
|
||||
m[p + "ib"] = ki;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::get_mapping(bool kb_is_1000) {
|
||||
if(kb_is_1000) {
|
||||
static auto m = init_mapping(true);
|
||||
return m;
|
||||
}
|
||||
static auto m = init_mapping(false);
|
||||
return m;
|
||||
}
|
||||
|
||||
namespace detail {} // namespace detail
|
||||
/// @}
|
||||
|
||||
// [CLI11:extra_validators_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
#endif
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -73,70 +74,92 @@ CLI11_INLINE Option *Option::expected(int value_min, int value_max) {
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::check(Validator_p validator) {
|
||||
validator->non_modifying();
|
||||
validators_.push_back(std::move(validator));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::check(Validator validator, const std::string &validator_name) {
|
||||
validator.non_modifying();
|
||||
validators_.push_back(std::move(validator));
|
||||
if(!validator_name.empty())
|
||||
validators_.back().name(validator_name);
|
||||
auto vp = std::make_shared<Validator>(std::move(validator));
|
||||
if(!validator_name.empty()) {
|
||||
vp->name(validator_name);
|
||||
}
|
||||
validators_.push_back(std::move(vp));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::check(std::function<std::string(const std::string &)> Validator,
|
||||
std::string Validator_description,
|
||||
std::string Validator_name) {
|
||||
validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
|
||||
validators_.back().non_modifying();
|
||||
CLI11_INLINE Option *Option::check(std::function<std::string(const std::string &)> validator_func,
|
||||
std::string validator_description,
|
||||
std::string validator_name) {
|
||||
|
||||
auto vp = std::make_shared<Validator>(
|
||||
std::move(validator_func), std::move(validator_description), std::move(validator_name));
|
||||
vp->non_modifying();
|
||||
validators_.push_back(std::move(vp));
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::transform(Validator Validator, const std::string &Validator_name) {
|
||||
validators_.insert(validators_.begin(), std::move(Validator));
|
||||
if(!Validator_name.empty())
|
||||
validators_.front().name(Validator_name);
|
||||
CLI11_INLINE Option *Option::transform(Validator_p validator) {
|
||||
validators_.insert(validators_.begin(), std::move(validator));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::transform(const std::function<std::string(std::string)> &func,
|
||||
CLI11_INLINE Option *Option::transform(Validator validator, const std::string &transform_name) {
|
||||
auto vp = std::make_shared<Validator>(std::move(validator));
|
||||
if(!transform_name.empty()) {
|
||||
vp->name(transform_name);
|
||||
}
|
||||
validators_.insert(validators_.begin(), std::move(vp));
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::transform(const std::function<std::string(std::string)> &transform_func,
|
||||
std::string transform_description,
|
||||
std::string transform_name) {
|
||||
validators_.insert(validators_.begin(),
|
||||
Validator(
|
||||
[func](std::string &val) {
|
||||
val = func(val);
|
||||
return std::string{};
|
||||
},
|
||||
std::move(transform_description),
|
||||
std::move(transform_name)));
|
||||
auto vp = std::make_shared<Validator>(
|
||||
[transform_func](std::string &val) {
|
||||
val = transform_func(val);
|
||||
return std::string{};
|
||||
},
|
||||
std::move(transform_description),
|
||||
std::move(transform_name));
|
||||
validators_.insert(validators_.begin(), std::move(vp));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Option *Option::each(const std::function<void(std::string)> &func) {
|
||||
validators_.emplace_back(
|
||||
auto vp = std::make_shared<Validator>(
|
||||
[func](std::string &inout) {
|
||||
func(inout);
|
||||
return std::string{};
|
||||
},
|
||||
std::string{});
|
||||
validators_.push_back(std::move(vp));
|
||||
return this;
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator *Option::get_validator(const std::string &Validator_name) {
|
||||
for(auto &Validator : validators_) {
|
||||
if(Validator_name == Validator.get_name()) {
|
||||
return &Validator;
|
||||
CLI11_INLINE Validator *Option::get_validator(const std::string &validator_name) {
|
||||
for(auto &validator : validators_) {
|
||||
if(validator_name == validator->get_name()) {
|
||||
return validator.get();
|
||||
}
|
||||
}
|
||||
if((Validator_name.empty()) && (!validators_.empty())) {
|
||||
return &(validators_.front());
|
||||
if((validator_name.empty()) && (!validators_.empty())) {
|
||||
return validators_.front().get();
|
||||
}
|
||||
throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
|
||||
throw OptionNotFound(std::string{"Validator "} + validator_name + " Not Found");
|
||||
}
|
||||
|
||||
CLI11_INLINE Validator *Option::get_validator(int index) {
|
||||
// This is an signed int so that it is not equivalent to a pointer.
|
||||
// This is a signed int so that it is not equivalent to a pointer.
|
||||
if(index >= 0 && index < static_cast<int>(validators_.size())) {
|
||||
return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
|
||||
return validators_[static_cast<decltype(validators_)::size_type>(index)].get();
|
||||
}
|
||||
throw OptionNotFound("Validator index is not valid");
|
||||
}
|
||||
@@ -510,8 +533,8 @@ CLI11_INLINE Option *Option::type_size(int option_type_size_min, int option_type
|
||||
CLI11_NODISCARD CLI11_INLINE std::string Option::get_type_name() const {
|
||||
std::string full_type_name = type_name_();
|
||||
if(!validators_.empty()) {
|
||||
for(const auto &Validator : validators_) {
|
||||
std::string vtype = Validator.get_description();
|
||||
for(const auto &validator : validators_) {
|
||||
std::string vtype = validator->get_description();
|
||||
if(!vtype.empty()) {
|
||||
full_type_name += ":" + vtype;
|
||||
}
|
||||
@@ -645,10 +668,10 @@ CLI11_INLINE std::string Option::_validate(std::string &result, int index) const
|
||||
return err_msg;
|
||||
}
|
||||
for(const auto &vali : validators_) {
|
||||
auto v = vali.get_application_index();
|
||||
auto v = vali->get_application_index();
|
||||
if(v == -1 || v == index) {
|
||||
try {
|
||||
err_msg = vali(result);
|
||||
err_msg = (*vali)(result);
|
||||
} catch(const ValidationError &err) {
|
||||
err_msg = err.what();
|
||||
}
|
||||
|
||||
@@ -213,27 +213,6 @@ CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("P
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") {
|
||||
func_ = [](std::string &ip_addr) {
|
||||
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{};
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() {
|
||||
func_ = [](std::string &str) {
|
||||
try {
|
||||
@@ -279,41 +258,6 @@ CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool
|
||||
};
|
||||
}
|
||||
|
||||
CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
|
||||
if(kb_is_1000) {
|
||||
description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
|
||||
} else {
|
||||
description("SIZE [b, kb(=1024b), ...]");
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::init_mapping(bool kb_is_1000) {
|
||||
std::map<std::string, result_t> m;
|
||||
result_t k_factor = kb_is_1000 ? 1000 : 1024;
|
||||
result_t ki_factor = 1024;
|
||||
result_t k = 1;
|
||||
result_t ki = 1;
|
||||
m["b"] = 1;
|
||||
for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
|
||||
k *= k_factor;
|
||||
ki *= ki_factor;
|
||||
m[p] = k;
|
||||
m[p + "b"] = k;
|
||||
m[p + "i"] = ki;
|
||||
m[p + "ib"] = ki;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::get_mapping(bool kb_is_1000) {
|
||||
if(kb_is_1000) {
|
||||
static auto m = init_mapping(true);
|
||||
return m;
|
||||
}
|
||||
static auto m = init_mapping(false);
|
||||
return m;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline) {
|
||||
|
||||
@@ -30,6 +30,7 @@ cli11_headers = files(
|
||||
'include/CLI/StringTools.hpp',
|
||||
'include/CLI/TypeTools.hpp',
|
||||
'include/CLI/Validators.hpp',
|
||||
'include/CLI/ExtraValidators.hpp',
|
||||
'include/CLI/Version.hpp',
|
||||
)
|
||||
|
||||
@@ -43,6 +44,7 @@ cli11_impl_headers = files(
|
||||
'include/CLI/impl/Split_inl.hpp',
|
||||
'include/CLI/impl/StringTools_inl.hpp',
|
||||
'include/CLI/impl/Validators_inl.hpp',
|
||||
'include/CLI/impl/ExtraValidators_inl.hpp',
|
||||
)
|
||||
|
||||
subdir('single-include')
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace {namespace} {{
|
||||
|
||||
{validators_inl_hpp}
|
||||
|
||||
{extra_validators_hpp}
|
||||
|
||||
{extra_validators_inl_hpp}
|
||||
|
||||
{formatter_fwd_hpp}
|
||||
|
||||
{option_hpp}
|
||||
|
||||
@@ -11,11 +11,15 @@ else()
|
||||
if(CMAKE_VERSION VERSION_GREATER 3.19)
|
||||
# This is only useful for visual studio and other IDE builds
|
||||
target_sources(CLI11 PRIVATE ${CLI11_headers} ${CLI11_library_headers} ${CLI11_impl_headers})
|
||||
|
||||
endif()
|
||||
|
||||
set(PUBLIC_OR_INTERFACE INTERFACE)
|
||||
endif()
|
||||
|
||||
if(CLI11_ENABLE_EXTRA_VALIDATORS)
|
||||
target_compile_definitions(CLI11 ${PUBLIC_OR_INTERFACE} -DCLI11_ENABLE_EXTRA_VALIDATORS=1)
|
||||
endif()
|
||||
# Allow IDE's to group targets into folders
|
||||
add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <CLI/impl/Argv_inl.hpp>
|
||||
#include <CLI/impl/Config_inl.hpp>
|
||||
#include <CLI/impl/Encoding_inl.hpp>
|
||||
#include <CLI/impl/ExtraValidators_inl.hpp>
|
||||
#include <CLI/impl/Formatter_inl.hpp>
|
||||
#include <CLI/impl/Option_inl.hpp>
|
||||
#include <CLI/impl/Split_inl.hpp>
|
||||
|
||||
@@ -1433,30 +1433,6 @@ TEST_CASE_METHOD(TApp, "RequiredPositionalValidation", "[app]") {
|
||||
CHECK("string" == d2);
|
||||
}
|
||||
|
||||
// Tests positionals at end
|
||||
TEST_CASE_METHOD(TApp, "PositionalValidation", "[app]") {
|
||||
std::string options;
|
||||
std::string foo;
|
||||
|
||||
app.add_option("bar", options)->check(CLI::Number.name("valbar"));
|
||||
// disable the check on foo
|
||||
app.add_option("foo", foo)->check(CLI::Number.active(false));
|
||||
app.validate_positionals();
|
||||
args = {"1", "param1"};
|
||||
run();
|
||||
|
||||
CHECK("1" == options);
|
||||
CHECK("param1" == foo);
|
||||
|
||||
args = {"param1", "1"};
|
||||
CHECK_NOTHROW(run());
|
||||
|
||||
CHECK("1" == options);
|
||||
CHECK("param1" == foo);
|
||||
|
||||
CHECK(nullptr != app.get_option("bar")->get_validator("valbar"));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "PositionalNoSpaceLong", "[app]") {
|
||||
std::vector<std::string> options;
|
||||
std::string foo, bar;
|
||||
@@ -1829,6 +1805,8 @@ TEST_CASE_METHOD(TApp, "BigPositional", "[app]") {
|
||||
CHECK(vec == args);
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
TEST_CASE_METHOD(TApp, "VectorArgAndPositional", "[app]") {
|
||||
std::vector<std::string> vec;
|
||||
std::vector<int> ivec;
|
||||
@@ -1856,6 +1834,7 @@ TEST_CASE_METHOD(TApp, "VectorArgAndPositional", "[app]") {
|
||||
app.validate_optional_arguments(false);
|
||||
CHECK_THROWS(run());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_METHOD(TApp, "Reset", "[app]") {
|
||||
|
||||
@@ -1944,25 +1923,6 @@ TEST_CASE_METHOD(TApp, "FileNotExists", "[app]") {
|
||||
CHECK(!CLI::ExistingFile(myfile).empty());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "FileExists", "[app]") {
|
||||
std::string myfile{"TestNonFileNotUsed.txt"};
|
||||
CHECK(!CLI::ExistingFile(myfile).empty());
|
||||
|
||||
std::string filename = "Failed";
|
||||
app.add_option("--file", filename)->check(CLI::ExistingFile);
|
||||
args = {"--file", myfile};
|
||||
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
CHECK(ok);
|
||||
run();
|
||||
CHECK(filename == myfile);
|
||||
|
||||
std::remove(myfile.c_str());
|
||||
CHECK(!CLI::ExistingFile(myfile).empty());
|
||||
}
|
||||
|
||||
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 && defined(_MSC_VER)
|
||||
TEST_CASE_METHOD(TApp, "filesystemWideName", "[app]") {
|
||||
std::filesystem::path myfile{L"voil\u20ac.txt"};
|
||||
@@ -2005,6 +1965,25 @@ TEST_CASE_METHOD(TApp, "NotFileExists", "[app]") {
|
||||
CHECK(!CLI::ExistingFile(myfile).empty());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "FileExists", "[app]") {
|
||||
std::string myfile{"TestNonFileNotUsed.txt"};
|
||||
CHECK(!CLI::ExistingFile(myfile).empty());
|
||||
|
||||
std::string filename = "Failed";
|
||||
app.add_option("--file", filename)->check(CLI::ExistingFile);
|
||||
args = {"--file", myfile};
|
||||
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
CHECK(ok);
|
||||
run();
|
||||
CHECK(filename == myfile);
|
||||
|
||||
std::remove(myfile.c_str());
|
||||
CHECK(!CLI::ExistingFile(myfile).empty());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "DefaultedResult", "[app]") {
|
||||
std::string sval = "NA";
|
||||
int ival{0};
|
||||
@@ -2327,24 +2306,6 @@ TEST_CASE_METHOD(TApp, "NonNegative", "[app]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
|
||||
|
||||
/// Note that this must be a double in Range, too
|
||||
app.add_option("--one")->check(CLI::TypeValidator<unsigned int>());
|
||||
|
||||
args = {"--one=1"};
|
||||
CHECK_NOTHROW(run());
|
||||
|
||||
args = {"--one=-7"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"--one=error"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"--one=4.568"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NeedsTrue", "[app]") {
|
||||
std::string str;
|
||||
app.add_option("-s,--string", str);
|
||||
|
||||
@@ -48,6 +48,7 @@ set(CLI11_TESTS
|
||||
TrueFalseTest
|
||||
localeTest
|
||||
OptionGroupTest
|
||||
ExtraValidatorsTest
|
||||
EncodingTest)
|
||||
|
||||
if(WIN32)
|
||||
|
||||
@@ -3461,6 +3461,8 @@ TEST_CASE_METHOD(TApp, "TomlOutputFlag", "[config]") {
|
||||
CHECK_THAT(str, Contains("nothing"));
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
TEST_CASE_METHOD(TApp, "TomlOutputSet", "[config]") {
|
||||
|
||||
int v{0};
|
||||
@@ -3473,6 +3475,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputSet", "[config]") {
|
||||
std::string str = app.config_to_str();
|
||||
CHECK_THAT(str, Contains("simple=2"));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_METHOD(TApp, "TomlOutputDefault", "[config]") {
|
||||
|
||||
@@ -3937,6 +3940,8 @@ TEST_CASE_METHOD(TApp, "IniOutputFlag", "[config]") {
|
||||
CHECK_THAT(str, Contains("nothing"));
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
TEST_CASE_METHOD(TApp, "IniOutputSet", "[config]") {
|
||||
|
||||
int v{0};
|
||||
@@ -3950,6 +3955,8 @@ TEST_CASE_METHOD(TApp, "IniOutputSet", "[config]") {
|
||||
CHECK_THAT(str, Contains("simple=2"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE_METHOD(TApp, "IniOutputDefault", "[config]") {
|
||||
|
||||
int v{7};
|
||||
|
||||
546
tests/ExtraValidatorsTest.cpp
Normal file
546
tests/ExtraValidatorsTest.cpp
Normal file
@@ -0,0 +1,546 @@
|
||||
// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include "app_helper.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("Validators:basic", "[helpers]") { CHECK(true); }
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
TEST_CASE("Validators: IPValidate1", "[helpers]") {
|
||||
std::string ip = "1.1.1.1";
|
||||
CHECK(CLI::ValidIPV4(ip).empty());
|
||||
ip = "224.255.0.1";
|
||||
CHECK(CLI::ValidIPV4(ip).empty());
|
||||
// check that it doesn't work with a trailing .
|
||||
ip = "224.255.0.1.";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "-1.255.0.1";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.256.0.1";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.223.0.";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.256.0.1";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "aaa";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.2.3.abc";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "11.22";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Validators: NumberValidator", "[helpers]") {
|
||||
std::string num = "1.1.1.1";
|
||||
CHECK_FALSE(CLI::Number(num).empty());
|
||||
num = "1.7";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "10000";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "-0.000";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "+1.55";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "a";
|
||||
CHECK_FALSE(CLI::Number(num).empty());
|
||||
}
|
||||
|
||||
// Tests positionals validation at the end
|
||||
TEST_CASE_METHOD(TApp, "PositionalValidation", "[app]") {
|
||||
std::string options;
|
||||
std::string foo;
|
||||
|
||||
app.add_option("bar", options)->check(CLI::Number.name("valbar"));
|
||||
// disable the check on foo
|
||||
app.add_option("foo", foo)->check(CLI::Number.active(false));
|
||||
app.validate_positionals();
|
||||
args = {"1", "param1"};
|
||||
run();
|
||||
|
||||
CHECK("1" == options);
|
||||
CHECK("param1" == foo);
|
||||
|
||||
args = {"param1", "1"};
|
||||
CHECK_NOTHROW(run());
|
||||
|
||||
CHECK("1" == options);
|
||||
CHECK("param1" == foo);
|
||||
|
||||
CHECK(nullptr != app.get_option("bar")->get_validator("valbar"));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "BoundTests", "[transform]") {
|
||||
double value = NAN;
|
||||
app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
|
||||
args = {"-s", "15"};
|
||||
run();
|
||||
CHECK(5.9 == value);
|
||||
|
||||
args = {"-s", "3.689"};
|
||||
run();
|
||||
CHECK(std::stod("3.689") == value);
|
||||
|
||||
// value can't be converted to int so it is just ignored
|
||||
args = {"-s", "abcd"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "2.5"};
|
||||
run();
|
||||
CHECK(3.4 == value);
|
||||
|
||||
auto help = app.help();
|
||||
CHECK(help.find("bounded to") != std::string::npos);
|
||||
CHECK(help.find("[3.4 - 5.9]") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
|
||||
|
||||
/// Note that this must be a double in Range, too
|
||||
app.add_option("--one")->check(CLI::TypeValidator<unsigned int>());
|
||||
|
||||
args = {"--one=1"};
|
||||
CHECK_NOTHROW(run());
|
||||
|
||||
args = {"--one=-7"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"--one=error"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"--one=4.568"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitCorrectlySplitNumber", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
|
||||
|
||||
int value = 0;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "42"};
|
||||
run();
|
||||
CHECK(42 == value);
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", " 42 cc "};
|
||||
run();
|
||||
CHECK(42000 == value);
|
||||
args = {"-n", " -42 cc "};
|
||||
run();
|
||||
CHECK(-42000 == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitFloatTest", "[transform]") {
|
||||
std::map<std::string, double> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
|
||||
double value{0.0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "42"};
|
||||
run();
|
||||
CHECK(42 == Approx(value));
|
||||
|
||||
args = {"-n", ".5"};
|
||||
run();
|
||||
CHECK(.5 == Approx(value));
|
||||
|
||||
args = {"-n", "42.5 a"};
|
||||
run();
|
||||
CHECK(425 == Approx(value));
|
||||
|
||||
args = {"-n", "42.cc"};
|
||||
run();
|
||||
CHECK(42000 == Approx(value));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitCaseSensitive", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_SENSITIVE));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitCaseInsensitive", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_INSENSITIVE));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42b"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
|
||||
args = {"-n", "42B"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_SENSITIVE));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
|
||||
args = {"-n", "42"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit2", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_INSENSITIVE));
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42b"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
|
||||
args = {"-n", "42"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitBadMapping", "[transform]") {
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"A", 100}},
|
||||
CLI::AsNumberWithUnit::CASE_INSENSITIVE),
|
||||
CLI::ValidationError);
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"9", 100}}), CLI::ValidationError);
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"AA A", 100}}), CLI::ValidationError);
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"", 100}}), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitBadInput", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "13 a b"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "13 c"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "a"};
|
||||
// Assume 1.0 unit
|
||||
CHECK_NOTHROW(run());
|
||||
args = {"-n", "12.0a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "a5"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", ""};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "13 a-"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitIntOverflow", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}};
|
||||
|
||||
std::int32_t value = 0;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "1000 a"};
|
||||
run();
|
||||
CHECK(1000000000 == value);
|
||||
|
||||
args = {"-n", "1000000 a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "-1000000 a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "21474836 b"};
|
||||
run();
|
||||
CHECK(2147483600 == value);
|
||||
|
||||
args = {"-n", "21474836 c"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitFloatOverflow", "[transform]") {
|
||||
std::map<std::string, float> mapping{{"a", 2.f}, {"b", 1.f}, {"c", 0.f}};
|
||||
|
||||
float value{0.0F};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "3e+38 a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "3e+38 b"};
|
||||
run();
|
||||
CHECK(3e+38f == Approx(value));
|
||||
|
||||
args = {"-n", "3e+38 c"};
|
||||
run();
|
||||
CHECK(0.f == Approx(value));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AsSizeValue1000_1024", "[transform]") {
|
||||
std::uint64_t value{0};
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(true));
|
||||
|
||||
args = {"-s", "10240"};
|
||||
run();
|
||||
CHECK(10240u == value);
|
||||
|
||||
args = {"-s", "1b"};
|
||||
run();
|
||||
CHECK(1u == value);
|
||||
|
||||
std::uint64_t k_value{1000u};
|
||||
std::uint64_t ki_value{1024u};
|
||||
args = {"-s", "1k"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1kb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1 Kb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1ki"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1kib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u;
|
||||
ki_value = 1024ull * 1024u;
|
||||
args = {"-s", "1m"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1mb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1mi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u;
|
||||
args = {"-s", "1g"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1gb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1gi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1t"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1tb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1ti"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1tib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1p"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1pb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1pi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1e"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1eb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1ei"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1eib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "duration_test", "[transform]") {
|
||||
std::chrono::seconds duration{1};
|
||||
|
||||
app.option_defaults()->ignore_case();
|
||||
app.add_option_function<std::size_t>(
|
||||
"--duration",
|
||||
[&](size_t a_value) { duration = std::chrono::seconds{a_value}; },
|
||||
"valid units: sec, min, h, day.")
|
||||
->capture_default_str()
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}}));
|
||||
CHECK_NOTHROW(app.parse(std::vector<std::string>{"1 day", "--duration"}));
|
||||
|
||||
CHECK(std::chrono::seconds(86400) == duration);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AsSizeValue1024", "[transform]") {
|
||||
std::uint64_t value{0};
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(false));
|
||||
|
||||
args = {"-s", "10240"};
|
||||
run();
|
||||
CHECK(10240u == value);
|
||||
|
||||
args = {"-s", "1b"};
|
||||
run();
|
||||
CHECK(1u == value);
|
||||
|
||||
std::uint64_t ki_value{1024u};
|
||||
args = {"-s", "1k"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1kb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1 Kb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1ki"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1kib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u;
|
||||
args = {"-s", "1m"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u;
|
||||
args = {"-s", "1g"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1t"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1tb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1ti"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1tib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1p"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1e"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1eb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1ei"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1eib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -526,26 +526,6 @@ TEST_CASE("THelp: ManualSetters", "[help]") {
|
||||
CHECK_THAT(help, Contains("[18]"));
|
||||
}
|
||||
|
||||
TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
|
||||
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
int x{1};
|
||||
|
||||
CLI::Option *op1 = app.add_option("--op1", x)->check(CLI::IsMember({1, 2}));
|
||||
CLI::Option *op2 = app.add_option("--op2", x)->transform(CLI::IsMember({1, 2}));
|
||||
op1->default_str("12");
|
||||
op1->type_name("BIGGLES");
|
||||
op2->type_name("QUIGGLES");
|
||||
CHECK(1 == x);
|
||||
|
||||
std::string help = app.help();
|
||||
CHECK_THAT(help, Contains("[12]"));
|
||||
CHECK_THAT(help, Contains("BIGGLES"));
|
||||
CHECK_THAT(help, Contains("QUIGGLES"));
|
||||
CHECK_THAT(help, Contains("{1,2}"));
|
||||
}
|
||||
|
||||
TEST_CASE("THelp: Subcom", "[help]") {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
@@ -618,6 +598,29 @@ TEST_CASE("THelp: MasterName", "[help]") {
|
||||
CHECK_THAT(app.help(), Contains("MyRealName"));
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
|
||||
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
int x{1};
|
||||
|
||||
CLI::Option *op1 = app.add_option("--op1", x)->check(CLI::IsMember({1, 2}));
|
||||
CLI::Option *op2 = app.add_option("--op2", x)->transform(CLI::IsMember({1, 2}));
|
||||
op1->default_str("12");
|
||||
op1->type_name("BIGGLES");
|
||||
op2->type_name("QUIGGLES");
|
||||
CHECK(1 == x);
|
||||
|
||||
std::string help = app.help();
|
||||
CHECK_THAT(help, Contains("[12]"));
|
||||
CHECK_THAT(help, Contains("BIGGLES"));
|
||||
CHECK_THAT(help, Contains("QUIGGLES"));
|
||||
CHECK_THAT(help, Contains("{1,2}"));
|
||||
}
|
||||
|
||||
TEST_CASE("THelp: IntDefaults", "[help]") {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
@@ -650,6 +653,8 @@ TEST_CASE("THelp: SetLower", "[help]") {
|
||||
CHECK_THAT(help, Contains("THREE"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("THelp: OnlyOneHelp", "[help]") {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
@@ -1364,6 +1369,9 @@ TEST_CASE("THelp: CombinedValidatorsPathyTextAsTransform", "[help]") {
|
||||
CHECK_THAT(help, Contains("TEXT:(PATH(existing)) OR (PATH"));
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
// #113 Part 2
|
||||
TEST_CASE("THelp: ChangingSet", "[help]") {
|
||||
CLI::App app;
|
||||
@@ -1450,6 +1458,8 @@ TEST_CASE("THelp: ChangingCaselessSetDefaulted", "[help]") {
|
||||
CHECK_THAT(help, Contains("4"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// New defaults tests (1.8)
|
||||
|
||||
TEST_CASE("THelp: ChangingDefaults", "[help]") {
|
||||
|
||||
@@ -630,25 +630,6 @@ TEST_CASE("Validators: PathNotExistsDir", "[helpers]") {
|
||||
CHECK(!CLI::ExistingPath(mydir).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Validators: IPValidate1", "[helpers]") {
|
||||
std::string ip = "1.1.1.1";
|
||||
CHECK(CLI::ValidIPV4(ip).empty());
|
||||
ip = "224.255.0.1";
|
||||
CHECK(CLI::ValidIPV4(ip).empty());
|
||||
ip = "-1.255.0.1";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.256.0.1";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.256.0.1";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "aaa";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "1.2.3.abc";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
ip = "11.22";
|
||||
CHECK_FALSE(CLI::ValidIPV4(ip).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Validators: PositiveValidator", "[helpers]") {
|
||||
std::string num = "1.1.1.1";
|
||||
CHECK_FALSE(CLI::PositiveNumber(num).empty());
|
||||
@@ -687,21 +668,6 @@ TEST_CASE("Validators: NonNegativeValidator", "[helpers]") {
|
||||
CHECK_FALSE(CLI::NonNegativeNumber(num).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Validators: NumberValidator", "[helpers]") {
|
||||
std::string num = "1.1.1.1";
|
||||
CHECK_FALSE(CLI::Number(num).empty());
|
||||
num = "1.7";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "10000";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "-0.000";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "+1.55";
|
||||
CHECK(CLI::Number(num).empty());
|
||||
num = "a";
|
||||
CHECK_FALSE(CLI::Number(num).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Validators: CombinedAndRange", "[helpers]") {
|
||||
auto crange = CLI::Range(0, 12) & CLI::Range(4, 16);
|
||||
CHECK(crange("4").empty());
|
||||
|
||||
@@ -95,9 +95,13 @@ TEST_CASE_METHOD(TApp, "doubleVectorFunctionRunCallbackOnDefault", "[optiontype]
|
||||
REQUIRE(3U == vec.size());
|
||||
CHECK(5.0 == vec[0]);
|
||||
CHECK(7.0 == vec[2]);
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
opt->check(CLI::Number);
|
||||
opt->run_callback_for_default(false);
|
||||
CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ValidationError);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const std::map<std::string, double> testValuesDouble{
|
||||
|
||||
@@ -385,6 +385,9 @@ TEST_CASE_METHOD(TApp, "stringLikeTests", "[optiontype]") {
|
||||
CHECK("bca" == m_type.m_value);
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
#if CLI11_HAS_FILESYSTEM
|
||||
#include <string_view>
|
||||
// test code from https://github.com/CLIUtils/CLI11/issues/881
|
||||
@@ -419,6 +422,8 @@ TEST_CASE_METHOD(TApp, "AsStringRef", "[app]") {
|
||||
CHECK(inputStr2 == "optC");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE_METHOD(TApp, "VectorExpectedRange", "[optiontype]") {
|
||||
std::vector<std::string> strvec;
|
||||
|
||||
|
||||
@@ -36,6 +36,20 @@ static_assert(CLI::detail::pair_adaptor<std::vector<std::string>>::value == fals
|
||||
static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs");
|
||||
static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs");
|
||||
|
||||
/** just test a set as an option value type */
|
||||
TEST_CASE_METHOD(TApp, "simpleSet", "[set]") {
|
||||
std::set<std::string> value;
|
||||
auto *opt = app.add_option("-s,--set", value);
|
||||
args = {"-s", "one", "-s", "two", "-s", "one"};
|
||||
run();
|
||||
CHECK(app.count("-s") == 3u);
|
||||
CHECK(opt->count() == 3u);
|
||||
CHECK(value.size() == 2u);
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
TEST_CASE_METHOD(TApp, "SimpleMaps", "[set]") {
|
||||
int value{0};
|
||||
std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
|
||||
@@ -80,6 +94,28 @@ TEST_CASE_METHOD(TApp, "StringStringMapNoModify", "[set]") {
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "StringStringMapNoModifyMultiple", "[set]") {
|
||||
std::string value1, value2, value3;
|
||||
std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
|
||||
CLI::Validator_p membership = std::make_shared<CLI::Validator>(CLI::IsMember(map));
|
||||
|
||||
app.add_option("--set1", value1)->check(membership);
|
||||
app.add_option("--set2", value2)->check(membership);
|
||||
app.add_option("--set3", value3)->check(membership);
|
||||
args = {"--set1", "a"};
|
||||
run();
|
||||
CHECK("a" == value1);
|
||||
|
||||
args = {"--set2", "b"};
|
||||
run();
|
||||
CHECK("b" == value2);
|
||||
|
||||
args = {"--set3", "c"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
// check that the validators are actually the same
|
||||
CHECK(app.get_option("--set1")->get_validator(0) == app.get_option("--set3")->get_validator(0));
|
||||
}
|
||||
|
||||
enum SimpleEnum { SE_one = 1, SE_two = 2 };
|
||||
|
||||
TEST_CASE_METHOD(TApp, "EnumMap", "[set]") {
|
||||
@@ -731,3 +767,5 @@ TEST_CASE_METHOD(TApp, "AddRemoveSetItemsNoCase", "[set]") {
|
||||
args = {"--type2", "TYpE2"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
|
||||
TEST_CASE_METHOD(TApp, "SimpleTransform", "[transform]") {
|
||||
int value{0};
|
||||
auto *opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
|
||||
@@ -552,184 +555,48 @@ TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomValidator", "[transform]") {
|
||||
CHECK(help.find("15->5") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "BoundTests", "[transform]") {
|
||||
double value = NAN;
|
||||
app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
|
||||
TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomSharedValidator", "[transform]") {
|
||||
std::string value;
|
||||
|
||||
CLI::Validator_p ctrans = std::make_shared<CLI::Validator>(
|
||||
CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) | CLI::Validator(
|
||||
[](std::string &element) {
|
||||
if(element == "frog") {
|
||||
element = "hops";
|
||||
}
|
||||
return std::string{};
|
||||
},
|
||||
std::string{}));
|
||||
ctrans->name("check");
|
||||
auto *opt = app.add_option("-s", value)->transform(ctrans);
|
||||
args = {"-s", "15"};
|
||||
run();
|
||||
CHECK(5.9 == value);
|
||||
CHECK("5" == value);
|
||||
|
||||
args = {"-s", "3.689"};
|
||||
args = {"-s", "18"};
|
||||
run();
|
||||
CHECK(std::stod("3.689") == value);
|
||||
CHECK("6" == value);
|
||||
|
||||
// value can't be converted to int so it is just ignored
|
||||
args = {"-s", "abcd"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-s", "2.5"};
|
||||
args = {"-s", "frog"};
|
||||
run();
|
||||
CHECK(3.4 == value);
|
||||
CHECK("hops" == value);
|
||||
|
||||
args = {"-s", "25"};
|
||||
run();
|
||||
CHECK("25" == value);
|
||||
|
||||
auto help = app.help();
|
||||
CHECK(help.find("bounded to") != std::string::npos);
|
||||
CHECK(help.find("[3.4 - 5.9]") != std::string::npos);
|
||||
CHECK(help.find("15->5") != std::string::npos);
|
||||
CHECK(help.find("OR") == std::string::npos);
|
||||
|
||||
auto *validator = opt->get_validator("check");
|
||||
CHECK("check" == validator->get_name());
|
||||
validator->active(false);
|
||||
help = app.help();
|
||||
CHECK(help.find("15->5") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitCorrectlySplitNumber", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
|
||||
|
||||
int value = 0;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "42"};
|
||||
run();
|
||||
CHECK(42 == value);
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", " 42 cc "};
|
||||
run();
|
||||
CHECK(42000 == value);
|
||||
args = {"-n", " -42 cc "};
|
||||
run();
|
||||
CHECK(-42000 == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitFloatTest", "[transform]") {
|
||||
std::map<std::string, double> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
|
||||
double value{0.0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "42"};
|
||||
run();
|
||||
CHECK(42 == Approx(value));
|
||||
|
||||
args = {"-n", ".5"};
|
||||
run();
|
||||
CHECK(.5 == Approx(value));
|
||||
|
||||
args = {"-n", "42.5 a"};
|
||||
run();
|
||||
CHECK(425 == Approx(value));
|
||||
|
||||
args = {"-n", "42.cc"};
|
||||
run();
|
||||
CHECK(42000 == Approx(value));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitCaseSensitive", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_SENSITIVE));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitCaseInsensitive", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_INSENSITIVE));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42b"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
|
||||
args = {"-n", "42B"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_SENSITIVE));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
|
||||
args = {"-n", "42"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit2", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
mapping, CLI::AsNumberWithUnit::UNIT_REQUIRED | CLI::AsNumberWithUnit::CASE_INSENSITIVE));
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
CHECK(420 == value);
|
||||
|
||||
args = {"-n", "42b"};
|
||||
run();
|
||||
CHECK(4200 == value);
|
||||
|
||||
args = {"-n", "42"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitBadMapping", "[transform]") {
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"A", 100}},
|
||||
CLI::AsNumberWithUnit::CASE_INSENSITIVE),
|
||||
CLI::ValidationError);
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"9", 100}}), CLI::ValidationError);
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"AA A", 100}}), CLI::ValidationError);
|
||||
CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"", 100}}), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitBadInput", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}};
|
||||
|
||||
int value{0};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "13 a b"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "13 c"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "a"};
|
||||
// Assume 1.0 unit
|
||||
CHECK_NOTHROW(run());
|
||||
args = {"-n", "12.0a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "a5"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", ""};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
args = {"-n", "13 a-"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
#endif
|
||||
static const std::map<std::string, std::string> validValues = {
|
||||
{"test\\u03C0\\u00e9", from_u8string(u8"test\u03C0\u00E9")},
|
||||
{"test\\u03C0\\u00e9", from_u8string(u8"test\u73C0\u0057")},
|
||||
@@ -776,267 +643,3 @@ TEST_CASE_METHOD(TApp, "StringEscapeInvalid", "[transform]") {
|
||||
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitIntOverflow", "[transform]") {
|
||||
std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}};
|
||||
|
||||
std::int32_t value = 0;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "1000 a"};
|
||||
run();
|
||||
CHECK(1000000000 == value);
|
||||
|
||||
args = {"-n", "1000000 a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "-1000000 a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "21474836 b"};
|
||||
run();
|
||||
CHECK(2147483600 == value);
|
||||
|
||||
args = {"-n", "21474836 c"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "NumberWithUnitFloatOverflow", "[transform]") {
|
||||
std::map<std::string, float> mapping{{"a", 2.f}, {"b", 1.f}, {"c", 0.f}};
|
||||
|
||||
float value{0.0F};
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "3e+38 a"};
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "3e+38 b"};
|
||||
run();
|
||||
CHECK(3e+38f == Approx(value));
|
||||
|
||||
args = {"-n", "3e+38 c"};
|
||||
run();
|
||||
CHECK(0.f == Approx(value));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AsSizeValue1000_1024", "[transform]") {
|
||||
std::uint64_t value{0};
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(true));
|
||||
|
||||
args = {"-s", "10240"};
|
||||
run();
|
||||
CHECK(10240u == value);
|
||||
|
||||
args = {"-s", "1b"};
|
||||
run();
|
||||
CHECK(1u == value);
|
||||
|
||||
std::uint64_t k_value{1000u};
|
||||
std::uint64_t ki_value{1024u};
|
||||
args = {"-s", "1k"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1kb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1 Kb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1ki"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1kib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u;
|
||||
ki_value = 1024ull * 1024u;
|
||||
args = {"-s", "1m"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1mb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1mi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u;
|
||||
args = {"-s", "1g"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1gb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1gi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1t"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1tb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1ti"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1tib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1p"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1pb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1pi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1e"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1eb"};
|
||||
run();
|
||||
CHECK(k_value == value);
|
||||
args = {"-s", "1ei"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1eib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "duration_test", "[transform]") {
|
||||
std::chrono::seconds duration{1};
|
||||
|
||||
app.option_defaults()->ignore_case();
|
||||
app.add_option_function<std::size_t>(
|
||||
"--duration",
|
||||
[&](size_t a_value) { duration = std::chrono::seconds{a_value}; },
|
||||
"valid units: sec, min, h, day.")
|
||||
->capture_default_str()
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}}));
|
||||
CHECK_NOTHROW(app.parse(std::vector<std::string>{"1 day", "--duration"}));
|
||||
|
||||
CHECK(std::chrono::seconds(86400) == duration);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AsSizeValue1024", "[transform]") {
|
||||
std::uint64_t value{0};
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(false));
|
||||
|
||||
args = {"-s", "10240"};
|
||||
run();
|
||||
CHECK(10240u == value);
|
||||
|
||||
args = {"-s", "1b"};
|
||||
run();
|
||||
CHECK(1u == value);
|
||||
|
||||
std::uint64_t ki_value{1024u};
|
||||
args = {"-s", "1k"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1kb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1 Kb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1ki"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1kib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u;
|
||||
args = {"-s", "1m"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1mib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u;
|
||||
args = {"-s", "1g"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1gib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1t"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1tb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1ti"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1tib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1p"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pi"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1pib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1e"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1eb"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1ei"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
args = {"-s", "1eib"};
|
||||
run();
|
||||
CHECK(ki_value == value);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ testnames = [
|
||||
['TrueFalseTest', {}],
|
||||
['localeTest', {}],
|
||||
['OptionGroupTest', {}],
|
||||
['ExtraValidatorsTest', {}],
|
||||
['EncodingTest', {}],
|
||||
# multi-only
|
||||
['TimerTest', {}],
|
||||
|
||||
Reference in New Issue
Block a user