Initial commit for implementing MutuallyExclusiveGroup

This commit is contained in:
Pranav Srinivas Kumar 2023-11-04 14:57:01 -05:00
parent 281f1ab017
commit 39988ec62d
3 changed files with 82 additions and 0 deletions

View File

@ -52,6 +52,7 @@ SOFTWARE.
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>
@ -1455,6 +1456,43 @@ public:
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.insert(&argument);
return argument;
}
private:
ArgumentParser &m_parent;
bool m_required{false};
std::unordered_set<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
// Accepts a variadic number of ArgumentParser objects
template <typename... Targs>
@ -1520,6 +1558,24 @@ public:
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
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() + "'");
}
}
}
}
/* Call parse_known_args_internal - which does all the work
@ -2006,6 +2062,7 @@ private:
}
using argument_it = std::list<Argument>::iterator;
using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
using argument_parser_it =
std::list<std::reference_wrapper<ArgumentParser>>::iterator;
@ -2030,6 +2087,9 @@ private:
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
std::map<std::string_view, argument_parser_it> m_subparser_map;
std::map<std::string_view, bool> m_subparser_used;
std::vector<MutuallyExclusiveGroup>
m_mutually_exclusive_groups; /// TODO: Add this to the copy/move
/// constructors
};
} // namespace argparse

View File

@ -41,6 +41,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_invalid_arguments.cpp
test_is_used.cpp
test_issue_37.cpp
test_mutually_exclusive_group.cpp
test_negative_numbers.cpp
test_optional_arguments.cpp
test_parent_parsers.cpp

View File

@ -0,0 +1,21 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;
TEST_CASE("User-supplied argument" * test_suite("is_used")) {
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 '--first VAR' not allowed with '--second VAR'",
std::runtime_error);
}