diff --git a/README.md b/README.md index 6bf2046..9b83463 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f54d4c3..0502894 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -350,6 +350,17 @@ std::string join(StrIt first, StrIt last, const std::string &separator) { return value.str(); } +template +struct can_invoke_to_string { + template + static auto test(int) -> decltype(std::to_string(std::declval()), std::true_type{}); + + template + static auto test(...) -> std::false_type; + + static constexpr bool value = decltype(test(0))::value; +}; + } // namespace details enum class nargs_pattern { optional, any, at_least_one }; @@ -408,6 +419,14 @@ public: template Argument &default_value(T &&value) { m_default_value_repr = details::repr(value); + + if constexpr (std::is_convertible_v) { + m_default_value_str = std::string{std::string_view{value}}; + } + else if constexpr (details::can_invoke_to_string::value) { + m_default_value_str = std::to_string(value); + } + m_default_value = std::forward(value); return *this; } @@ -525,12 +544,19 @@ public: return nargs(nargs_pattern::any); } - void add_choice(const std::string &choice) { + template + void add_choice(T&& choice) { + static_assert(std::is_convertible_v || details::can_invoke_to_string::value, "Choice is not convertible to string_type"); if (!m_choices.has_value()) { - /// create it m_choices = std::vector{}; } - m_choices.value().push_back(choice); + + if constexpr (std::is_convertible_v) { + m_choices.value().push_back(std::string{std::string_view{std::forward(choice)}}); + } + else if constexpr (details::can_invoke_to_string::value) { + m_choices.value().push_back(std::to_string(std::forward(choice))); + } } Argument &choices() { @@ -540,18 +566,10 @@ public: return *this; } - template - Argument &choices(const std::string &first, T &...rest) { - add_choice(first); - choices(rest...); - - return *this; - } - - template Argument &choices(const char *first, T &...rest) { - add_choice(first); - choices(rest...); - + template + Argument &choices(T&& first, U&&...rest) { + add_choice(std::forward(first)); + choices(std::forward(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 m_default_value_str; // used for checking default_value against choices std::any m_implicit_value; std::optional> m_choices{std::nullopt}; using valued_action = std::function; diff --git a/test/test_choices.cpp b/test/test_choices.cpp index 9c17d23..d8f4d54 100644 --- a/test/test_choices.cpp +++ b/test/test_choices.cpp @@ -67,4 +67,25 @@ TEST_CASE("Parse multiple arguments one of which is not in the fixed number of " program.parse_args({"test", "red", "green2"}), "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); } \ No newline at end of file