1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +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:
Viacheslav Kroilov 2019-05-18 06:22:17 +02:00 committed by Henry Schreiner
parent b6e3fb126a
commit 59a36565fe
5 changed files with 837 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

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