Added support for integer type in choices

This commit is contained in:
Pranav Srinivas Kumar 2023-10-27 10:28:54 -05:00
parent 9bb553b882
commit 0b8d0e2426
3 changed files with 85 additions and 24 deletions

View File

@ -1060,13 +1060,7 @@ argparse::ArgumentParser program("test");
program.add_argument("input")
.default_value(std::string{"baz"})
.action([](const std::string& value) {
static const std::vector<std::string> choices = { "foo", "bar", "baz" };
if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
return value;
}
return std::string{ "baz" };
});
.choices("foo", "bar", "baz");
try {
program.parse_args(argc, argv);
@ -1083,7 +1077,34 @@ std::cout << input << std::endl;
```console
foo@bar:/home/dev/$ ./main fex
baz
Invalid argument "fex" - allowed options: {foo, bar, baz}
```
Using choices also works in integer types, e.g.,
```cpp
argparse::ArgumentParser program("test");
program.add_argument("input")
.default_value(0)
.choices(0, 1, 2, 3, 4, 5);
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto input = program.get("input");
std::cout << input << std::endl;
```
```console
foo@bar:/home/dev/$ ./main 6
Invalid argument "6" - allowed options: {0, 1, 2, 3, 4, 5}
```
## Using `option=value` syntax

View File

@ -350,6 +350,17 @@ std::string join(StrIt first, StrIt last, const std::string &separator) {
return value.str();
}
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{});
template <typename U>
static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test<T>(0))::value;
};
} // namespace details
enum class nargs_pattern { optional, any, at_least_one };
@ -408,6 +419,14 @@ public:
template <typename T> Argument &default_value(T &&value) {
m_default_value_repr = details::repr(value);
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) {
m_default_value_str = std::to_string(value);
}
m_default_value = std::forward<T>(value);
return *this;
}
@ -525,12 +544,19 @@ public:
return nargs(nargs_pattern::any);
}
void add_choice(const std::string &choice) {
template <typename T>
void add_choice(T&& 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()) {
/// create it
m_choices = std::vector<std::string>{};
}
m_choices.value().push_back(choice);
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::to_string(std::forward<T>(choice)));
}
}
Argument &choices() {
@ -540,18 +566,10 @@ public:
return *this;
}
template <typename... T>
Argument &choices(const std::string &first, T &...rest) {
add_choice(first);
choices(rest...);
return *this;
}
template <typename... T> Argument &choices(const char *first, T &...rest) {
add_choice(first);
choices(rest...);
template <typename T, typename... U>
Argument &choices(T&& first, U&&...rest) {
add_choice(std::forward<T>(first));
choices(std::forward<U>(rest)...);
return *this;
}
@ -560,7 +578,7 @@ public:
const auto &choices = m_choices.value();
if (m_default_value.has_value()) {
if (std::find(choices.begin(), choices.end(), m_default_value_repr) ==
if (std::find(choices.begin(), choices.end(), m_default_value_str) ==
choices.end()) {
// provided arg not in list of allowed choices
// report error
@ -1152,6 +1170,7 @@ 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::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

@ -68,3 +68,24 @@ TEST_CASE("Parse multiple arguments one of which is not in the fixed number of "
"Invalid argument \"green2\" - allowed options: {red, green, blue}",
std::runtime_error);
}
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);
program.parse_args({"test", "1", "2"});
}
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);
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "6", "7"}),
"Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}",
std::runtime_error);
}