mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-03 22:54:39 +00:00
#277 Added in-built support for string_type choices
This commit is contained in:
parent
9fe48c74e4
commit
9bb553b882
@ -509,10 +509,12 @@ public:
|
|||||||
m_num_args_range = NArgsRange{0, 1};
|
m_num_args_range = NArgsRange{0, 1};
|
||||||
break;
|
break;
|
||||||
case nargs_pattern::any:
|
case nargs_pattern::any:
|
||||||
m_num_args_range = NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
|
m_num_args_range =
|
||||||
|
NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
|
||||||
break;
|
break;
|
||||||
case nargs_pattern::at_least_one:
|
case nargs_pattern::at_least_one:
|
||||||
m_num_args_range = NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
|
m_num_args_range =
|
||||||
|
NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
@ -523,6 +525,80 @@ public:
|
|||||||
return nargs(nargs_pattern::any);
|
return nargs(nargs_pattern::any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_choice(const std::string &choice) {
|
||||||
|
if (!m_choices.has_value()) {
|
||||||
|
/// create it
|
||||||
|
m_choices = std::vector<std::string>{};
|
||||||
|
}
|
||||||
|
m_choices.value().push_back(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument &choices() {
|
||||||
|
if (!m_choices.has_value()) {
|
||||||
|
throw std::runtime_error("Zero choices provided");
|
||||||
|
}
|
||||||
|
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...);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void find_default_value_in_choices_or_throw() const {
|
||||||
|
|
||||||
|
const auto &choices = m_choices.value();
|
||||||
|
|
||||||
|
if (m_default_value.has_value()) {
|
||||||
|
if (std::find(choices.begin(), choices.end(), m_default_value_repr) ==
|
||||||
|
choices.end()) {
|
||||||
|
// provided arg not in list of allowed choices
|
||||||
|
// report error
|
||||||
|
|
||||||
|
std::string choices_as_csv =
|
||||||
|
std::accumulate(choices.begin(), choices.end(), std::string(),
|
||||||
|
[](const std::string &a, const std::string &b) {
|
||||||
|
return a + (a.empty() ? "" : ", ") + b;
|
||||||
|
});
|
||||||
|
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::string{"Invalid default value "} + m_default_value_repr +
|
||||||
|
" - allowed options: {" + choices_as_csv + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
void find_value_in_choices_or_throw(Iterator it) const {
|
||||||
|
|
||||||
|
const auto &choices = m_choices.value();
|
||||||
|
|
||||||
|
if (std::find(choices.begin(), choices.end(), *it) == choices.end()) {
|
||||||
|
// provided arg not in list of allowed choices
|
||||||
|
// report error
|
||||||
|
|
||||||
|
std::string choices_as_csv =
|
||||||
|
std::accumulate(choices.begin(), choices.end(), std::string(),
|
||||||
|
[](const std::string &a, const std::string &b) {
|
||||||
|
return a + (a.empty() ? "" : ", ") + b;
|
||||||
|
});
|
||||||
|
|
||||||
|
throw std::runtime_error(std::string{"Invalid argument "} +
|
||||||
|
details::repr(*it) + " - allowed options: {" +
|
||||||
|
choices_as_csv + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
Iterator consume(Iterator start, Iterator end,
|
Iterator consume(Iterator start, Iterator end,
|
||||||
std::string_view used_name = {}) {
|
std::string_view used_name = {}) {
|
||||||
@ -532,6 +608,14 @@ public:
|
|||||||
m_is_used = true;
|
m_is_used = true;
|
||||||
m_used_name = used_name;
|
m_used_name = used_name;
|
||||||
|
|
||||||
|
if (m_choices.has_value()) {
|
||||||
|
// Check each value in (start, end) and make sure
|
||||||
|
// it is in the list of allowed choices/options
|
||||||
|
for (auto it = start; it != end; ++it) {
|
||||||
|
find_value_in_choices_or_throw(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto num_args_max = m_num_args_range.get_max();
|
const auto num_args_max = m_num_args_range.get_max();
|
||||||
const auto num_args_min = m_num_args_range.get_min();
|
const auto num_args_min = m_num_args_range.get_min();
|
||||||
std::size_t dist = 0;
|
std::size_t dist = 0;
|
||||||
@ -602,6 +686,12 @@ public:
|
|||||||
throw_nargs_range_validation_error();
|
throw_nargs_range_validation_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_choices.has_value()) {
|
||||||
|
// Make sure the default value (if provided)
|
||||||
|
// is in the list of choices
|
||||||
|
find_default_value_in_choices_or_throw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_inline_usage() const {
|
std::string get_inline_usage() const {
|
||||||
@ -738,8 +828,7 @@ public:
|
|||||||
using ValueType = typename T::value_type;
|
using ValueType = typename T::value_type;
|
||||||
auto lhs = get<T>();
|
auto lhs = get<T>();
|
||||||
return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
|
return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
|
||||||
std::end(rhs),
|
std::end(rhs), [](const auto &a, const auto &b) {
|
||||||
[](const auto &a, const auto &b) {
|
|
||||||
return std::any_cast<const ValueType &>(a) == b;
|
return std::any_cast<const ValueType &>(a) == b;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1064,6 +1153,7 @@ private:
|
|||||||
std::any m_default_value;
|
std::any m_default_value;
|
||||||
std::string m_default_value_repr;
|
std::string m_default_value_repr;
|
||||||
std::any m_implicit_value;
|
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 &)>;
|
using valued_action = std::function<std::any(const std::string &)>;
|
||||||
using void_action = std::function<void(const std::string &)>;
|
using void_action = std::function<void(const std::string &)>;
|
||||||
std::variant<valued_action, void_action> m_action{
|
std::variant<valued_action, void_action> m_action{
|
||||||
@ -1152,16 +1242,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
auto arg_used = std::any_of(m_argument_map.cbegin(),
|
auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(),
|
||||||
m_argument_map.cend(),
|
[](auto &it) { return it.second->m_is_used; });
|
||||||
[](auto &it) {
|
auto subparser_used =
|
||||||
return it.second->m_is_used;
|
std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(),
|
||||||
});
|
[](auto &it) { return it.second; });
|
||||||
auto subparser_used = std::any_of(m_subparser_used.cbegin(),
|
|
||||||
m_subparser_used.cend(),
|
|
||||||
[](auto &it) {
|
|
||||||
return it.second;
|
|
||||||
});
|
|
||||||
|
|
||||||
return m_is_parsed && (arg_used || subparser_used);
|
return m_is_parsed && (arg_used || subparser_used);
|
||||||
}
|
}
|
||||||
@ -1215,8 +1300,7 @@ public:
|
|||||||
/* Getter for arguments and subparsers.
|
/* Getter for arguments and subparsers.
|
||||||
* @throws std::logic_error in case of an invalid argument or subparser name
|
* @throws std::logic_error in case of an invalid argument or subparser name
|
||||||
*/
|
*/
|
||||||
template <typename T = Argument>
|
template <typename T = Argument> T &at(std::string_view name) {
|
||||||
T& at(std::string_view name) {
|
|
||||||
if constexpr (std::is_same_v<T, Argument>) {
|
if constexpr (std::is_same_v<T, Argument>) {
|
||||||
return (*this)[name];
|
return (*this)[name];
|
||||||
} else {
|
} else {
|
||||||
@ -1692,7 +1776,8 @@ private:
|
|||||||
}
|
}
|
||||||
std::size_t max_size = 0;
|
std::size_t max_size = 0;
|
||||||
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
|
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
|
||||||
max_size = std::max<std::size_t>(max_size, argument->get_arguments_length());
|
max_size =
|
||||||
|
std::max<std::size_t>(max_size, argument->get_arguments_length());
|
||||||
}
|
}
|
||||||
for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
|
for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
|
||||||
max_size = std::max<std::size_t>(max_size, command.size());
|
max_size = std::max<std::size_t>(max_size, command.size());
|
||||||
|
@ -29,6 +29,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
|
|||||||
test_append.cpp
|
test_append.cpp
|
||||||
test_as_container.cpp
|
test_as_container.cpp
|
||||||
test_bool_operator.cpp
|
test_bool_operator.cpp
|
||||||
|
test_choices.cpp
|
||||||
test_compound_arguments.cpp
|
test_compound_arguments.cpp
|
||||||
test_container_arguments.cpp
|
test_container_arguments.cpp
|
||||||
test_const_correct.cpp
|
test_const_correct.cpp
|
||||||
|
@ -5,8 +5,8 @@ import argparse;
|
|||||||
#endif
|
#endif
|
||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ TEST_CASE("Get argument with .at()" * test_suite("as_container")) {
|
|||||||
|
|
||||||
SUBCASE("with unknown argument") {
|
SUBCASE("with unknown argument") {
|
||||||
program.parse_args({"test"});
|
program.parse_args({"test"});
|
||||||
REQUIRE_THROWS_WITH_AS(program.at("--folder"),
|
REQUIRE_THROWS_WITH_AS(program.at("--folder"), "No such argument: --folder",
|
||||||
"No such argument: --folder", std::logic_error);
|
std::logic_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,8 @@ TEST_CASE("Get subparser with .at()" * test_suite("as_container")) {
|
|||||||
SUBCASE("and its argument") {
|
SUBCASE("and its argument") {
|
||||||
program.parse_args({"test", "walk", "4km/h"});
|
program.parse_args({"test", "walk", "4km/h"});
|
||||||
REQUIRE(&(program.at<argparse::ArgumentParser>("walk")) == &walk_cmd);
|
REQUIRE(&(program.at<argparse::ArgumentParser>("walk")) == &walk_cmd);
|
||||||
REQUIRE(&(program.at<argparse::ArgumentParser>("walk").at("speed")) == &speed);
|
REQUIRE(&(program.at<argparse::ArgumentParser>("walk").at("speed")) ==
|
||||||
|
&speed);
|
||||||
REQUIRE(program.at<argparse::ArgumentParser>("walk").is_used("speed"));
|
REQUIRE(program.at<argparse::ArgumentParser>("walk").is_used("speed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,7 @@ import argparse;
|
|||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
TEST_CASE("ArgumentParser in bool context" *
|
TEST_CASE("ArgumentParser in bool context" * test_suite("argument_parser")) {
|
||||||
test_suite("argument_parser")) {
|
|
||||||
argparse::ArgumentParser program("test");
|
argparse::ArgumentParser program("test");
|
||||||
program.add_argument("cases").remaining();
|
program.add_argument("cases").remaining();
|
||||||
|
|
||||||
|
70
test/test_choices.cpp
Normal file
70
test/test_choices.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#ifdef WITH_MODULE
|
||||||
|
import argparse;
|
||||||
|
#else
|
||||||
|
#include <argparse/argparse.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <doctest.hpp>
|
||||||
|
|
||||||
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
TEST_CASE("Parse argument that is provided zero choices" *
|
||||||
|
test_suite("choices")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
REQUIRE_THROWS_WITH_AS(program.add_argument("color").choices(),
|
||||||
|
"Zero choices provided", std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse argument that is in the fixed number of allowed choices" *
|
||||||
|
test_suite("choices")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("color").choices("red", "green", "blue");
|
||||||
|
|
||||||
|
program.parse_args({"test", "red"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse argument that is in the fixed number of allowed choices, with "
|
||||||
|
"invalid default" *
|
||||||
|
test_suite("choices")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("color").default_value("yellow").choices("red", "green",
|
||||||
|
"blue");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test"}),
|
||||||
|
"Invalid default value \"yellow\" - allowed options: {red, green, blue}",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse invalid argument that is not in the fixed number of allowed "
|
||||||
|
"choices" *
|
||||||
|
test_suite("choices")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("color").choices("red", "green", "blue");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test", "red2"}),
|
||||||
|
"Invalid argument \"red2\" - allowed options: {red, green, blue}",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Parse multiple arguments that are in the fixed number of allowed choices" *
|
||||||
|
test_suite("choices")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("color").nargs(2).choices("red", "green", "blue");
|
||||||
|
|
||||||
|
program.parse_args({"test", "red", "green"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse multiple arguments one of which is not in the fixed number of "
|
||||||
|
"allowed choices" *
|
||||||
|
test_suite("choices")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("color").nargs(2).choices("red", "green", "blue");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test", "red", "green2"}),
|
||||||
|
"Invalid argument \"green2\" - allowed options: {red, green, blue}",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
@ -5,9 +5,9 @@ import argparse;
|
|||||||
#endif
|
#endif
|
||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import argparse;
|
|||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ import argparse;
|
|||||||
#endif
|
#endif
|
||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
@ -82,22 +82,19 @@ TEST_CASE("Users can replace default -h/--help" * test_suite("help")) {
|
|||||||
|
|
||||||
TEST_CASE("Multiline help message alignment") {
|
TEST_CASE("Multiline help message alignment") {
|
||||||
// '#' is used at the beginning of each help message line to simplify testing.
|
// '#' is used at the beginning of each help message line to simplify testing.
|
||||||
// It is important to ensure that this character doesn't appear elsewhere in the test case.
|
// It is important to ensure that this character doesn't appear elsewhere in
|
||||||
// Default arguments (e.g., -h/--help, -v/--version) are not included in this test.
|
// the test case. Default arguments (e.g., -h/--help, -v/--version) are not
|
||||||
|
// included in this test.
|
||||||
argparse::ArgumentParser program("program");
|
argparse::ArgumentParser program("program");
|
||||||
program.add_argument("INPUT1")
|
program.add_argument("INPUT1").help(
|
||||||
.help(
|
|
||||||
"#This is the first line of help message.\n"
|
"#This is the first line of help message.\n"
|
||||||
"#And this is the second line of help message."
|
"#And this is the second line of help message.");
|
||||||
);
|
program.add_argument("program_input2").help("#There is only one line.");
|
||||||
program.add_argument("program_input2")
|
|
||||||
.help("#There is only one line.");
|
|
||||||
program.add_argument("-p", "--prog_input3")
|
program.add_argument("-p", "--prog_input3")
|
||||||
.help(
|
.help(
|
||||||
R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
#Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
#Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
||||||
#accusantium doloremque laudantium, totam rem aperiam...)"
|
#accusantium doloremque laudantium, totam rem aperiam...)");
|
||||||
);
|
|
||||||
program.add_argument("--verbose").default_value(false).implicit_value(true);
|
program.add_argument("--verbose").default_value(false).implicit_value(true);
|
||||||
|
|
||||||
std::ostringstream stream;
|
std::ostringstream stream;
|
||||||
@ -107,7 +104,8 @@ R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|||||||
auto help_message_start = std::string::npos;
|
auto help_message_start = std::string::npos;
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(iss, line)) {
|
while (std::getline(iss, line)) {
|
||||||
// Find the position of '#', which indicates the start of the help message line
|
// Find the position of '#', which indicates the start of the help message
|
||||||
|
// line
|
||||||
auto pos = line.find('#');
|
auto pos = line.find('#');
|
||||||
|
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
|
@ -5,8 +5,8 @@ import argparse;
|
|||||||
#endif
|
#endif
|
||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import argparse.details;
|
|||||||
#endif
|
#endif
|
||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
#include <set>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
@ -6,8 +6,8 @@ import argparse;
|
|||||||
#include <doctest.hpp>
|
#include <doctest.hpp>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using doctest::test_suite;
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user