1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-01 13:13:53 +00:00

feat: add a validator that checks for specific types conversion (#526)

This commit is contained in:
Philip Top 2020-12-28 07:58:57 -08:00 committed by GitHub
parent 28b35af5ea
commit 31be35b241
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 59 deletions

View File

@ -409,6 +409,7 @@ CLI11 has several Validators built-in that perform some common checks
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
- `CLI::Number`: Requires the input be a number.
- `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
- `CLI::TypeValidator<TYPE>`:🚧 Requires that the option be convertible to the specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that the input be convertible to an `unsigned int` regardless of the end conversion.
These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
@ -715,7 +716,7 @@ app.set_config(option_name="",
If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML] format by default 🚧, though the default reader can also accept files in INI format as well 🆕. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file 🆕:
```ini
```toml
# Comments are supported, using a #
# The default section is [default], case insensitive

View File

@ -419,53 +419,6 @@ class IPV4Validator : public Validator {
}
};
/// Validate the argument is a number and greater than 0
class PositiveNumber : public Validator {
public:
PositiveNumber() : Validator("POSITIVE") {
func_ = [](std::string &number_str) {
double number;
if(!detail::lexical_cast(number_str, number)) {
return std::string("Failed parsing number: (") + number_str + ')';
}
if(number <= 0) {
return std::string("Number less or equal to 0: (") + number_str + ')';
}
return std::string();
};
}
};
/// Validate the argument is a number and greater than or equal to 0
class NonNegativeNumber : public Validator {
public:
NonNegativeNumber() : Validator("NONNEGATIVE") {
func_ = [](std::string &number_str) {
double number;
if(!detail::lexical_cast(number_str, number)) {
return std::string("Failed parsing number: (") + number_str + ')';
}
if(number < 0) {
return std::string("Number less than 0: (") + number_str + ')';
}
return std::string();
};
}
};
/// Validate the argument is a number
class Number : public Validator {
public:
Number() : Validator("NUMBER") {
func_ = [](std::string &number_str) {
double number;
if(!detail::lexical_cast(number_str, number)) {
return std::string("Failed parsing as a number (") + number_str + ')';
}
return std::string();
};
}
};
} // namespace detail
// Static is not needed here, because global const implies static.
@ -485,14 +438,23 @@ const detail::NonexistentPathValidator NonexistentPath;
/// Check for an IP4 address
const detail::IPV4Validator ValidIPV4;
/// Check for a positive number
const detail::PositiveNumber PositiveNumber;
/// Check for a non-negative number
const detail::NonNegativeNumber NonNegativeNumber;
/// 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) {
func_ = [](std::string &input_string) {
auto val = DesiredType();
if(!detail::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 detail::Number Number;
const TypeValidator<double> Number("NUMBER");
/// Produce a range (factory). Min and max are inclusive.
class Range : public Validator {
@ -501,10 +463,13 @@ class Range : public Validator {
///
/// 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> Range(T min, T max) {
std::stringstream out;
out << detail::type_name<T>() << " in [" << min << " - " << max << "]";
description(out.str());
template <typename T>
Range(T min, T max, const std::string &validator_name = std::string{}) : Validator(validator_name) {
if(validator_name.empty()) {
std::stringstream out;
out << detail::type_name<T>() << " in [" << min << " - " << max << "]";
description(out.str());
}
func_ = [min, max](std::string &input) {
T val;
@ -518,9 +483,17 @@ class Range : public Validator {
}
/// Range of one value is 0 to value
template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {}
template <typename T>
explicit Range(T max, const std::string &validator_name = std::string{})
: Range(static_cast<T>(0), max, validator_name) {}
};
/// Check for a non negative number
const Range NonNegativeNumber(std::numeric_limits<double>::max(), "NONNEGATIVE");
/// Check for a positive valued number (val>0.0), min() her 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:

View File

@ -1815,6 +1815,24 @@ TEST_F(TApp, RangeDouble) {
run();
}
TEST_F(TApp, typeCheck) {
/// Note that this must be a double in Range, too
app.add_option("--one")->check(CLI::TypeValidator<unsigned int>());
args = {"--one=1"};
EXPECT_NO_THROW(run());
args = {"--one=-7"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--one=error"};
EXPECT_THROW(run(), CLI::ValidationError);
args = {"--one=4.568"};
EXPECT_THROW(run(), CLI::ValidationError);
}
// Check to make sure programmatic access to left over is available
TEST_F(TApp, AllowExtras) {