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:
Philip Top
2025-09-01 05:13:21 -07:00
committed by GitHub
parent 1ab8646760
commit 8c77664bd0
37 changed files with 1823 additions and 1229 deletions

View File

@@ -9,6 +9,7 @@ engines:
coverage:
enabled: false
cppcheck:
enabled: false
language: c++
languages:

View File

@@ -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

View File

@@ -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"],
)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 &center, const T &range)
: CLI::Validator(
[this](const std::string &value) -> std::string {
T newValue;
auto result = CLI::detail::lexical_cast(value, newValue);
if(!(result && this->check(newValue))) {
return std::string("value not within range");
}
return std::string{};
},
"RANGE"),
center_point(center), delta(range) {}
CLI11_NODISCARD bool check(const T &test) const { return (test >= (center_point - delta)) && (test <= (center_point + delta)); }
CLI11_NODISCARD T center() const { return center_point; }
CLI11_NODISCARD T range() const { return delta; }
void center(const T &value) { center_point = value; }
void range(const T &value) { delta = value; }
};
int main(int argc, char **argv) {
/* this application creates custom validator which is a range center+/- range The center and range can be defined by
* other command line options and are updated dynamically
*/
CLI::App app("custom range validator");
std::string value;
auto dr = std::make_shared<DeltaRange<int>>(7, 3);
app.add_option("--number", value, "enter value in the related range")->check(dr)->required();
app.add_option_function<int>("--center", [&dr](int new_center) { dr->center(new_center); })->trigger_on_parse();
app.add_option_function<int>("--range", [&dr](int new_range) { dr->range(new_range); })->trigger_on_parse();
CLI11_PARSE(app, argc, argv);
std::cout << "number " << value << " in range = " << dr->center() << " +/- " << dr->range() << '\n';
return 0;
}
```
The Validator defines some new operations, and in the use case the Validator is
constructed using shared_ptrs. This allows polymorphism to work and the
Validator instance to be shared across multiple options, and as in this example
adapted during the parsing and checking.
There are a few limitation in this, single instances should not be used with
both transform and check. Check modifies some flags in the Validator to prevent
value modification, so that would prevent its use as a transform. Which could be
user modified later but that would potentially allow the check to modify the
value unintentionally.

View File

@@ -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

View 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 &center, const T &range)
: CLI::Validator(
[this](const std::string &value) -> std::string {
T newValue;
auto result = CLI::detail::lexical_cast(value, newValue);
if(!(result && this->check(newValue))) {
return std::string("value not within range");
}
return std::string{};
},
"RANGE"),
center_point(center), delta(range) {}
CLI11_NODISCARD bool check(const T &test) const {
return (test >= (center_point - delta)) && (test <= (center_point + delta));
}
CLI11_NODISCARD T center() const { return center_point; }
CLI11_NODISCARD T range() const { return delta; }
void center(const T &value) { center_point = value; }
void range(const T &value) { delta = value; }
};
int main(int argc, char **argv) {
/* this application creates custom validator which is a range center+/- range The center and range can be defined by
* other command line options and are updated dynamically
*/
CLI::App app("custom range validator");
std::string value;
auto dr = std::make_shared<DeltaRange<int>>(7, 3);
app.add_option("--number", value, "enter value in the related range")->check(dr)->required();
app.add_option_function<int>("--center", [&dr](int new_center) { dr->center(new_center); })->trigger_on_parse();
app.add_option_function<int>("--range", [&dr](int new_range) { dr->range(new_range); })->trigger_on_parse();
CLI11_PARSE(app, argc, argv);
std::cout << "number " << value << " in range = " << dr->center() << " +/- " << dr->range() << '\n';
return 0;
}

View 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;
}

View File

@@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
#include <CLI/CLI.hpp>
#include <iostream>
#include <map>

View File

@@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
#include <CLI/CLI.hpp>
#include <iostream>
#include <map>

View File

@@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
#include <CLI/CLI.hpp>
#include <string>

View File

@@ -4,6 +4,8 @@
//
// SPDX-License-Identifier: BSD-3-Clause
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
#include <CLI/CLI.hpp>
#include <iostream>
#include <string>

View File

@@ -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;

View File

@@ -41,4 +41,6 @@
#include "Formatter.hpp"
#include "ExtraValidators.hpp"
// IWYU pragma: end_exports

View 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

View File

@@ -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);

View File

@@ -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

View 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

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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')

View File

@@ -74,6 +74,10 @@ namespace {namespace} {{
{validators_inl_hpp}
{extra_validators_hpp}
{extra_validators_inl_hpp}
{formatter_fwd_hpp}
{option_hpp}

View File

@@ -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

View File

@@ -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>

View File

@@ -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);

View File

@@ -48,6 +48,7 @@ set(CLI11_TESTS
TrueFalseTest
localeTest
OptionGroupTest
ExtraValidatorsTest
EncodingTest)
if(WIN32)

View File

@@ -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};

View 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

View File

@@ -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]") {

View File

@@ -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());

View File

@@ -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{

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}

View File

@@ -59,6 +59,7 @@ testnames = [
['TrueFalseTest', {}],
['localeTest', {}],
['OptionGroupTest', {}],
['ExtraValidatorsTest', {}],
['EncodingTest', {}],
# multi-only
['TimerTest', {}],