mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-04 07:04:39 +00:00
Merge pull request #301 from p-ranav/feature/221_mutex_args
Closes #221
This commit is contained in:
commit
086c8f3db0
33
README.md
33
README.md
@ -25,6 +25,7 @@
|
|||||||
* [Deciding if the value was given by the user](#deciding-if-the-value-was-given-by-the-user)
|
* [Deciding if the value was given by the user](#deciding-if-the-value-was-given-by-the-user)
|
||||||
* [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments)
|
* [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments)
|
||||||
* [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value)
|
* [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value)
|
||||||
|
* [Mutually Exclusive Group](#mutually-exclusive-group)
|
||||||
* [Negative Numbers](#negative-numbers)
|
* [Negative Numbers](#negative-numbers)
|
||||||
* [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments)
|
* [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments)
|
||||||
* [Printing Help](#printing-help)
|
* [Printing Help](#printing-help)
|
||||||
@ -280,6 +281,38 @@ program.parse_args(argc, argv); // Example: ./main -VVVV
|
|||||||
std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4
|
std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Mutually Exclusive Group
|
||||||
|
|
||||||
|
Create a mutually exclusive group using `program.add_mutually_exclusive_group(required = false)`. `argparse`` will make sure that only one of the arguments in the mutually exclusive group was present on the command line:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto &group = program.add_mutually_exclusive_group();
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
```
|
||||||
|
|
||||||
|
with the following usage will yield an error:
|
||||||
|
|
||||||
|
```console
|
||||||
|
foo@bar:/home/dev/$ ./main --first 1 --second 2
|
||||||
|
Argument '--second VAR' not allowed with '--first VAR'
|
||||||
|
```
|
||||||
|
|
||||||
|
The `add_mutually_exclusive_group()` function also accepts a `required` argument, to indicate that at least one of the mutually exclusive arguments is required:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto &group = program.add_mutually_exclusive_group(true);
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
```
|
||||||
|
|
||||||
|
with the following usage will yield an error:
|
||||||
|
|
||||||
|
```console
|
||||||
|
foo@bar:/home/dev/$ ./main
|
||||||
|
One of the arguments '--first VAR' or '--second VAR' is required
|
||||||
|
```
|
||||||
|
|
||||||
### Negative Numbers
|
### Negative Numbers
|
||||||
|
|
||||||
Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes!
|
Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes!
|
||||||
|
@ -453,12 +453,13 @@ template <typename T> struct IsChoiceTypeSupported {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename StringType>
|
template <typename StringType>
|
||||||
int get_levenshtein_distance(const StringType &s1, const StringType &s2) {
|
std::size_t get_levenshtein_distance(const StringType &s1,
|
||||||
std::vector<std::vector<int>> dp(s1.size() + 1,
|
const StringType &s2) {
|
||||||
std::vector<int>(s2.size() + 1, 0));
|
std::vector<std::vector<std::size_t>> dp(
|
||||||
|
s1.size() + 1, std::vector<std::size_t>(s2.size() + 1, 0));
|
||||||
|
|
||||||
for (int i = 0; i <= s1.size(); ++i) {
|
for (std::size_t i = 0; i <= s1.size(); ++i) {
|
||||||
for (int j = 0; j <= s2.size(); ++j) {
|
for (std::size_t j = 0; j <= s2.size(); ++j) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
dp[i][j] = j;
|
dp[i][j] = j;
|
||||||
} else if (j == 0) {
|
} else if (j == 0) {
|
||||||
@ -479,10 +480,10 @@ std::string_view
|
|||||||
get_most_similar_string(const std::map<std::string_view, ValueType> &map,
|
get_most_similar_string(const std::map<std::string_view, ValueType> &map,
|
||||||
const std::string_view input) {
|
const std::string_view input) {
|
||||||
std::string_view most_similar{};
|
std::string_view most_similar{};
|
||||||
int min_distance = std::numeric_limits<int>::max();
|
std::size_t min_distance = std::numeric_limits<std::size_t>::max();
|
||||||
|
|
||||||
for (const auto &entry : map) {
|
for (const auto &entry : map) {
|
||||||
int distance = get_levenshtein_distance(entry.first, input);
|
std::size_t distance = get_levenshtein_distance(entry.first, input);
|
||||||
if (distance < min_distance) {
|
if (distance < min_distance) {
|
||||||
min_distance = distance;
|
min_distance = distance;
|
||||||
most_similar = entry.first;
|
most_similar = entry.first;
|
||||||
@ -1418,6 +1419,19 @@ public:
|
|||||||
m_subparser_map.insert_or_assign(it->get().m_program_name, it);
|
m_subparser_map.insert_or_assign(it->get().m_program_name, it);
|
||||||
m_subparser_used.insert_or_assign(it->get().m_program_name, false);
|
m_subparser_used.insert_or_assign(it->get().m_program_name, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &g : other.m_mutually_exclusive_groups) {
|
||||||
|
MutuallyExclusiveGroup group(*this, g.m_required);
|
||||||
|
for (const auto &arg : g.m_elements) {
|
||||||
|
// Find argument in argument map and add reference to it
|
||||||
|
// in new group
|
||||||
|
// argument_it = other.m_argument_map.find("name")
|
||||||
|
auto first_name = arg->m_names[0];
|
||||||
|
auto it = m_argument_map.find(first_name);
|
||||||
|
group.m_elements.push_back(&(*it->second));
|
||||||
|
}
|
||||||
|
m_mutually_exclusive_groups.push_back(std::move(group));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~ArgumentParser() = default;
|
~ArgumentParser() = default;
|
||||||
@ -1455,6 +1469,43 @@ public:
|
|||||||
return *argument;
|
return *argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MutuallyExclusiveGroup {
|
||||||
|
friend class ArgumentParser;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MutuallyExclusiveGroup() = delete;
|
||||||
|
|
||||||
|
explicit MutuallyExclusiveGroup(ArgumentParser &parent,
|
||||||
|
bool required = false)
|
||||||
|
: m_parent(parent), m_required(required), m_elements({}) {}
|
||||||
|
|
||||||
|
MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
|
||||||
|
MutuallyExclusiveGroup &
|
||||||
|
operator=(const MutuallyExclusiveGroup &other) = delete;
|
||||||
|
|
||||||
|
MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept
|
||||||
|
: m_parent(other.m_parent), m_required(other.m_required),
|
||||||
|
m_elements(std::move(other.m_elements)) {
|
||||||
|
other.m_elements.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Targs> Argument &add_argument(Targs... f_args) {
|
||||||
|
auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
|
||||||
|
m_elements.push_back(&argument);
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ArgumentParser &m_parent;
|
||||||
|
bool m_required{false};
|
||||||
|
std::vector<Argument *> m_elements{};
|
||||||
|
};
|
||||||
|
|
||||||
|
MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) {
|
||||||
|
m_mutually_exclusive_groups.emplace_back(*this, required);
|
||||||
|
return m_mutually_exclusive_groups.back();
|
||||||
|
}
|
||||||
|
|
||||||
// Parameter packed add_parents method
|
// Parameter packed add_parents method
|
||||||
// Accepts a variadic number of ArgumentParser objects
|
// Accepts a variadic number of ArgumentParser objects
|
||||||
template <typename... Targs>
|
template <typename... Targs>
|
||||||
@ -1520,6 +1571,43 @@ public:
|
|||||||
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
|
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
|
||||||
argument->validate();
|
argument->validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check each mutually exclusive group and make sure
|
||||||
|
// there are no constraint violations
|
||||||
|
for (const auto &group : m_mutually_exclusive_groups) {
|
||||||
|
auto mutex_argument_used{false};
|
||||||
|
Argument *mutex_argument_it{nullptr};
|
||||||
|
for (Argument *arg : group.m_elements) {
|
||||||
|
if (!mutex_argument_used && arg->m_is_used) {
|
||||||
|
mutex_argument_used = true;
|
||||||
|
mutex_argument_it = arg;
|
||||||
|
} else if (mutex_argument_used && arg->m_is_used) {
|
||||||
|
// Violation
|
||||||
|
throw std::runtime_error("Argument '" + arg->get_usage_full() +
|
||||||
|
"' not allowed with '" +
|
||||||
|
mutex_argument_it->get_usage_full() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mutex_argument_used && group.m_required) {
|
||||||
|
// at least one argument from the group is
|
||||||
|
// required
|
||||||
|
std::string argument_names{};
|
||||||
|
std::size_t i = 0;
|
||||||
|
std::size_t size = group.m_elements.size();
|
||||||
|
for (Argument *arg : group.m_elements) {
|
||||||
|
if (i + 1 == size) {
|
||||||
|
// last
|
||||||
|
argument_names += "'" + arg->get_usage_full() + "' ";
|
||||||
|
} else {
|
||||||
|
argument_names += "'" + arg->get_usage_full() + "' or ";
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("One of the arguments " + argument_names +
|
||||||
|
"is required");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Call parse_known_args_internal - which does all the work
|
/* Call parse_known_args_internal - which does all the work
|
||||||
@ -2006,6 +2094,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
using argument_it = std::list<Argument>::iterator;
|
using argument_it = std::list<Argument>::iterator;
|
||||||
|
using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
|
||||||
using argument_parser_it =
|
using argument_parser_it =
|
||||||
std::list<std::reference_wrapper<ArgumentParser>>::iterator;
|
std::list<std::reference_wrapper<ArgumentParser>>::iterator;
|
||||||
|
|
||||||
@ -2030,6 +2119,7 @@ private:
|
|||||||
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
|
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
|
||||||
std::map<std::string_view, argument_parser_it> m_subparser_map;
|
std::map<std::string_view, argument_parser_it> m_subparser_map;
|
||||||
std::map<std::string_view, bool> m_subparser_used;
|
std::map<std::string_view, bool> m_subparser_used;
|
||||||
|
std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace argparse
|
} // namespace argparse
|
||||||
|
@ -41,6 +41,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
|
|||||||
test_invalid_arguments.cpp
|
test_invalid_arguments.cpp
|
||||||
test_is_used.cpp
|
test_is_used.cpp
|
||||||
test_issue_37.cpp
|
test_issue_37.cpp
|
||||||
|
test_mutually_exclusive_group.cpp
|
||||||
test_negative_numbers.cpp
|
test_negative_numbers.cpp
|
||||||
test_optional_arguments.cpp
|
test_optional_arguments.cpp
|
||||||
test_parent_parsers.cpp
|
test_parent_parsers.cpp
|
||||||
|
102
test/test_mutually_exclusive_group.cpp
Normal file
102
test/test_mutually_exclusive_group.cpp
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#ifdef WITH_MODULE
|
||||||
|
import argparse;
|
||||||
|
#else
|
||||||
|
#include <argparse/argparse.hpp>
|
||||||
|
#endif
|
||||||
|
#include <doctest.hpp>
|
||||||
|
|
||||||
|
using doctest::test_suite;
|
||||||
|
|
||||||
|
TEST_CASE("Create mutually exclusive group with 2 arguments" *
|
||||||
|
test_suite("mutex_args")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
auto &group = program.add_mutually_exclusive_group();
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test", "--first", "1", "--second", "2"}),
|
||||||
|
"Argument '--second VAR' not allowed with '--first VAR'",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Create mutually exclusive group with 2 arguments with required flag" *
|
||||||
|
test_suite("mutex_args")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
auto &group = program.add_mutually_exclusive_group(true);
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test"}),
|
||||||
|
"One of the arguments '--first VAR' or '--second VAR' is required",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Create mutually exclusive group with 3 arguments with required flag" *
|
||||||
|
test_suite("mutex_args")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
auto &group = program.add_mutually_exclusive_group(true);
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
group.add_argument("--third");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}),
|
||||||
|
"One of the arguments '--first VAR' or '--second VAR' "
|
||||||
|
"or '--third VAR' is required",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Create mutually exclusive group with 2 arguments, then copy the parser" *
|
||||||
|
test_suite("mutex_args")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
auto &group = program.add_mutually_exclusive_group();
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
|
||||||
|
auto program_copy(program);
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program_copy.parse_args({"test", "--first", "1", "--second", "2"}),
|
||||||
|
"Argument '--second VAR' not allowed with '--first VAR'",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create mutually exclusive group with 3 arguments" *
|
||||||
|
test_suite("mutex_args")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
auto &group = program.add_mutually_exclusive_group();
|
||||||
|
group.add_argument("--first");
|
||||||
|
group.add_argument("--second");
|
||||||
|
group.add_argument("--third");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test", "--first", "1", "--third", "2"}),
|
||||||
|
"Argument '--third VAR' not allowed with '--first VAR'",
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create two mutually exclusive groups" * test_suite("mutex_args")) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
|
||||||
|
auto &group_1 = program.add_mutually_exclusive_group();
|
||||||
|
group_1.add_argument("--first");
|
||||||
|
group_1.add_argument("--second");
|
||||||
|
group_1.add_argument("--third");
|
||||||
|
|
||||||
|
auto &group_2 = program.add_mutually_exclusive_group();
|
||||||
|
group_2.add_argument("-a");
|
||||||
|
group_2.add_argument("-b");
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(
|
||||||
|
program.parse_args({"test", "--first", "1", "-a", "2", "-b", "3"}),
|
||||||
|
"Argument '-b VAR' not allowed with '-a VAR'", std::runtime_error);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user