From 39988ec62d5b22b01c2887d1ee5ee5f60a050368 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Sat, 4 Nov 2023 14:57:01 -0500 Subject: [PATCH] Initial commit for implementing MutuallyExclusiveGroup --- include/argparse/argparse.hpp | 60 ++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/test_mutually_exclusive_group.cpp | 21 +++++++++ 3 files changed, 82 insertions(+) create mode 100644 test/test_mutually_exclusive_group.cpp diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index fa31ef0..a66c8ed 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -52,6 +52,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -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 Argument &add_argument(Targs... f_args) { + auto &argument = m_parent.add_argument(std::forward(f_args)...); + m_elements.insert(&argument); + return argument; + } + + private: + ArgumentParser &m_parent; + bool m_required{false}; + std::unordered_set 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 @@ -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::iterator; + using mutex_group_it = std::vector::iterator; using argument_parser_it = std::list>::iterator; @@ -2030,6 +2087,9 @@ private: std::list> m_subparsers; std::map m_subparser_map; std::map m_subparser_used; + std::vector + m_mutually_exclusive_groups; /// TODO: Add this to the copy/move + /// constructors }; } // namespace argparse diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 718e14a..8e942be 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 diff --git a/test/test_mutually_exclusive_group.cpp b/test/test_mutually_exclusive_group.cpp new file mode 100644 index 0000000..e83e4f7 --- /dev/null +++ b/test/test_mutually_exclusive_group.cpp @@ -0,0 +1,21 @@ +#ifdef WITH_MODULE +import argparse; +#else +#include +#endif +#include + +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); +} \ No newline at end of file