From 7c7c481c439fedfa4325ae5fd792f4ed4c12a4e7 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Thu, 24 Sep 2026 10:28:03 +0200 Subject: [PATCH 1/4] basic attribute getters & argument, group, subparser getters --- include/argon/argument.hpp | 12 +- include/argon/{detail => }/argument_base.hpp | 7 +- include/argon/argument_group.hpp | 4 +- include/argon/{detail => }/argument_name.hpp | 6 +- include/argon/argument_parser.hpp | 170 ++++++++++++++---- include/argon/detail/argument_token.hpp | 4 +- include/argon/detail/help_builder.hpp | 2 +- include/argon/exceptions.hpp | 11 +- .../include/argument_parser_test_fixture.hpp | 11 +- tests/include/argument_test_fixture.hpp | 2 +- tests/include/utility.hpp | 4 +- tests/source/test_argument_name.cpp | 4 +- tests/source/test_argument_parser_cfg.cpp | 87 +++++++-- tests/source/test_argument_token.cpp | 4 +- tests/source/test_none_type_argument.cpp | 2 +- tests/source/test_optional_argument.cpp | 2 +- tests/source/test_positional_argument.cpp | 2 +- 17 files changed, 241 insertions(+), 93 deletions(-) rename include/argon/{detail => }/argument_base.hpp (96%) rename include/argon/{detail => }/argument_name.hpp (99%) diff --git a/include/argon/argument.hpp b/include/argon/argument.hpp index a81145f9..4b7dd427 100644 --- a/include/argon/argument.hpp +++ b/include/argon/argument.hpp @@ -8,7 +8,7 @@ #include "argon/action/predefined.hpp" #include "argon/action/util.hpp" -#include "argon/detail/argument_base.hpp" +#include "argon/argument_base.hpp" #include "argon/detail/help_builder.hpp" #include "argon/nargs/range.hpp" #include "argon/traits.hpp" @@ -54,7 +54,7 @@ enum class argument_type : bool { positional, optional }; * @tparam T The value type accepted by the argument (defaults to std::string). */ template -class argument : public detail::typed_argument_base { +class argument : public typed_argument_base { public: using value_type = T; ///< The argument's value type alias. using count_type = nargs::count_type; ///< The argument's count type alias. @@ -68,7 +68,7 @@ class argument : public detail::typed_argument_base { * @param name The name of the positional argument. * @note The constructor is enabled only if `type` is `argument_type::positional`. */ - argument(const detail::argument_name& name) + argument(const argument_name& name) requires(type == argument_type::positional) : _name(name), _nargs_range(_default_nargs_range_actual), _required(_default_required) {} @@ -77,7 +77,7 @@ class argument : public detail::typed_argument_base { * @param name The name of the optional argument. * @note The constructor is enabled only if `type` is `argument_type::optional`. */ - argument(const detail::argument_name& name) + argument(const argument_name& name) requires(type == argument_type::optional) : _name(name), _nargs_range(_default_nargs_range_actual), @@ -97,7 +97,7 @@ class argument : public detail::typed_argument_base { } /// @return Reference the name of the positional argument. - [[nodiscard]] const argon::detail::argument_name& name() const noexcept override { + [[nodiscard]] const argon::argument_name& name() const noexcept override { return this->_name; } @@ -768,7 +768,7 @@ class argument : public detail::typed_argument_base { } // attributes - const argon::detail::argument_name _name; ///< The argument's name. + const argon::argument_name _name; ///< The argument's name. std::optional _help_msg; ///< The argument's help message. nargs::range _nargs_range; ///< The argument's nargs range attribute value. [[no_unique_address]] value_arg_specific_type> diff --git a/include/argon/detail/argument_base.hpp b/include/argon/argument_base.hpp similarity index 96% rename from include/argon/detail/argument_base.hpp rename to include/argon/argument_base.hpp index d5714771..bfbfd843 100644 --- a/include/argon/detail/argument_base.hpp +++ b/include/argon/argument_base.hpp @@ -3,13 +3,13 @@ // Licensed under the MIT License. See the LICENSE file in the project root for full license information. /** - * @file argon/detail/argument_base.hpp + * @file argon/argument_base.hpp * @brief Defines the base argument class and common utility. */ #pragma once -#include "argon/detail/argument_name.hpp" +#include "argon/argument_name.hpp" #include "argon/detail/help_builder.hpp" #include "argon/traits.hpp" @@ -21,8 +21,6 @@ namespace argon { class argument_parser; -namespace detail { - /// @brief Argument class interface class argument_base { public: @@ -101,5 +99,4 @@ class typed_argument_base : public argument_base { virtual const std::vector& values() const = 0; }; -} // namespace detail } // namespace argon diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index 2be40660..9985bf3e 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -6,7 +6,7 @@ #pragma once -#include "argon/detail/argument_base.hpp" +#include "argon/argument_base.hpp" #include @@ -130,7 +130,7 @@ class argument_group { friend class argument_parser; private: - using arg_ptr_t = std::shared_ptr; ///< The argument pointer type alias. + using arg_ptr_t = std::shared_ptr; ///< The argument pointer type alias. using arg_ptr_vec_t = std::vector; ///< The argument pointer list type alias. /** diff --git a/include/argon/detail/argument_name.hpp b/include/argon/argument_name.hpp similarity index 99% rename from include/argon/detail/argument_name.hpp rename to include/argon/argument_name.hpp index 2abdfbce..525bb9bc 100644 --- a/include/argon/detail/argument_name.hpp +++ b/include/argon/argument_name.hpp @@ -2,7 +2,7 @@ // This file is part of the CPP-ARGON project (https://github.com/SpectraL519/cpp-argon). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. -/// @file argon/detail/argument_name.hpp +/// @file argon/argument_name.hpp #pragma once @@ -15,8 +15,6 @@ namespace argon { -namespace detail { - /// @brief Structure holding the argument's name. struct argument_name { /// @brief Specifies the type of argument name match. @@ -144,6 +142,8 @@ struct argument_name { static constexpr char flag_char_sentinel = '\0'; ///< Sentinel value for the flag character. }; +namespace detail { + /** * @brief Argument name member discriminator. * diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index f0ad3a0c..4cf54869 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -187,6 +187,25 @@ class argument_parser { ~argument_parser() = default; + /// @brief Returns the parser's name. + [[nodiscard]] std::string_view name() const noexcept { + return this->_name; + } + + /** + * @brief Returns the parser's full program name. + * + * - For top-level parsers, this is the same as the parser's name. + * - For subparsers, the name is prefixed with its parent parser names. + * + * Top-level parser: `git` + * Subparser: `git submodule` + * Nested subparser : `git submodule init` + */ + [[nodiscard]] std::string_view program_name() const noexcept { + return this->_program_name; + } + /** * @brief Set the program version. * @param version The version of the program. @@ -211,6 +230,11 @@ class argument_parser { return *this; } + /// @return The program version if set, std::nullopt otherwise. + [[nodiscard]] const std::optional& program_version() const noexcept { + return this->_program_version; + } + /** * @brief Set the program description. * @param description The description of the program. @@ -221,6 +245,11 @@ class argument_parser { return *this; } + /// @return The program description if set, std::nullopt otherwise. + [[nodiscard]] const std::optional& program_description() const noexcept { + return this->_program_description; + } + /** * @brief Set the verbosity mode. * @note The default verbosity mode value is `false`. @@ -232,6 +261,11 @@ class argument_parser { return *this; } + /// @return The configured verbosity mode flag. + [[nodiscard]] bool is_verbose() const noexcept { + return this->_verbose; + } + /** * @brief Set the unknown argument flags handling policy. * @param policy The unknown arguments policy value. @@ -243,6 +277,11 @@ class argument_parser { return *this; } + /// @return The configured unknown arguments policy. + [[nodiscard]] unknown_policy unknown_arguments_policy() const noexcept { + return this->_unknown_policy; + } + /** * @brief Set the flag character. * @param chr The flag character. @@ -268,6 +307,11 @@ class argument_parser { return *this; } + /// @return The configured flag character. + [[nodiscard]] char flag_char() const noexcept { + return this->_flag_char; + } + /** * @brief Add default arguments to the argument parser. * @tparam ArgvRange Type of the positional argument discriminator range. @@ -333,7 +377,7 @@ class argument_parser { auto full_name = group._format_arg_name(base_name); this->_verify_arg_name_pattern(full_name); - const detail::argument_name arg_name(std::move(full_name)); + const argument_name arg_name(std::move(full_name)); if (this->_is_arg_name_used(arg_name)) throw invalid_configuration::argument_name_used(arg_name); @@ -398,8 +442,8 @@ class argument_parser { const auto arg_name = name_discr == n_primary - ? detail::argument_name{std::move(full_name), "", this->_flag_char} - : detail::argument_name{"", std::move(full_name), this->_flag_char}; + ? argument_name{std::move(full_name), "", this->_flag_char} + : argument_name{"", std::move(full_name), this->_flag_char}; if (this->_is_arg_name_used(arg_name)) throw invalid_configuration::argument_name_used(arg_name); @@ -433,7 +477,7 @@ class argument_parser { auto full_secondary_name = group._format_arg_name(base_secondary_name); this->_verify_arg_name_pattern(full_secondary_name); - const detail::argument_name arg_name( + const argument_name arg_name( std::move(full_primary_name), std::move(full_secondary_name), this->_flag_char ); if (this->_is_arg_name_used(arg_name)) @@ -524,6 +568,16 @@ class argument_parser { .nargs(0ull); } + /** + * @brief Retrieves an argument's base configuration interface by its name. + * @param arg_name The name of the argument. + * @return Constant reference to the argument base. + * @throws argon::lookup_failure if an argument with the given name cannot be found. + */ + [[nodiscard]] const argument_base& argument(std::string_view arg_name) const { + return *this->_get_argument(arg_name); + } + /** * @brief Adds an argument group with the given name to the parser's configuration. * @param name Name of the group. @@ -533,6 +587,38 @@ class argument_parser { return *this->_argument_groups.emplace_back(argument_group::create(*this, name)); } + /** + * @brief Retrieves an argument group by its name. + * @param name The name of the group. + * @return Reference to the argument group. + * @throws argon::lookup_failure if a group with the given name does not exist. + */ + [[nodiscard]] argument_group& group(std::string_view name) { + const auto it = std::ranges::find_if(this->_argument_groups, [name](const auto& grp) { + return grp->_name == name; + }); + if (it == this->_argument_groups.end()) + throw lookup_failure(std::format("Argument group with given name [{}] not found.", name) + ); + return **it; + } + + /** + * @brief Retrieves an argument group by its name. + * @param name The name of the group. + * @return Constant reference to the argument group. + * @throws argon::lookup_failure if a group with the given name does not exist. + */ + [[nodiscard]] const argument_group& group(std::string_view name) const { + const auto it = std::ranges::find_if(this->_argument_groups, [name](const auto& grp) { + return grp->_name == name; + }); + if (it == this->_argument_groups.end()) + throw lookup_failure(std::format("Argument group with given name [{}] not found.", name) + ); + return **it; + } + /** * @brief Adds an subparser with the given name to the parser's configuration. * @param name Name of the subparser. @@ -554,6 +640,36 @@ class argument_parser { ); } + /** + * @brief Retrieves a subparser by its name. + * @param name The name of the subparser. + * @return Reference to the subparser. + * @throws argon::lookup_failure if a subparser with the given name does not exist. + */ + [[nodiscard]] argument_parser& subparser(std::string_view name) { + const auto it = std::ranges::find_if(this->_subparsers, [name](const auto& sub) { + return sub->_name == name; + }); + if (it == this->_subparsers.end()) + throw lookup_failure(std::format("Subparser with given name [{}] not found.", name)); + return **it; + } + + /** + * @brief Retrieves a subparser by its name. + * @param name The name of the subparser. + * @return Constant reference to the subparser. + * @throws argon::lookup_failure if a subparser with the given name does not exist. + */ + [[nodiscard]] const argument_parser& subparser(std::string_view name) const { + const auto it = std::ranges::find_if(this->_subparsers, [name](const auto& sub) { + return sub->_name == name; + }); + if (it == this->_subparsers.end()) + throw lookup_failure(std::format("Subparser with given name [{}] not found.", name)); + return **it; + } + /** * @brief Parses the command-line arguments. * @@ -715,24 +831,7 @@ class argument_parser { } } - /// @brief Returns the parser's name. - [[nodiscard]] std::string_view name() const noexcept { - return this->_name; - } - - /** - * @brief Returns the parser's full program name. - * - * - For top-level parsers, this is the same as the parser's name. - * - For subparsers, the name is prefixed with its parent parser names. - * - * Top-level parser: `git` - * Subparser: `git submodule` - * Nested subparser : `git submodule init` - */ - [[nodiscard]] std::string_view program_name() const noexcept { - return this->_program_name; - } + // --- parsing state and argument value getters --- /** * @brief Check whether this parser was invoked. @@ -802,7 +901,7 @@ class argument_parser { [[nodiscard]] traits::argument_result_type value(std::string_view arg_name) const { const auto arg = this->_get_argument(arg_name); - const auto* typed_arg = dynamic_cast*>(arg.get()); + const auto* typed_arg = dynamic_cast*>(arg.get()); if (not typed_arg) throw type_error::invalid_value_type(arg->name()); @@ -822,7 +921,7 @@ class argument_parser { [[nodiscard]] T value_or(std::string_view arg_name, U&& fallback_value) const { const auto arg = this->_get_argument(arg_name); - const auto* typed_arg = dynamic_cast*>(arg.get()); + const auto* typed_arg = dynamic_cast*>(arg.get()); if (not typed_arg) throw type_error::invalid_value_type(arg->name()); @@ -847,7 +946,7 @@ class argument_parser { [[nodiscard]] const std::vector& values(std::string_view arg_name) const { const auto arg = this->_get_argument(arg_name); - const auto* typed_arg = dynamic_cast*>(arg.get()); + const auto* typed_arg = dynamic_cast*>(arg.get()); if (not typed_arg) throw type_error::invalid_value_type(arg->name()); @@ -902,13 +1001,18 @@ class argument_parser { return os; } + /// @return The universal assignment character used to assign values inline. + [[nodiscard]] static constexpr char assign_char() noexcept { + return _assign_char; + } + #ifdef AP_TESTING /// @brief Friend struct for testing purposes. friend struct ::argon_testing::argument_parser_test_fixture; #endif private: - using arg_ptr_t = std::shared_ptr; + using arg_ptr_t = std::shared_ptr; using arg_ptr_vec_t = std::vector; using arg_ptr_vec_iter_t = typename arg_ptr_vec_t::iterator; @@ -995,7 +1099,7 @@ class argument_parser { */ [[nodiscard]] auto _name_match_predicate( const std::string_view arg_name, - const detail::argument_name::match_type m_type = detail::argument_name::m_any + const argument_name::match_type m_type = argument_name::m_any ) const noexcept { return [=](const arg_ptr_t& arg) { return arg->name().match(arg_name, m_type); }; } @@ -1005,7 +1109,7 @@ class argument_parser { * @param arg_name The name of the argument. * @return Argument predicate based on the provided name. */ - [[nodiscard]] auto _name_match_predicate(const detail::argument_name& arg_name) const noexcept { + [[nodiscard]] auto _name_match_predicate(const argument_name& arg_name) const noexcept { return [&arg_name](const arg_ptr_t& arg) { return arg->name().match(arg_name); }; } @@ -1014,7 +1118,7 @@ class argument_parser { * @param arg_name The name of the argument. * @return True if the argument name is already used, false otherwise. */ - [[nodiscard]] bool _is_arg_name_used(const detail::argument_name& arg_name) const noexcept { + [[nodiscard]] bool _is_arg_name_used(const argument_name& arg_name) const noexcept { const auto predicate = this->_name_match_predicate(arg_name); if (std::ranges::find_if(this->_positional_args, predicate) != this->_positional_args.end()) @@ -1268,9 +1372,7 @@ class argument_parser { const auto opt_arg_it = std::ranges::find_if( this->_optional_args, - this->_name_match_predicate( - expected_secondary_name, detail::argument_name::m_secondary - ) + this->_name_match_predicate(expected_secondary_name, argument_name::m_secondary) ); if (opt_arg_it == this->_optional_args.end()) { @@ -1307,8 +1409,8 @@ class argument_parser { const auto actual_tok_value = this->_strip_flag_prefix(flag_tok); const auto match_type = flag_tok.type == detail::argument_token::t_flag_primary - ? detail::argument_name::m_primary - : detail::argument_name::m_secondary; + ? argument_name::m_primary + : argument_name::m_secondary; return std::ranges::find_if( this->_optional_args, this->_name_match_predicate(actual_tok_value, match_type) diff --git a/include/argon/detail/argument_token.hpp b/include/argon/detail/argument_token.hpp index b25b1e46..5894b450 100644 --- a/include/argon/detail/argument_token.hpp +++ b/include/argon/detail/argument_token.hpp @@ -6,7 +6,7 @@ #pragma once -#include "argon/detail/argument_base.hpp" +#include "argon/argument_base.hpp" #include #include @@ -17,7 +17,7 @@ namespace argon::detail { /// @brief Structure representing a single command-line argument token. struct argument_token { - using arg_ptr_t = std::shared_ptr; ///< Argument pointer type alias. + using arg_ptr_t = std::shared_ptr; ///< Argument pointer type alias. using arg_ptr_vec_t = std::vector; /// @brief The token type discriminator. diff --git a/include/argon/detail/help_builder.hpp b/include/argon/detail/help_builder.hpp index 4a7c2c02..779f71d0 100644 --- a/include/argon/detail/help_builder.hpp +++ b/include/argon/detail/help_builder.hpp @@ -9,7 +9,7 @@ #pragma once -#include "argon/detail/argument_name.hpp" +#include "argon/argument_name.hpp" #include "argon/traits.hpp" #include "argon/util/string.hpp" diff --git a/include/argon/exceptions.hpp b/include/argon/exceptions.hpp index 77c34140..f3f1db03 100644 --- a/include/argon/exceptions.hpp +++ b/include/argon/exceptions.hpp @@ -6,7 +6,7 @@ #pragma once -#include "argon/detail/argument_name.hpp" +#include "argon/argument_name.hpp" #include "argon/util/string.hpp" #include "argon/util/typing.hpp" @@ -33,8 +33,7 @@ struct invalid_configuration : public argument_parser_exception { ); } - static invalid_configuration argument_name_used(const detail::argument_name& arg_name - ) noexcept { + static invalid_configuration argument_name_used(const argument_name& arg_name) noexcept { return invalid_configuration(std::format("Given name [{}] already used.", arg_name.str())); } }; @@ -48,7 +47,7 @@ struct parsing_failure : public argument_parser_exception { } static parsing_failure invalid_nvalues( - const detail::argument_name& arg_name, const std::weak_ordering ordering + const argument_name& arg_name, const std::weak_ordering ordering ) noexcept { if (std::is_lt(ordering)) return parsing_failure( @@ -71,7 +70,7 @@ struct type_error : public argument_parser_exception { explicit type_error(const std::string& message) : argument_parser_exception(message) {} static type_error invalid_value_type( - const detail::argument_name& arg_name, const std::type_info& value_type + const argument_name& arg_name, const std::type_info& value_type ) noexcept { return type_error(std::format( "Invalid value type specified for argument [{}] = {}.", @@ -81,7 +80,7 @@ struct type_error : public argument_parser_exception { } template - static type_error invalid_value_type(const detail::argument_name& arg_name) noexcept { + static type_error invalid_value_type(const argument_name& arg_name) noexcept { return type_error(std::format( "Invalid value type specified for argument [{}] = {}.", arg_name.str(), diff --git a/tests/include/argument_parser_test_fixture.hpp b/tests/include/argument_parser_test_fixture.hpp index 09a68e61..642313fa 100644 --- a/tests/include/argument_parser_test_fixture.hpp +++ b/tests/include/argument_parser_test_fixture.hpp @@ -5,9 +5,9 @@ #include #include +using argon::argument_name; using argon::optional_argument; using argon::positional_argument; -using argon::detail::argument_name; using argon::detail::argument_token; using argon::traits::c_argument_value_type; using argon::traits::c_forward_iterator_of; @@ -165,15 +165,6 @@ struct argument_parser_test_fixture { return arg_tokens; } - // argument_parser private member accessors - [[nodiscard]] const std::optional& get_program_description() const { - return this->sut._program_description; - } - - [[nodiscard]] const std::optional& get_program_version() const { - return this->sut._program_version; - } - // private function callers [[nodiscard]] arg_token_vec_t tokenize(int argc, char* argv[]) { return this->sut._tokenize(argv + 1, argv + argc, state); diff --git a/tests/include/argument_test_fixture.hpp b/tests/include/argument_test_fixture.hpp index 4ea3e860..eceb2252 100644 --- a/tests/include/argument_test_fixture.hpp +++ b/tests/include/argument_test_fixture.hpp @@ -4,8 +4,8 @@ #include using argon::argument; +using argon::argument_name; using argon::argument_type; -using argon::detail::argument_name; using argon::detail::help_builder; using argon::traits::argument_result_type; using argon::traits::c_argument_value_type; diff --git a/tests/include/utility.hpp b/tests/include/utility.hpp index 6c5077a4..3e2990a5 100644 --- a/tests/include/utility.hpp +++ b/tests/include/utility.hpp @@ -10,12 +10,12 @@ void discard(T&&) { } template -bool is_positional(const argon::detail::argument_base& arg) { +bool is_positional(const argon::argument_base& arg) { return dynamic_cast*>(&arg); } template -bool is_optional(const argon::detail::argument_base& arg) { +bool is_optional(const argon::argument_base& arg) { return dynamic_cast*>(&arg); } diff --git a/tests/source/test_argument_name.cpp b/tests/source/test_argument_name.cpp index 4d56d3bd..dc6d8460 100644 --- a/tests/source/test_argument_name.cpp +++ b/tests/source/test_argument_name.cpp @@ -1,10 +1,10 @@ #include "doctest.h" -#include +#include #include -using namespace argon::detail; +using argon::argument_name; TEST_SUITE_BEGIN("test_argument_name"); diff --git a/tests/source/test_argument_parser_cfg.cpp b/tests/source/test_argument_parser_cfg.cpp index 2a32318f..7c61ae84 100644 --- a/tests/source/test_argument_parser_cfg.cpp +++ b/tests/source/test_argument_parser_cfg.cpp @@ -47,15 +47,17 @@ TEST_CASE_FIXTURE( CHECK_EQ(subparser.program_name(), std::format("{} {}", sut.name(), subparser_name)); } +// --- program_version --- + TEST_CASE_FIXTURE( - test_argument_parser_cfg, "parser's program version member should be nullopt by default" + test_argument_parser_cfg, "program_version() getter should return nullopt by default" ) { - CHECK_FALSE(get_program_version()); + CHECK_FALSE(sut.program_version()); } TEST_CASE_FIXTURE( test_argument_parser_cfg, - "program_version() should throw if the version string contains whitespaces" + "program_version() setter should throw if the version string contains whitespaces" ) { CHECK_THROWS_WITH_AS( sut.program_version("invalid version"), @@ -64,38 +66,82 @@ TEST_CASE_FIXTURE( ); } -TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() should set the program version member") { +TEST_CASE_FIXTURE( + test_argument_parser_cfg, "program_version() setter should update the version member" +) { sut.program_version(test_version); - auto stored_program_version = get_program_version(); + auto stored_program_version = sut.program_version(); REQUIRE(stored_program_version); CHECK_EQ(stored_program_version.value(), test_version.str()); sut.program_version(test_str_version); - stored_program_version = get_program_version(); + stored_program_version = sut.program_version(); REQUIRE(stored_program_version); CHECK_EQ(stored_program_version.value(), test_str_version); } +// --- program_description --- + TEST_CASE_FIXTURE( - test_argument_parser_cfg, "parser's program description member should be nullopt by default" + test_argument_parser_cfg, "program_description() getter should return nullopt by default" ) { - const auto stored_program_description = get_program_description(); - CHECK_FALSE(stored_program_description); + CHECK_FALSE(sut.program_description()); } TEST_CASE_FIXTURE( - test_argument_parser_cfg, "program_description() should set the program description member" + test_argument_parser_cfg, "program_description() setter should update the description member" ) { sut.program_description(test_description); - const auto stored_program_description = get_program_description(); + const auto stored_program_description = sut.program_description(); REQUIRE(stored_program_description); CHECK_EQ(stored_program_description.value(), test_description); } +// --- is_verbose --- + +TEST_CASE_FIXTURE(test_argument_parser_cfg, "is_verbose() should return false by default") { + CHECK_FALSE(sut.is_verbose()); +} + +TEST_CASE_FIXTURE(test_argument_parser_cfg, "verbose() setter should update the verbosity mode") { + sut.verbose(); + CHECK(sut.is_verbose()); + + sut.verbose(false); + CHECK_FALSE(sut.is_verbose()); +} + +// --- unknown_arguments_policy --- + +TEST_CASE_FIXTURE( + test_argument_parser_cfg, "unknown_arguments_policy() getter should return fail by default" +) { + CHECK_EQ(sut.unknown_arguments_policy(), argon::unknown_policy::fail); +} + TEST_CASE_FIXTURE( - test_argument_parser_cfg, "flag_char() should throw if the parser already has arguments" + test_argument_parser_cfg, "unknown_arguments_policy() setter should update the policy member" +) { + sut.unknown_arguments_policy(argon::unknown_policy::warn); + CHECK_EQ(sut.unknown_arguments_policy(), argon::unknown_policy::warn); + + sut.unknown_arguments_policy(argon::unknown_policy::ignore); + CHECK_EQ(sut.unknown_arguments_policy(), argon::unknown_policy::ignore); + + sut.unknown_arguments_policy(argon::unknown_policy::as_values); + CHECK_EQ(sut.unknown_arguments_policy(), argon::unknown_policy::as_values); +} + +// --- flag_char --- + +TEST_CASE_FIXTURE(test_argument_parser_cfg, "flag_char() getter should return '-' by default") { + CHECK_EQ(sut.flag_char(), '-'); +} + +TEST_CASE_FIXTURE( + test_argument_parser_cfg, "flag_char() setter should throw if the parser already has arguments" ) { sut.add_positional_argument("arg1"); sut.add_optional_argument("arg2"); @@ -109,7 +155,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_argument_parser_cfg, - "flag_char() should throw if the given character is not a printable ASCII character" + "flag_char() setter should throw if the given character is not a printable ASCII character" ) { CHECK_THROWS_WITH_AS( sut.flag_char('\n'), @@ -120,7 +166,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_argument_parser_cfg, - "flag_char() should throw if the given character is the same as the assignment character" + "flag_char() setter should throw if the given character is the same as the assignment character" ) { CHECK_THROWS_WITH_AS( sut.flag_char('='), @@ -129,4 +175,17 @@ TEST_CASE_FIXTURE( ); } +TEST_CASE_FIXTURE( + test_argument_parser_cfg, "flag_char() setter should update the flag character member" +) { + sut.flag_char('/'); + CHECK_EQ(sut.flag_char(), '/'); +} + +// --- assign_char --- + +TEST_CASE_FIXTURE(test_argument_parser_cfg, "assign_char() getter should return '='") { + CHECK_EQ(argon::argument_parser::assign_char(), '='); +} + TEST_SUITE_END(); // test_argument_parser_cfg diff --git a/tests/source/test_argument_token.cpp b/tests/source/test_argument_token.cpp index 9b6c897e..8fda55ab 100644 --- a/tests/source/test_argument_token.cpp +++ b/tests/source/test_argument_token.cpp @@ -5,9 +5,9 @@ using sut_type = argon::detail::argument_token; using enum sut_type::token_type; +using argon::argument_base; +using argon::argument_name; using argon::optional_argument; -using argon::detail::argument_base; -using argon::detail::argument_name; TEST_SUITE_BEGIN("test_argument_token"); diff --git a/tests/source/test_none_type_argument.cpp b/tests/source/test_none_type_argument.cpp index 1a4f2b89..b0fc2b33 100644 --- a/tests/source/test_none_type_argument.cpp +++ b/tests/source/test_none_type_argument.cpp @@ -5,9 +5,9 @@ using namespace argon_testing; TEST_SUITE_BEGIN("test_none_type_argument"); +using argon::argument_name; using argon::optional_argument; using argon::parsing_failure; -using argon::detail::argument_name; namespace { diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index b8cb3c93..cbf2fcb1 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -7,9 +7,9 @@ using namespace argon_testing; using namespace argon::nargs; +using argon::argument_name; using argon::optional_argument; using argon::parsing_failure; -using argon::detail::argument_name; using argon::detail::parameter_descriptor; TEST_SUITE_BEGIN("test_optional_argument"); diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index a6e7afef..630eff69 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -5,9 +5,9 @@ using namespace argon_testing; using namespace argon::nargs; +using argon::argument_name; using argon::parsing_failure; using argon::positional_argument; -using argon::detail::argument_name; using argon::detail::parameter_descriptor; TEST_SUITE_BEGIN("test_positional_argument"); From 3065750051256fb0a465ef44a57395a69cee410c Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Thu, 24 Sep 2026 10:54:51 +0200 Subject: [PATCH 2/4] aligned argument_base method visibility --- include/argon/argument.hpp | 20 ++++++------ include/argon/argument_base.hpp | 34 +++++++++++--------- include/argon/argument_name.hpp | 1 + tests/include/argument_test_fixture.hpp | 35 --------------------- tests/source/test_optional_argument.cpp | 38 +++++++++++------------ tests/source/test_positional_argument.cpp | 22 ++++++------- 6 files changed, 60 insertions(+), 90 deletions(-) diff --git a/include/argon/argument.hpp b/include/argon/argument.hpp index 4b7dd427..a7308169 100644 --- a/include/argon/argument.hpp +++ b/include/argon/argument.hpp @@ -442,6 +442,16 @@ class argument : public typed_argument_base { return this->has_parsed_values() or this->_has_predefined_values_impl(); } + /// @return `true` if parsed values are available for the argument, `false` otherwise. + [[nodiscard]] bool has_parsed_values() const noexcept override { + return not this->_values.empty(); + } + + /// @return `true` if the argument has predefined values, `false` otherwise. + [[nodiscard]] bool has_predefined_values() const noexcept override { + return this->_has_predefined_values_impl(); + } + /** * @return Reference to the stored value of the argument. * @note If multiple values are available, the first one is returned. @@ -571,16 +581,6 @@ class argument : public typed_argument_base { return this->_set_value_impl(str_value); } - /// @return `true` if parsed values are available for the argument, `false` otherwise. - [[nodiscard]] bool has_parsed_values() const noexcept override { - return not this->_values.empty(); - } - - /// @return `true` if the argument has predefined values, `false` otherwise. - [[nodiscard]] bool has_predefined_values() const noexcept override { - return this->_has_predefined_values_impl(); - } - /// @return The ordering relationship of the argument's values and its nargs range attribute. [[nodiscard]] std::weak_ordering nvalues_ordering() const noexcept override { if (this->_values.empty() and this->_has_predefined_values_impl()) diff --git a/include/argon/argument_base.hpp b/include/argon/argument_base.hpp index bfbfd843..f48821fe 100644 --- a/include/argon/argument_base.hpp +++ b/include/argon/argument_base.hpp @@ -26,6 +26,8 @@ class argument_base { public: virtual ~argument_base() = default; + // --- attribute getters --- + /// @return `true` if the argument is a positional argument instance, `false` otherwise. virtual bool is_positional() const noexcept = 0; @@ -53,16 +55,7 @@ class argument_base { /// @return `true` if the argument is greedy, `false` otherwise. virtual bool is_greedy() const noexcept = 0; - friend class ::argon::argument_parser; - -protected: - /// @param verbose The verbosity mode value. If `true` all non-default parameters will be included in the output. - /// @return A help message builder object for the argument. - virtual detail::help_builder help_builder(const bool verbose) const noexcept = 0; - - /// @brief Mark the argument as used. - /// @return `true` if the argument accepts further values, `false` otherwise. - virtual bool mark_used() = 0; + // --- state getters --- /// @return `true` if the argument has been used, `false` otherwise. virtual bool is_used() const noexcept = 0; @@ -70,11 +63,6 @@ class argument_base { /// @return The number of times an argument has been used. virtual std::size_t count() const noexcept = 0; - /// @brief Set the value for the argument. - /// @param value The string representation of the value. - /// @return `true` if the argument accepts further values, `false` otherwise. - virtual bool set_value(const std::string& value) = 0; - /// @return `true` if the argument has a value, `false` otherwise. virtual bool has_value() const noexcept = 0; @@ -84,6 +72,22 @@ class argument_base { /// @return `true` if the argument has predefined values, `false` otherwise. virtual bool has_predefined_values() const noexcept = 0; + friend class ::argon::argument_parser; + +protected: + /// @param verbose The verbosity mode value. If `true` all non-default parameters will be included in the output. + /// @return A help message builder object for the argument. + virtual detail::help_builder help_builder(const bool verbose) const noexcept = 0; + + /// @brief Mark the argument as used. + /// @return `true` if the argument accepts further values, `false` otherwise. + virtual bool mark_used() = 0; + + /// @brief Set the value for the argument. + /// @param value The string representation of the value. + /// @return `true` if the argument accepts further values, `false` otherwise. + virtual bool set_value(const std::string& value) = 0; + /// @return The ordering relationship of argument range. virtual std::weak_ordering nvalues_ordering() const noexcept = 0; }; diff --git a/include/argon/argument_name.hpp b/include/argon/argument_name.hpp index 525bb9bc..6b03024d 100644 --- a/include/argon/argument_name.hpp +++ b/include/argon/argument_name.hpp @@ -15,6 +15,7 @@ namespace argon { +// TODO: make a class with private members /// @brief Structure holding the argument's name. struct argument_name { /// @brief Specifies the type of argument name match. diff --git a/tests/include/argument_test_fixture.hpp b/tests/include/argument_test_fixture.hpp index eceb2252..b2f4efa6 100644 --- a/tests/include/argument_test_fixture.hpp +++ b/tests/include/argument_test_fixture.hpp @@ -42,52 +42,17 @@ struct argument_test_fixture { arg._values.clear(); } - template - [[nodiscard]] bool has_parsed_values(const argument& arg) const { - return arg.has_parsed_values(); - } - - template - [[nodiscard]] bool has_predefined_values(const argument& arg) const { - return arg.has_predefined_values(); - } - template [[nodiscard]] std::weak_ordering nvalues_ordering(const argument& arg) const { return arg.nvalues_ordering(); } - template - [[nodiscard]] const argument_name& get_name(const argument& arg) const { - return arg.name(); - } - - template - [[nodiscard]] const std::optional& get_help(const argument& arg) const { - return arg._help_msg; - } - template [[nodiscard]] help_builder get_help_builder(const argument& arg, const bool verbose) const { return arg.help_builder(verbose); } - template - [[nodiscard]] bool is_hidden(const argument& arg) const { - return arg.is_hidden(); - } - - template - [[nodiscard]] bool is_required(const argument& arg) const { - return arg.is_required(); - } - - template - [[nodiscard]] bool is_bypass_required_enabled(const argument& arg) const { - return arg.is_bypass_required_enabled(); - } - // exception message builders std::string invalid_value_msg(const argument_name& arg_name, const std::string& value) const { return std::format("Cannot parse value `{}` for argument [{}].", value, arg_name.str()); diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index cbf2fcb1..b284b142 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -47,7 +47,7 @@ const range non_default_range = range{1ull, choices.size()}; TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argument_name instance") { SUBCASE("initialized with the primary name only") { const auto sut = sut_type(arg_name_primary); - const auto name = get_name(sut); + const auto name = sut.name(); CHECK(name.match(primary_name)); CHECK_FALSE(name.match(secondary_name)); @@ -55,7 +55,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argume SUBCASE("initialized with the primary and secondary names") { const auto sut = sut_type(arg_name); - const auto name = get_name(sut); + const auto name = sut.name(); CHECK(name.match(primary_name)); CHECK(name.match(secondary_name)); @@ -64,14 +64,14 @@ TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argume TEST_CASE_FIXTURE(argument_test_fixture, "help() should return nullopt by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(get_help(sut)); + CHECK_FALSE(sut.help()); } TEST_CASE_FIXTURE(argument_test_fixture, "help() should return a massage set for the argument") { auto sut = sut_type(arg_name_primary); sut.help(help_msg); - const auto stored_help_msg = get_help(sut); + const auto stored_help_msg = sut.help(); REQUIRE(stored_help_msg); CHECK_EQ(stored_help_msg, help_msg); @@ -86,14 +86,14 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); auto bld = get_help_builder(sut, verbose); - REQUIRE_EQ(bld.name, get_name(sut).str()); + REQUIRE_EQ(bld.name, sut.name().str()); CHECK_FALSE(bld.help); CHECK(bld.params.empty()); // with a help msg sut.help(help_msg); bld = get_help_builder(sut, verbose); - REQUIRE_EQ(bld.name, get_name(sut).str()); + REQUIRE_EQ(bld.name, sut.name().str()); CHECK(bld.help); CHECK_EQ(bld.help.value(), help_msg); CHECK(bld.params.empty()); @@ -108,7 +108,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); auto bld = get_help_builder(sut, verbose); - REQUIRE_EQ(bld.name, get_name(sut).str()); + REQUIRE_EQ(bld.name, sut.name().str()); CHECK_FALSE(bld.help); CHECK(bld.params.empty()); @@ -174,10 +174,10 @@ TEST_CASE_FIXTURE( "is_hidden() should return false by default or the value passed in the attribute setter" ) { auto sut = sut_type(arg_name_primary); - REQUIRE_FALSE(is_hidden(sut)); + REQUIRE_FALSE(sut.is_hidden()); sut.hidden(); - CHECK(is_hidden(sut)); + CHECK(sut.is_hidden()); } TEST_CASE_FIXTURE(argument_test_fixture, "is_required() should return false by default") { @@ -365,7 +365,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(has_parsed_values(sut)); + CHECK_FALSE(sut.has_parsed_values()); } TEST_CASE_FIXTURE( @@ -377,30 +377,30 @@ TEST_CASE_FIXTURE( SUBCASE("default_values") { sut.default_values(default_value); - CHECK_FALSE(has_parsed_values(sut)); + CHECK_FALSE(sut.has_parsed_values()); } SUBCASE("implicit_values") { sut.implicit_values(implicit_value); - CHECK_FALSE(has_parsed_values(sut)); + CHECK_FALSE(sut.has_parsed_values()); } SUBCASE("default_values and implicit_values") { sut.default_values(default_value); sut.implicit_values(implicit_value); - CHECK_FALSE(has_parsed_values(sut)); + CHECK_FALSE(sut.has_parsed_values()); } } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should true if the value is set") { auto sut = sut_type(arg_name_primary); set_value(sut, arbitrary_value); - CHECK(has_parsed_values(sut)); + CHECK(sut.has_parsed_values()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_predefined_values() should return false by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(has_predefined_values(sut)); + CHECK_FALSE(sut.has_predefined_values()); } TEST_CASE_FIXTURE( @@ -409,7 +409,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.default_values(default_value); - CHECK(has_predefined_values(sut)); + CHECK(sut.has_predefined_values()); } TEST_CASE_FIXTURE( @@ -420,7 +420,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.implicit_values(implicit_value); - CHECK_FALSE(has_predefined_values(sut)); + CHECK_FALSE(sut.has_predefined_values()); } TEST_CASE_FIXTURE( @@ -433,7 +433,7 @@ TEST_CASE_FIXTURE( mark_used(sut); - CHECK(has_predefined_values(sut)); + CHECK(sut.has_predefined_values()); } TEST_CASE_FIXTURE( @@ -444,7 +444,7 @@ TEST_CASE_FIXTURE( sut.default_values(default_value); sut.implicit_values(implicit_value); - CHECK(has_predefined_values(sut)); + CHECK(sut.has_predefined_values()); } TEST_CASE_FIXTURE( diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index 630eff69..ccb3f7d3 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -37,19 +37,19 @@ const range non_default_range = range{1ull, choices.size()}; TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argument_name instance") { const auto sut = sut_type(arg_name); - CHECK_EQ(get_name(sut), arg_name); + CHECK_EQ(sut.name(), arg_name); } TEST_CASE_FIXTURE(argument_test_fixture, "help() should return nullopt by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(get_help(sut)); + CHECK_FALSE(sut.help()); } TEST_CASE_FIXTURE(argument_test_fixture, "help() should return a massage set for the argument") { auto sut = sut_type(arg_name); sut.help(help_msg); - const auto stored_help_msg = get_help(sut); + const auto stored_help_msg = sut.help(); REQUIRE(stored_help_msg); CHECK_EQ(stored_help_msg, help_msg); @@ -64,14 +64,14 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); auto bld = get_help_builder(sut, verbose); - REQUIRE_EQ(bld.name, get_name(sut).str()); + REQUIRE_EQ(bld.name, sut.name().str()); CHECK_FALSE(bld.help); CHECK(bld.params.empty()); // with a help msg sut.help(help_msg); bld = get_help_builder(sut, verbose); - REQUIRE_EQ(bld.name, get_name(sut).str()); + REQUIRE_EQ(bld.name, sut.name().str()); CHECK(bld.help); CHECK_EQ(bld.help.value(), help_msg); CHECK(bld.params.empty()); @@ -86,7 +86,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); auto bld = get_help_builder(sut, verbose); - REQUIRE_EQ(bld.name, get_name(sut).str()); + REQUIRE_EQ(bld.name, sut.name().str()); CHECK_FALSE(bld.help); CHECK(bld.params.empty()); @@ -290,7 +290,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(has_parsed_values(sut)); + CHECK_FALSE(sut.has_parsed_values()); } TEST_CASE_FIXTURE( @@ -300,19 +300,19 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); sut.default_values(default_value); - CHECK_FALSE(has_parsed_values(sut)); + CHECK_FALSE(sut.has_parsed_values()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should true if the value is set") { auto sut = sut_type(arg_name); set_value(sut, valid_value); - CHECK(has_parsed_values(sut)); + CHECK(sut.has_parsed_values()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_predefined_values() should return false by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(has_predefined_values(sut)); + CHECK_FALSE(sut.has_predefined_values()); } TEST_CASE_FIXTURE( @@ -321,7 +321,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); sut.default_values(default_value); - CHECK(has_predefined_values(sut)); + CHECK(sut.has_predefined_values()); } TEST_CASE_FIXTURE( From fd27dfb6c41af740bc54e231022da1ad5a7c7b49 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Thu, 24 Sep 2026 11:00:46 +0200 Subject: [PATCH 3/4] reworked the argument_name type --- include/argon/argument_name.hpp | 64 ++++++++------ tests/source/test_argument_name.cpp | 12 +-- .../source/test_argument_parser_args_cfg.cpp | 85 +++++++++++++++++++ .../test_argument_parser_parse_args.cpp | 64 +++++++------- 4 files changed, 163 insertions(+), 62 deletions(-) diff --git a/include/argon/argument_name.hpp b/include/argon/argument_name.hpp index 6b03024d..489ea754 100644 --- a/include/argon/argument_name.hpp +++ b/include/argon/argument_name.hpp @@ -15,9 +15,9 @@ namespace argon { -// TODO: make a class with private members -/// @brief Structure holding the argument's name. -struct argument_name { +/// @brief Class holding the argument's name. +class argument_name { +public: /// @brief Specifies the type of argument name match. enum class match_type : std::uint8_t { m_any, ///< Matches either the primary or the secondary name. @@ -28,12 +28,12 @@ struct argument_name { argument_name() = delete; - argument_name& operator=(const argument_name&) = delete; - argument_name& operator=(argument_name&&) = delete; - argument_name(const argument_name&) = default; argument_name(argument_name&&) = default; + argument_name& operator=(const argument_name&) = default; + argument_name& operator=(argument_name&&) = default; + /** * @brief Primary and secondary name constructor. * @param primary The primary name of the argument. @@ -43,8 +43,8 @@ struct argument_name { explicit argument_name( std::string primary, std::string secondary = "", char flag_char = flag_char_sentinel ) - : primary(std::move(primary)), secondary(std::move(secondary)), flag_char(flag_char) { - if (this->primary.empty() and this->secondary.empty()) + : _primary(std::move(primary)), _secondary(std::move(secondary)), _flag_char(flag_char) { + if (this->_primary.empty() and this->_secondary.empty()) throw std::logic_error("An argument name cannot be empty! At least one of " "primary/secondary must be specified"); } @@ -58,17 +58,32 @@ struct argument_name { * @return Equality of argument names. */ bool operator==(const argument_name& other) const noexcept { - return this->primary == other.primary and this->secondary == other.secondary; + return this->_primary == other._primary and this->_secondary == other._secondary; } /// @brief Checks if the argument name instance has a primary name. [[nodiscard]] bool has_primary() const noexcept { - return not this->primary.empty(); + return not this->_primary.empty(); } /// @brief Checks if the argument name instance has a secondary name. [[nodiscard]] bool has_secondary() const noexcept { - return not this->secondary.empty(); + return not this->_secondary.empty(); + } + + /// @return The primary name of the argument. + [[nodiscard]] const std::string& primary() const noexcept { + return this->_primary; + } + + /// @return The secondary (short) name of the argument. + [[nodiscard]] const std::string& secondary() const noexcept { + return this->_secondary; + } + + /// @return The flag character (used for optional argument names). + [[nodiscard]] char flag_char() const noexcept { + return this->_flag_char; } /** @@ -81,12 +96,12 @@ struct argument_name { const noexcept { switch (m_type) { case m_any: - return (this->has_primary() and this->primary == arg_name) - or (this->has_secondary() and this->secondary == arg_name); + return (this->has_primary() and this->_primary == arg_name) + or (this->has_secondary() and this->_secondary == arg_name); case m_primary: - return this->has_primary() and this->primary == arg_name; + return this->has_primary() and this->_primary == arg_name; case m_secondary: - return this->has_secondary() and this->secondary == arg_name; + return this->has_secondary() and this->_secondary == arg_name; } return false; @@ -98,11 +113,11 @@ struct argument_name { * @return True if arg_name's primary or secondary value matches the argument_name instance. */ [[nodiscard]] bool match(const argument_name& arg_name) const noexcept { - if (arg_name.has_primary() and this->match(arg_name.primary)) + if (arg_name.has_primary() and this->match(arg_name._primary)) return true; if (arg_name.has_secondary()) - return this->match(arg_name.secondary); + return this->match(arg_name._secondary); return false; } @@ -112,14 +127,14 @@ struct argument_name { */ [[nodiscard]] std::string str() const noexcept { const std::string fc( - static_cast(this->flag_char != flag_char_sentinel), this->flag_char + static_cast(this->_flag_char != flag_char_sentinel), this->_flag_char ); std::string primary_str = - this->has_primary() ? std::format("{}{}{}", fc, fc, this->primary) : ""; + this->has_primary() ? std::format("{}{}{}", fc, fc, this->_primary) : ""; std::string separator = this->has_primary() and this->has_secondary() ? ", " : ""; std::string secondary_str = - this->has_secondary() ? std::format("{}{}", fc, this->secondary) : ""; + this->has_secondary() ? std::format("{}{}", fc, this->_secondary) : ""; return std::format("{}{}{}", primary_str, separator, secondary_str); } @@ -135,12 +150,13 @@ struct argument_name { return os; } - const std::string primary; ///< The primary name of the argument. - const std::string secondary; ///< The optional (short) name of the argument. - const char flag_char; ///< The flag character (used for optional argument names). - // --- constants --- static constexpr char flag_char_sentinel = '\0'; ///< Sentinel value for the flag character. + +private: + std::string _primary; ///< The primary name of the argument. + std::string _secondary; ///< The optional (short) name of the argument. + char _flag_char; ///< The flag character (used for optional argument names). }; namespace detail { diff --git a/tests/source/test_argument_name.cpp b/tests/source/test_argument_name.cpp index dc6d8460..b4683ede 100644 --- a/tests/source/test_argument_name.cpp +++ b/tests/source/test_argument_name.cpp @@ -35,14 +35,14 @@ TEST_CASE("arugment_name construction should throw if both primary and secondary } TEST_CASE("argument_name members should be initialzed properly") { - CHECK_EQ(arg_name_primary_1.primary, primary_1); - CHECK(arg_name_primary_1.secondary.empty()); + CHECK_EQ(arg_name_primary_1.primary(), primary_1); + CHECK(arg_name_primary_1.secondary().empty()); - CHECK(arg_name_secondary_1.primary.empty()); - CHECK_EQ(arg_name_secondary_1.secondary, secondary_1); + CHECK(arg_name_secondary_1.primary().empty()); + CHECK_EQ(arg_name_secondary_1.secondary(), secondary_1); - CHECK_EQ(arg_name_full_1.primary, primary_1); - CHECK_EQ(arg_name_full_1.secondary, secondary_1); + CHECK_EQ(arg_name_full_1.primary(), primary_1); + CHECK_EQ(arg_name_full_1.secondary(), secondary_1); } TEST_CASE("operator==(argument_name) should return true only if both primary and secondary names " diff --git a/tests/source/test_argument_parser_args_cfg.cpp b/tests/source/test_argument_parser_args_cfg.cpp index 2d4e2af7..10c82c98 100644 --- a/tests/source/test_argument_parser_args_cfg.cpp +++ b/tests/source/test_argument_parser_args_cfg.cpp @@ -417,4 +417,89 @@ TEST_CASE_FIXTURE( ); } +// argument getters + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "argument() getter should throw if there is no argument with the given name" +) { + CHECK_THROWS_AS(discard(sut.argument("nonexistent")), lookup_failure); +} + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "argument() getter should return the correct argument base reference" +) { + sut.add_positional_argument("pos_arg"); + sut.add_optional_argument("opt_arg", "o"); + + CHECK_NOTHROW(discard(sut.argument("pos_arg"))); + CHECK_EQ(sut.argument("pos_arg").name().primary(), "pos_arg"); + CHECK(sut.argument("pos_arg").is_positional()); + + CHECK_NOTHROW(discard(sut.argument("opt_arg"))); + CHECK_EQ(sut.argument("opt_arg").name().primary(), "opt_arg"); + CHECK(sut.argument("opt_arg").is_optional()); + + // Should also be retrievable by the secondary name + CHECK_NOTHROW(discard(sut.argument("o"))); + CHECK_EQ(sut.argument("o").name().secondary(), "o"); +} + +// group getters + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "group() getter should throw if there is no group with the given name" +) { + CHECK_THROWS_AS(discard(sut.group("Nonexistent Group")), lookup_failure); + + const auto& const_sut = sut; + CHECK_THROWS_AS(discard(const_sut.group("Nonexistent Group")), lookup_failure); +} + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "group() getter should return the correct argument group reference" +) { + const std::string group_name = "My Group"; + sut.add_group(group_name); + + // Non-const getter + CHECK_NOTHROW(discard(sut.group(group_name))); + + // Const getter + const auto& const_sut = sut; + CHECK_NOTHROW(discard(const_sut.group(group_name))); +} + +// subparser getters + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "subparser() getter should throw if there is no subparser with the given name" +) { + CHECK_THROWS_AS(discard(sut.subparser("nonexistent")), lookup_failure); + + const auto& const_sut = sut; + CHECK_THROWS_AS(discard(const_sut.subparser("nonexistent")), lookup_failure); +} + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "subparser() getter should return the correct subparser reference" +) { + const std::string sub_name = "my_sub"; + sut.add_subparser(sub_name); + + // Non-const getter + CHECK_NOTHROW(discard(sut.subparser(sub_name))); + CHECK_EQ(sut.subparser(sub_name).name(), sub_name); + + // Const getter + const auto& const_sut = sut; + CHECK_NOTHROW(discard(const_sut.subparser(sub_name))); + CHECK_EQ(const_sut.subparser(sub_name).name(), sub_name); +} + TEST_SUITE_END(); // test_argument_parser_args_cfg diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index 8c333178..d0ce2b51 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -229,7 +229,7 @@ TEST_CASE_FIXTURE( add_arguments(n_positional_args, n_optional_args); const auto required_arg_name = init_arg_name(n_args_total, flag_char); - sut.add_optional_argument(required_arg_name.primary, required_arg_name.secondary).required(); + sut.add_optional_argument(required_arg_name.primary(), required_arg_name.secondary()).required(); const auto argc = get_argc(n_positional_args, n_optional_args); auto argv = init_argv(n_positional_args, n_optional_args); @@ -253,7 +253,7 @@ TEST_CASE_FIXTURE( auto argv = init_argv(n_positional_args, n_optional_args); const auto range_arg_name = init_arg_name(n_args_total, flag_char); - sut.add_optional_argument(range_arg_name.primary, range_arg_name.secondary) + sut.add_optional_argument(range_arg_name.primary(), range_arg_name.secondary()) .nargs(at_least(1ull)); CHECK_THROWS_WITH_AS( @@ -305,10 +305,10 @@ TEST_CASE_FIXTURE( std::string invalid_flag; SUBCASE("primary name with a secondary flag prefix") { - invalid_flag = "-" + opt_arg_name.primary; + invalid_flag = "-" + opt_arg_name.primary(); } SUBCASE("secondary name with a primary flag prefix") { - invalid_flag = "--" + opt_arg_name.secondary; + invalid_flag = "--" + opt_arg_name.secondary(); } CAPTURE(invalid_flag); @@ -337,7 +337,7 @@ TEST_CASE_FIXTURE( add_arguments(n_positional_args, n_optional_args); const auto required_arg_name = init_arg_name(n_args_total); - sut.add_optional_argument(required_arg_name.primary, required_arg_name.secondary).required(); + sut.add_optional_argument(required_arg_name.primary(), required_arg_name.secondary()).required(); int argc; char** argv; @@ -374,12 +374,12 @@ TEST_CASE_FIXTURE( "option enabled and is used" ) { const std::size_t n_positional_args = 1ull; - const auto bypass_required_arg_name = init_arg_name(n_positional_args - 1ull).primary; + const auto bypass_required_arg_name = init_arg_name(n_positional_args - 1ull).primary(); sut.add_positional_argument(bypass_required_arg_name).required(false).suppress_arg_checks(); const std::string bypass_required_arg_value = "bypass_required_arg_value"; for (std::size_t i = 0ull; i < n_optional_args; ++i) - sut.add_optional_argument(init_arg_name(n_positional_args + i).primary).required(); + sut.add_optional_argument(init_arg_name(n_positional_args + i).primary()).required(); std::vector argv_vec{"program", bypass_required_arg_value}; const int argc = static_cast(argv_vec.size()); @@ -401,7 +401,7 @@ TEST_CASE_FIXTURE( const auto bypass_required_arg_name = init_arg_name(n_args_total); sut.add_optional_argument( - bypass_required_arg_name.primary, bypass_required_arg_name.secondary + bypass_required_arg_name.primary(), bypass_required_arg_name.secondary() ) .default_values(false) .implicit_values(true) @@ -423,7 +423,7 @@ TEST_CASE_FIXTURE( const auto argv = to_char_2d_array(argv_vec); REQUIRE_NOTHROW(sut.parse_args(argc, argv)); - CHECK(sut.value(bypass_required_arg_name.primary)); + CHECK(sut.value(bypass_required_arg_name.primary())); free_argv(argc, argv); } @@ -497,7 +497,7 @@ TEST_CASE_FIXTURE( add_arguments(n_positional_args, n_optional_args); const auto required_arg_name = init_arg_name(n_args_total); - sut.add_optional_argument(required_arg_name.primary, required_arg_name.secondary).required(); + sut.add_optional_argument(required_arg_name.primary(), required_arg_name.secondary()).required(); int argc; char** argv; @@ -711,9 +711,9 @@ TEST_CASE_FIXTURE( for (std::size_t i = 0ull; i < n_args_total; ++i) { const auto arg_name = init_arg_name(i); - REQUIRE(sut.has_value(arg_name.primary)); + REQUIRE(sut.has_value(arg_name.primary())); CHECK_THROWS_AS( - discard(sut.value(arg_name.primary)), argon::type_error + discard(sut.value(arg_name.primary())), argon::type_error ); } @@ -744,9 +744,9 @@ TEST_CASE_FIXTURE( const auto arg_name = init_arg_name(i); const auto arg_value = init_arg_value(i); - REQUIRE(sut.has_value(arg_name.primary)); - CHECK_EQ(sut.value(arg_name.primary), arg_value); - CHECK_EQ(sut.value(arg_name.secondary), arg_value); + REQUIRE(sut.has_value(arg_name.primary())); + CHECK_EQ(sut.value(arg_name.primary()), arg_value); + CHECK_EQ(sut.value(arg_name.secondary()), arg_value); } free_argv(argc, argv); @@ -759,7 +759,7 @@ TEST_CASE_FIXTURE( ) { for (std::size_t i = 0ull; i < n_optional_args; ++i) { const auto arg_name = init_arg_name(i); - sut.add_optional_argument(arg_name.primary, arg_name.secondary) + sut.add_optional_argument(arg_name.primary(), arg_name.secondary()) .default_values(init_arg_value(i)); } @@ -767,9 +767,9 @@ TEST_CASE_FIXTURE( const auto arg_name = init_arg_name(i); const auto arg_value = init_arg_value(i); - REQUIRE(sut.has_value(arg_name.primary)); - CHECK_EQ(sut.value(arg_name.primary), arg_value); - CHECK_EQ(sut.value(arg_name.secondary), arg_value); + REQUIRE(sut.has_value(arg_name.primary())); + CHECK_EQ(sut.value(arg_name.primary()), arg_value); + CHECK_EQ(sut.value(arg_name.secondary()), arg_value); } } @@ -799,9 +799,9 @@ TEST_CASE_FIXTURE( for (std::size_t i = 0ull; i < n_args_total; ++i) { const auto arg_name = init_arg_name(i); - REQUIRE(sut.has_value(arg_name.primary)); + REQUIRE(sut.has_value(arg_name.primary())); CHECK_THROWS_AS( - discard(sut.value_or(arg_name.primary, invalid_value_type{})), + discard(sut.value_or(arg_name.primary(), invalid_value_type{})), argon::type_error ); } @@ -833,9 +833,9 @@ TEST_CASE_FIXTURE( const auto arg_name = init_arg_name(i); const auto arg_value = init_arg_value(i); - REQUIRE(sut.has_value(arg_name.primary)); - CHECK_EQ(sut.value_or(arg_name.primary, empty_str), arg_value); - CHECK_EQ(sut.value_or(arg_name.secondary, empty_str), arg_value); + REQUIRE(sut.has_value(arg_name.primary())); + CHECK_EQ(sut.value_or(arg_name.primary(), empty_str), arg_value); + CHECK_EQ(sut.value_or(arg_name.secondary(), empty_str), arg_value); } free_argv(argc, argv); @@ -848,7 +848,7 @@ TEST_CASE_FIXTURE( ) { for (std::size_t i = 0ull; i < n_optional_args; ++i) { const auto arg_name = init_arg_name(i); - sut.add_optional_argument(arg_name.primary, arg_name.secondary) + sut.add_optional_argument(arg_name.primary(), arg_name.secondary()) .default_values(init_arg_value(i)); } @@ -856,9 +856,9 @@ TEST_CASE_FIXTURE( const auto arg_name = init_arg_name(i); const auto arg_value = init_arg_value(i); - REQUIRE(sut.has_value(arg_name.primary)); - CHECK_EQ(sut.value_or(arg_name.primary, empty_str), arg_value); - CHECK_EQ(sut.value_or(arg_name.secondary, empty_str), arg_value); + REQUIRE(sut.has_value(arg_name.primary())); + CHECK_EQ(sut.value_or(arg_name.primary(), empty_str), arg_value); + CHECK_EQ(sut.value_or(arg_name.secondary(), empty_str), arg_value); } } @@ -884,8 +884,8 @@ TEST_CASE_FIXTURE( const auto arg_name = init_arg_name(i); const default_value_type fallback_value = 2 * static_cast(i); - CHECK_EQ(sut.value_or(arg_name.primary, fallback_value), fallback_value); - CHECK_EQ(sut.value_or(arg_name.secondary, fallback_value), fallback_value); + CHECK_EQ(sut.value_or(arg_name.primary(), fallback_value), fallback_value); + CHECK_EQ(sut.value_or(arg_name.secondary(), fallback_value), fallback_value); } } @@ -905,8 +905,8 @@ TEST_CASE_FIXTURE( const auto arg_name = init_arg_name(i); const auto fallback_value = init_arg_value(i); - CHECK_EQ(sut.value_or(arg_name.primary, fallback_value), fallback_value); - CHECK_EQ(sut.value_or(arg_name.secondary, fallback_value), fallback_value); + CHECK_EQ(sut.value_or(arg_name.primary(), fallback_value), fallback_value); + CHECK_EQ(sut.value_or(arg_name.secondary(), fallback_value), fallback_value); } } From e66d337d59fe149837450d03abb8013eb7728c00 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Thu, 24 Sep 2026 11:49:00 +0200 Subject: [PATCH 4/4] resolved comments --- include/argon/argument_name.hpp | 6 +----- include/argon/argument_parser.hpp | 10 ++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/include/argon/argument_name.hpp b/include/argon/argument_name.hpp index 489ea754..0a814ef7 100644 --- a/include/argon/argument_name.hpp +++ b/include/argon/argument_name.hpp @@ -159,8 +159,6 @@ class argument_name { char _flag_char; ///< The flag character (used for optional argument names). }; -namespace detail { - /** * @brief Argument name member discriminator. * @@ -171,8 +169,6 @@ enum class argument_name_discriminator : bool { n_secondary ///< Represents the secondary name (used with a short flag prefix --). }; -} // namespace detail - -using enum detail::argument_name_discriminator; +using enum argument_name_discriminator; } // namespace argon diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 4cf54869..27e773d4 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -397,8 +397,7 @@ class argument_parser { */ template optional_argument& add_optional_argument( - const std::string_view base_name, - const detail::argument_name_discriminator name_discr = n_primary + const std::string_view base_name, const argument_name_discriminator name_discr = n_primary ) { return this->add_optional_argument(this->_gr_optional_args, base_name, name_discr); } @@ -433,7 +432,7 @@ class argument_parser { optional_argument& add_optional_argument( argument_group& group, const std::string_view base_name, - const detail::argument_name_discriminator name_discr = n_primary + const argument_name_discriminator name_discr = n_primary ) { this->_validate_group(group); @@ -499,8 +498,7 @@ class argument_parser { */ template optional_argument& add_flag( - const std::string_view base_name, - const detail::argument_name_discriminator name_discr = n_primary + const std::string_view base_name, const argument_name_discriminator name_discr = n_primary ) { return this->add_optional_argument(base_name, name_discr) .default_values(not StoreImplicitly) @@ -539,7 +537,7 @@ class argument_parser { optional_argument& add_flag( argument_group& group, const std::string_view base_name, - const detail::argument_name_discriminator name_discr = n_primary + const argument_name_discriminator name_discr = n_primary ) { return this->add_optional_argument(group, base_name, name_discr) .default_values(not StoreImplicitly)