mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 20:23:55 +00:00
Support for size values (640 KB) and numbers with unit in general (#253)
* [WIP] Initial implementation * Add mapping validation * More documentation * Add support for floats in checked_multiply and add tests * Place SuffixedNumber declaration correctly * Add tests * Refactor SuffixedNumber * Add as size value * Update README * SFINAE for checked_multiply() * Mark ctors as explicit * Small fixes * Clang format * Clang format * Adding GCC 4.7 support * Rename SuffixedNumber to AsNumberWithUnit
This commit is contained in:
parent
b6e3fb126a
commit
59a36565fe
@ -243,7 +243,7 @@ The `add_option_function<type>(...` function will typically require the template
|
||||
🚧 Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed. For example:
|
||||
|
||||
```cpp
|
||||
app.add_flag("--flag,!--no-flag,result,"help for flag"); // 🚧
|
||||
app.add_flag("--flag,!--no-flag",result,"help for flag"); // 🚧
|
||||
```
|
||||
|
||||
specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is
|
||||
@ -341,6 +341,8 @@ CLI11 has several Validators built-in that perform some common checks
|
||||
- `CLI::IsMember(...)`: 🚧 Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details.
|
||||
- `CLI::Transformer(...)`: 🚧 Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details.
|
||||
- `CLI::CheckedTransformer(...)`: 🚧 Modify the input using a map, and require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details.
|
||||
- `CLI::AsNumberWithUnit(...)`: Modify the `<NUMBER> <UNIT>` pair by matching the unit and multiplying the number by the corresponding factor. It can be used as a base for transformers, that accept things like size values (`1 KB`) or durations (`0.33 ms`).
|
||||
- `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.
|
||||
|
@ -193,6 +193,11 @@ inline bool valid_name_string(const std::string &str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Verify that str consists of letters only
|
||||
inline bool isalpha(const std::string &str) {
|
||||
return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
|
||||
}
|
||||
|
||||
/// Return a lower case version of a string
|
||||
inline std::string to_lower(std::string str) {
|
||||
std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
|
||||
|
@ -6,8 +6,10 @@
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/TypeTools.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@ -443,14 +445,17 @@ template <typename T> std::string generate_set(const T &set) {
|
||||
}
|
||||
|
||||
/// Generate a string representation of a map
|
||||
template <typename T> std::string generate_map(const T &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),
|
||||
[](const iteration_type_t &v) {
|
||||
return detail::as_string(detail::pair_adaptor<element_t>::first(v)) + "->" +
|
||||
detail::as_string(detail::pair_adaptor<element_t>::second(v));
|
||||
[key_only](const iteration_type_t &v) {
|
||||
auto res = detail::as_string(detail::pair_adaptor<element_t>::first(v));
|
||||
if(!key_only) {
|
||||
res += "->" + detail::as_string(detail::pair_adaptor<element_t>::second(v));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
","));
|
||||
out.push_back('}');
|
||||
@ -505,6 +510,31 @@ auto search(const T &set, const V &val, const std::function<V(V)> &filter_functi
|
||||
return {(it != std::end(setref)), it};
|
||||
}
|
||||
|
||||
/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.
|
||||
template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) {
|
||||
if(a == 0 || b == 0) {
|
||||
a *= b;
|
||||
return true;
|
||||
}
|
||||
T c = a * b;
|
||||
if(c / a != b) {
|
||||
return false;
|
||||
}
|
||||
a = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise.
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_multiply(T &a, T b) {
|
||||
T c = a * b;
|
||||
if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) {
|
||||
return false;
|
||||
}
|
||||
a = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
/// Verify items are in a set
|
||||
class IsMember : public Validator {
|
||||
@ -707,7 +737,7 @@ class CheckedTransformer : public Validator {
|
||||
: CheckedTransformer(std::forward<T>(mapping),
|
||||
[filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
|
||||
other...) {}
|
||||
}; // namespace CLI
|
||||
};
|
||||
|
||||
/// 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); }
|
||||
@ -722,6 +752,197 @@ inline std::string ignore_space(std::string 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 {
|
||||
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(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);
|
||||
}
|
||||
|
||||
bool converted = detail::lexical_cast(input, num);
|
||||
if(!converted) {
|
||||
throw ValidationError("Value " + input + " could not be converted to " + detail::type_name<Number>());
|
||||
}
|
||||
|
||||
if(unit.empty()) {
|
||||
// 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));
|
||||
}
|
||||
|
||||
// perform safe multiplication
|
||||
bool ok = detail::checked_multiply(num, it->second);
|
||||
if(!ok) {
|
||||
throw ValidationError(detail::as_string(num) + " multiplied by " + unit +
|
||||
" factor would cause number overflow. Use smaller value.");
|
||||
}
|
||||
input = detail::as_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("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();
|
||||
}
|
||||
};
|
||||
|
||||
/// 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 = 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) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
|
||||
if(kb_is_1000) {
|
||||
description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
|
||||
} else {
|
||||
description("SIZE [b, kb(=1024b), ...]");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// Get <size unit, factor> mapping
|
||||
static std::map<std::string, result_t> 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;
|
||||
}
|
||||
|
||||
/// Cache calculated mapping
|
||||
static std::map<std::string, result_t> get_mapping(bool kb_is_1000) {
|
||||
if(kb_is_1000) {
|
||||
static auto m = init_mapping(true);
|
||||
return m;
|
||||
} else {
|
||||
static auto m = init_mapping(false);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
@ -105,6 +105,18 @@ TEST(StringTools, flagValues) {
|
||||
EXPECT_EQ(CLI::detail::to_flag_value("475555233"), 475555233);
|
||||
}
|
||||
|
||||
TEST(StringTools, Validation) {
|
||||
EXPECT_TRUE(CLI::detail::isalpha(""));
|
||||
EXPECT_TRUE(CLI::detail::isalpha("a"));
|
||||
EXPECT_TRUE(CLI::detail::isalpha("abcd"));
|
||||
EXPECT_FALSE(CLI::detail::isalpha("_"));
|
||||
EXPECT_FALSE(CLI::detail::isalpha("2"));
|
||||
EXPECT_FALSE(CLI::detail::isalpha("test test"));
|
||||
EXPECT_FALSE(CLI::detail::isalpha("test "));
|
||||
EXPECT_FALSE(CLI::detail::isalpha(" test"));
|
||||
EXPECT_FALSE(CLI::detail::isalpha("test2"));
|
||||
}
|
||||
|
||||
TEST(Trim, Various) {
|
||||
std::string s1{" sdlfkj sdflk sd s "};
|
||||
std::string a1{"sdlfkj sdflk sd s"};
|
||||
@ -360,6 +372,194 @@ TEST(Validators, ProgramNameSplit) {
|
||||
EXPECT_TRUE(res.second.empty());
|
||||
}
|
||||
|
||||
TEST(CheckedMultiply, Int) {
|
||||
int a = 10;
|
||||
int b = -20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, -200);
|
||||
|
||||
a = 0;
|
||||
b = -20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, 0);
|
||||
|
||||
a = 20;
|
||||
b = 0;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, 0);
|
||||
|
||||
a = std::numeric_limits<int>::max();
|
||||
b = 1;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::max());
|
||||
|
||||
a = std::numeric_limits<int>::max();
|
||||
b = 2;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::max());
|
||||
|
||||
a = std::numeric_limits<int>::max();
|
||||
b = -1;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, -std::numeric_limits<int>::max());
|
||||
|
||||
a = std::numeric_limits<int>::max();
|
||||
b = std::numeric_limits<int>::max();
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::max());
|
||||
|
||||
a = std::numeric_limits<int>::min();
|
||||
b = std::numeric_limits<int>::max();
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::min());
|
||||
|
||||
a = std::numeric_limits<int>::min();
|
||||
b = 1;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::min());
|
||||
|
||||
a = std::numeric_limits<int>::min();
|
||||
b = -1;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::min());
|
||||
|
||||
a = std::numeric_limits<int>::min() / 100;
|
||||
b = 99;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<int>::min() / 100 * 99);
|
||||
}
|
||||
|
||||
TEST(CheckedMultiply, SizeT) {
|
||||
size_t a = 10;
|
||||
size_t b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, 200u);
|
||||
|
||||
a = 0u;
|
||||
b = 20u;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, 0u);
|
||||
|
||||
a = 20u;
|
||||
b = 0u;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, 0u);
|
||||
|
||||
a = std::numeric_limits<size_t>::max();
|
||||
b = 1u;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<size_t>::max());
|
||||
|
||||
a = std::numeric_limits<size_t>::max();
|
||||
b = 2u;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<size_t>::max());
|
||||
|
||||
a = std::numeric_limits<size_t>::max();
|
||||
b = std::numeric_limits<size_t>::max();
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<size_t>::max());
|
||||
|
||||
a = std::numeric_limits<size_t>::max() / 100;
|
||||
b = 99u;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_EQ(a, std::numeric_limits<size_t>::max() / 100u * 99u);
|
||||
}
|
||||
|
||||
TEST(CheckedMultiply, Float) {
|
||||
float a = 10;
|
||||
float b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, 200);
|
||||
|
||||
a = 0;
|
||||
b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, 0);
|
||||
|
||||
a = INFINITY;
|
||||
b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, INFINITY);
|
||||
|
||||
a = 2;
|
||||
b = -INFINITY;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, -INFINITY);
|
||||
|
||||
a = std::numeric_limits<float>::max() / 100;
|
||||
b = 1;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, std::numeric_limits<float>::max() / 100);
|
||||
|
||||
a = std::numeric_limits<float>::max() / 100;
|
||||
b = 99;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, std::numeric_limits<float>::max() / 100 * 99);
|
||||
|
||||
a = std::numeric_limits<float>::max() / 100;
|
||||
b = 101;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, std::numeric_limits<float>::max() / 100);
|
||||
|
||||
a = std::numeric_limits<float>::max() / 100;
|
||||
b = -99;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, std::numeric_limits<float>::max() / 100 * -99);
|
||||
|
||||
a = std::numeric_limits<float>::max() / 100;
|
||||
b = -101;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_FLOAT_EQ(a, std::numeric_limits<float>::max() / 100);
|
||||
}
|
||||
|
||||
TEST(CheckedMultiply, Double) {
|
||||
double a = 10;
|
||||
double b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, 200);
|
||||
|
||||
a = 0;
|
||||
b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, 0);
|
||||
|
||||
a = INFINITY;
|
||||
b = 20;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, INFINITY);
|
||||
|
||||
a = 2;
|
||||
b = -INFINITY;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, -INFINITY);
|
||||
|
||||
a = std::numeric_limits<double>::max() / 100;
|
||||
b = 1;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, std::numeric_limits<double>::max() / 100);
|
||||
|
||||
a = std::numeric_limits<double>::max() / 100;
|
||||
b = 99;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, std::numeric_limits<double>::max() / 100 * 99);
|
||||
|
||||
a = std::numeric_limits<double>::max() / 100;
|
||||
b = 101;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, std::numeric_limits<double>::max() / 100);
|
||||
|
||||
a = std::numeric_limits<double>::max() / 100;
|
||||
b = -99;
|
||||
ASSERT_TRUE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, std::numeric_limits<double>::max() / 100 * -99);
|
||||
|
||||
a = std::numeric_limits<double>::max() / 100;
|
||||
b = -101;
|
||||
ASSERT_FALSE(CLI::detail::checked_multiply(a, b));
|
||||
ASSERT_DOUBLE_EQ(a, std::numeric_limits<double>::max() / 100);
|
||||
}
|
||||
|
||||
// Yes, this is testing an app_helper :)
|
||||
TEST(AppHelper, TempfileCreated) {
|
||||
std::string name = "TestFileNotUsed.txt";
|
||||
|
@ -431,3 +431,406 @@ TEST_F(TApp, BoundTests) {
|
||||
EXPECT_TRUE(help.find("bounded to") != std::string::npos);
|
||||
EXPECT_TRUE(help.find("[3.4 - 5.9]") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitCorrecltySplitNumber) {
|
||||
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();
|
||||
EXPECT_EQ(value, 42);
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
EXPECT_EQ(value, 420);
|
||||
|
||||
args = {"-n", " 42 cc "};
|
||||
run();
|
||||
EXPECT_EQ(value, 42000);
|
||||
args = {"-n", " -42 cc "};
|
||||
run();
|
||||
EXPECT_EQ(value, -42000);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitFloatTest) {
|
||||
std::map<std::string, double> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
|
||||
double value = 0;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "42"};
|
||||
run();
|
||||
EXPECT_DOUBLE_EQ(value, 42);
|
||||
|
||||
args = {"-n", ".5"};
|
||||
run();
|
||||
EXPECT_DOUBLE_EQ(value, .5);
|
||||
|
||||
args = {"-n", "42.5 a"};
|
||||
run();
|
||||
EXPECT_DOUBLE_EQ(value, 425);
|
||||
|
||||
args = {"-n", "42.cc"};
|
||||
run();
|
||||
EXPECT_DOUBLE_EQ(value, 42000);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitCaseSensitive) {
|
||||
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();
|
||||
EXPECT_EQ(value, 420);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
EXPECT_EQ(value, 4200);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitCaseInsensitive) {
|
||||
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();
|
||||
EXPECT_EQ(value, 420);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
EXPECT_EQ(value, 420);
|
||||
|
||||
args = {"-n", "42b"};
|
||||
run();
|
||||
EXPECT_EQ(value, 4200);
|
||||
|
||||
args = {"-n", "42B"};
|
||||
run();
|
||||
EXPECT_EQ(value, 4200);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitMandatoryUnit) {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
|
||||
|
||||
int value;
|
||||
app.add_option("-n", value)
|
||||
->transform(CLI::AsNumberWithUnit(mapping,
|
||||
CLI::AsNumberWithUnit::Options(CLI::AsNumberWithUnit::UNIT_REQUIRED |
|
||||
CLI::AsNumberWithUnit::CASE_SENSITIVE)));
|
||||
|
||||
args = {"-n", "42a"};
|
||||
run();
|
||||
EXPECT_EQ(value, 420);
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
EXPECT_EQ(value, 4200);
|
||||
|
||||
args = {"-n", "42"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitMandatoryUnit2) {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
|
||||
|
||||
int value;
|
||||
app.add_option("-n", value)
|
||||
->transform(CLI::AsNumberWithUnit(mapping,
|
||||
CLI::AsNumberWithUnit::Options(CLI::AsNumberWithUnit::UNIT_REQUIRED |
|
||||
CLI::AsNumberWithUnit::CASE_INSENSITIVE)));
|
||||
|
||||
args = {"-n", "42A"};
|
||||
run();
|
||||
EXPECT_EQ(value, 420);
|
||||
|
||||
args = {"-n", "42b"};
|
||||
run();
|
||||
EXPECT_EQ(value, 4200);
|
||||
|
||||
args = {"-n", "42"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitBadMapping) {
|
||||
EXPECT_THROW(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"A", 100}},
|
||||
CLI::AsNumberWithUnit::CASE_INSENSITIVE),
|
||||
CLI::ValidationError);
|
||||
EXPECT_THROW(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"9", 100}}), CLI::ValidationError);
|
||||
EXPECT_THROW(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"AA A", 100}}), CLI::ValidationError);
|
||||
EXPECT_THROW(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"", 100}}), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitBadInput) {
|
||||
std::map<std::string, int> mapping{{"a", 10}, {"b", 100}};
|
||||
|
||||
int value;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "13 a b"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
args = {"-n", "13 c"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
args = {"-n", "a"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
args = {"-n", "12.0a"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
args = {"-n", "a5"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
args = {"-n", ""};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
args = {"-n", "13 a-"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitIntOverflow) {
|
||||
std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}};
|
||||
|
||||
int32_t value;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "1000 a"};
|
||||
run();
|
||||
EXPECT_EQ(value, 1000000000);
|
||||
|
||||
args = {"-n", "1000000 a"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "-1000000 a"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "21474836 b"};
|
||||
run();
|
||||
EXPECT_EQ(value, 2147483600);
|
||||
|
||||
args = {"-n", "21474836 c"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
}
|
||||
|
||||
TEST_F(TApp, NumberWithUnitFloatOverflow) {
|
||||
std::map<std::string, float> mapping{{"a", 2}, {"b", 1}, {"c", 0}};
|
||||
|
||||
float value;
|
||||
app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
|
||||
|
||||
args = {"-n", "3e+38 a"};
|
||||
EXPECT_THROW(run(), CLI::ValidationError);
|
||||
|
||||
args = {"-n", "3e+38 b"};
|
||||
run();
|
||||
EXPECT_FLOAT_EQ(value, 3e+38);
|
||||
|
||||
args = {"-n", "3e+38 c"};
|
||||
run();
|
||||
EXPECT_FLOAT_EQ(value, 0);
|
||||
}
|
||||
|
||||
TEST_F(TApp, AsSizeValue1000_1024) {
|
||||
uint64_t value;
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(true));
|
||||
|
||||
args = {"-s", "10240"};
|
||||
run();
|
||||
EXPECT_EQ(value, 10240u);
|
||||
|
||||
args = {"-s", "1b"};
|
||||
run();
|
||||
EXPECT_FLOAT_EQ(value, 1);
|
||||
|
||||
uint64_t k_value = 1000u;
|
||||
uint64_t ki_value = 1024u;
|
||||
args = {"-s", "1k"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1kb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1 Kb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1ki"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1kib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
k_value = 1000ull * 1000u;
|
||||
ki_value = 1024ull * 1024u;
|
||||
args = {"-s", "1m"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1mb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1mi"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1mib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u;
|
||||
args = {"-s", "1g"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1gb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1gi"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1gib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1t"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1tb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1ti"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1tib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1p"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1pb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1pi"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1pib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
k_value = 1000ull * 1000u * 1000u * 1000u * 1000u * 1000u;
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1e"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1eb"};
|
||||
run();
|
||||
EXPECT_EQ(value, k_value);
|
||||
args = {"-s", "1ei"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1eib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
}
|
||||
|
||||
TEST_F(TApp, AsSizeValue1024) {
|
||||
uint64_t value;
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(false));
|
||||
|
||||
args = {"-s", "10240"};
|
||||
run();
|
||||
EXPECT_EQ(value, 10240u);
|
||||
|
||||
args = {"-s", "1b"};
|
||||
run();
|
||||
EXPECT_FLOAT_EQ(value, 1);
|
||||
|
||||
uint64_t ki_value = 1024u;
|
||||
args = {"-s", "1k"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1kb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1 Kb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1ki"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1kib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
ki_value = 1024ull * 1024u;
|
||||
args = {"-s", "1m"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1mb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1mi"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1mib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u;
|
||||
args = {"-s", "1g"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1gb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1gi"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1gib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1t"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1tb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1ti"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1tib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1p"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1pb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1pi"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1pib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
|
||||
ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
|
||||
args = {"-s", "1e"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1eb"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1ei"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
args = {"-s", "1eib"};
|
||||
run();
|
||||
EXPECT_EQ(value, ki_value);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user