This commit is contained in:
Pranav Srinivas Kumar 2023-10-27 12:59:04 -05:00
parent d28188f4d5
commit 5e7ce61ca7
4 changed files with 83 additions and 41 deletions

View File

@ -233,9 +233,15 @@ template <class T, auto Param = 0> struct parse_number {
template <class T> struct parse_number<T, radix_16> {
auto operator()(std::string_view s) -> T {
if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
if (auto [ok, rest] = consume_hex_prefix(s); ok) {
return do_from_chars<T, radix_16>(rest);
}
} else {
// Allow passing hex numbers without prefix
// Shape 'x' already has to be specified
return do_from_chars<T, radix_16>(s);
}
throw std::invalid_argument{"pattern not found"};
}
};
@ -350,24 +356,22 @@ std::string join(StrIt first, StrIt last, const std::string &separator) {
return value.str();
}
template <typename T>
struct can_invoke_to_string {
template <typename T> struct can_invoke_to_string {
template <typename U>
static auto test(int) -> decltype(std::to_string(std::declval<U>()), std::true_type{});
static auto test(int)
-> decltype(std::to_string(std::declval<U>()), std::true_type{});
template <typename U>
static auto test(...) -> std::false_type;
template <typename U> static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T>
struct IsChoiceTypeSupported {
template <typename T> struct IsChoiceTypeSupported {
using CleanType = typename std::decay<T>::type;
static const bool value = std::is_integral<CleanType>::value ||
std::is_same<CleanType, std::string>::value ||
std::is_same<CleanType, std::string_view>::value ||
std::is_same<CleanType, const char*>::value;
std::is_same<CleanType, const char *>::value;
};
} // namespace details
@ -432,8 +436,7 @@ public:
if constexpr (std::is_convertible_v<T, std::string_view>) {
m_default_value_str = std::string{std::string_view{value}};
}
else if constexpr (details::can_invoke_to_string<T>::value) {
} else if constexpr (details::can_invoke_to_string<T>::value) {
m_default_value_str = std::to_string(value);
}
@ -554,18 +557,20 @@ public:
return nargs(nargs_pattern::any);
}
template <typename T>
void add_choice(T&& choice) {
static_assert(details::IsChoiceTypeSupported<T>::value, "Only string or integer type supported for choice");
static_assert(std::is_convertible_v<T, std::string_view> || details::can_invoke_to_string<T>::value, "Choice is not convertible to string_type");
template <typename T> void add_choice(T &&choice) {
static_assert(details::IsChoiceTypeSupported<T>::value,
"Only string or integer type supported for choice");
static_assert(std::is_convertible_v<T, std::string_view> ||
details::can_invoke_to_string<T>::value,
"Choice is not convertible to string_type");
if (!m_choices.has_value()) {
m_choices = std::vector<std::string>{};
}
if constexpr (std::is_convertible_v<T, std::string_view>) {
m_choices.value().push_back(std::string{std::string_view{std::forward<T>(choice)}});
}
else if constexpr (details::can_invoke_to_string<T>::value) {
m_choices.value().push_back(
std::string{std::string_view{std::forward<T>(choice)}});
} else if constexpr (details::can_invoke_to_string<T>::value) {
m_choices.value().push_back(std::to_string(std::forward<T>(choice)));
}
}
@ -578,7 +583,7 @@ public:
}
template <typename T, typename... U>
Argument &choices(T&& first, U&&...rest) {
Argument &choices(T &&first, U &&...rest) {
add_choice(std::forward<T>(first));
choices(std::forward<U>(rest)...);
return *this;
@ -1181,7 +1186,8 @@ private:
std::string m_metavar;
std::any m_default_value;
std::string m_default_value_repr;
std::optional<std::string> m_default_value_str; // used for checking default_value against choices
std::optional<std::string>
m_default_value_str; // used for checking default_value against choices
std::any m_implicit_value;
std::optional<std::vector<std::string>> m_choices{std::nullopt};
using valued_action = std::function<std::any(const std::string &)>;

View File

@ -69,8 +69,8 @@ TEST_CASE("Parse multiple arguments one of which is not in the fixed number of "
std::runtime_error);
}
TEST_CASE(
"Parse multiple arguments that are in the fixed number of allowed INTEGER choices" *
TEST_CASE("Parse multiple arguments that are in the fixed number of allowed "
"INTEGER choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5);
@ -78,8 +78,8 @@ TEST_CASE(
program.parse_args({"test", "1", "2"});
}
TEST_CASE(
"Parse multiple arguments that are not in fixed number of allowed INTEGER choices" *
TEST_CASE("Parse multiple arguments that are not in fixed number of allowed "
"INTEGER choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5);

View File

@ -24,12 +24,14 @@ TEST_CASE("Use a 'string' default value" * test_suite("default_value")) {
}
}
TEST_CASE("Use a default value with flag arguments" * test_suite("default_value")) {
TEST_CASE("Use a default value with flag arguments" *
test_suite("default_value")) {
argparse::ArgumentParser program("test");
program.add_argument("-inc_chr", "--include_chromes")
.help(std::string{"only process the anchor whose one of the end is contained on the specified "
.help(std::string{"only process the anchor whose one of the end is "
"contained on the specified "
"chromatin, used ',' to split."})
.default_value("all");
@ -73,7 +75,8 @@ TEST_CASE("Position of the argument with default value") {
}
SUBCASE("Arg with default value replaces the value if given") {
REQUIRE_NOTHROW(program.parse_args({"test", "-g", "a_different_value", "-s", "./src"}));
REQUIRE_NOTHROW(
program.parse_args({"test", "-g", "a_different_value", "-s", "./src"}));
REQUIRE(program.get("-g") == std::string("a_different_value"));
REQUIRE(program.get("-s") == std::string("./src"));
}

View File

@ -112,6 +112,39 @@ TEST_CASE_TEMPLATE("Parse a hexadecimal integer argument" * test_suite("scan"),
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0XFFFFFFFFFFFFFFFF1"}),
std::range_error);
}
SUBCASE("with hex digit without prefix") {
program.parse_args({"test", "-n", "1a"});
REQUIRE(program.get<T>("-n") == 0x1a);
}
SUBCASE("minus sign without prefix produces an optional argument") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-1"}),
std::invalid_argument);
}
SUBCASE("plus sign without prefix is not allowed") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+1a"}),
std::invalid_argument);
}
SUBCASE("without prefix does not fit") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "FFFFFFFFFFFFFFFF1"}),
std::range_error);
}
}
TEST_CASE("Parse multiple hex numbers without prefix" * test_suite("scan")) {
argparse::ArgumentParser program("test");
program.add_argument("-x", "--hex")
.help("bytes in hex separated by spaces")
.nargs(1, std::numeric_limits<std::size_t>::max())
.scan<'x', uint8_t>();
REQUIRE_NOTHROW(
program.parse_args({"test", "-x", "f2", "b2", "10", "80", "64"}));
const auto &input_bytes = program.get<std::vector<uint8_t>>("-x");
REQUIRE((input_bytes == std::vector<uint8_t>{0xf2, 0xb2, 0x10, 0x80, 0x64}));
}
TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"),