From b07ad23f9a96c32c8b4414bdec1d8f0ebf3edfe5 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 18 Sep 2026 17:37:14 +0200 Subject: [PATCH 1/6] small stuff --- include/argon/argument_group.hpp | 6 +++++- include/argon/argument_parser.hpp | 21 ++++++++++--------- .../test_argument_parser_add_elements.cpp | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index fe1d1b09..0b43393b 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -86,6 +86,9 @@ class argument_group { return *this; } + // argument_group& prefix(std::string_view prefix) noexcept { + // } + friend class argument_parser; private: @@ -112,7 +115,8 @@ class argument_group { argument_parser* _parser; ///< Pointer to the owning parser. std::string _name; ///< Name of the group (used in help output). - arg_ptr_vec_t _arguments; ///< A list of arguments that belong to this group. + std::string _prefix = ""; ///< Common argument flag prefix. + arg_ptr_vec_t _arguments = {}; ///< A list of arguments that belong to this group. bool _hidden : 1 = false; ///< The hidden attribute value (default: false). bool _required : 1 = false; ///< The required attribute value (default: false). diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index c1548d4c..0dfb2d64 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -517,7 +517,7 @@ class argument_parser { ); if (subparser_it != this->_subparsers.end()) throw std::logic_error(std::format( - "A subparser with the given name () already exists in parser '{}'", + "A subparser with the given name ({}) already exists in parser '{}'", (*subparser_it)->_name, this->_program_name )); @@ -1550,20 +1550,21 @@ class argument_parser { } } - std::string _name; ///< The name of the parser. - std::string - _program_name; ///< The name of the program in the format "... ". - std::optional _program_version; ///< The version of the program. - std::optional _program_description; ///< The description of the program. + std::string _name = ""; ///< The name of the parser. + std::string _program_name = + ""; ///< The name of the program in the format "... ". + std::optional _program_version = std::nullopt; ///< The version of the program. + std::optional _program_description = + std::nullopt; ///< The description of the program. bool _verbose = false; ///< Verbosity flag. unknown_policy _unknown_policy = unknown_policy::fail; ///< Policy for unknown arguments. - arg_ptr_vec_t _positional_args; ///< The list of positional arguments. - arg_ptr_vec_t _optional_args; ///< The list of optional arguments. - arg_group_ptr_vec_t _argument_groups; ///< The list of argument groups. + arg_ptr_vec_t _positional_args = {}; ///< The list of positional arguments. + arg_ptr_vec_t _optional_args = {}; ///< The list of optional arguments. + arg_group_ptr_vec_t _argument_groups = {}; ///< The list of argument groups. argument_group& _gr_positional_args; ///< The positional argument group. argument_group& _gr_optional_args; ///< The optional argument group. - arg_parser_ptr_vec_t _subparsers; ///< The list of subparsers. + arg_parser_ptr_vec_t _subparsers = {}; ///< The list of subparsers. bool _invoked = false; ///< A flag indicating whether the parser has been invoked to parse arguments. diff --git a/tests/source/test_argument_parser_add_elements.cpp b/tests/source/test_argument_parser_add_elements.cpp index b148e651..7edd9a34 100644 --- a/tests/source/test_argument_parser_add_elements.cpp +++ b/tests/source/test_argument_parser_add_elements.cpp @@ -386,7 +386,7 @@ TEST_CASE_FIXTURE( CHECK_THROWS_WITH_AS( sut.add_subparser(subparser_name), std::format( - "A subparser with the given name () already exists in parser '{}'", + "A subparser with the given name ({}) already exists in parser '{}'", subparser_name, sut.name() ) From e915184d56d9bbaec076e3f282eb0f7f0379d531 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 18 Sep 2026 18:13:07 +0200 Subject: [PATCH 2/6] initial group prefix impl --- include/argon/argument_group.hpp | 6 ++- include/argon/argument_parser.hpp | 30 +++++++++----- ....cpp => test_argument_parser_args_cfg.cpp} | 40 +++++++++++-------- .../test_argument_parser_parse_args.cpp | 6 +++ 4 files changed, 54 insertions(+), 28 deletions(-) rename tests/source/{test_argument_parser_add_elements.cpp => test_argument_parser_args_cfg.cpp} (92%) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index 0b43393b..41df62a8 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -86,8 +86,10 @@ class argument_group { return *this; } - // argument_group& prefix(std::string_view prefix) noexcept { - // } + argument_group& with_prefix(std::string_view prefix) noexcept { + this->_prefix = prefix; + return *this; + } friend class argument_parser; diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 0dfb2d64..6614e523 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -296,7 +296,7 @@ class argument_parser { /** * @brief Adds a positional argument to the parser's configuration and binds it to the given group. * @tparam T Type of the argument value. - * @param primary_name The name of the argument. + * @param name The name of the argument. * @return Reference to the added positional argument. * @throws argon::invalid_configuration */ @@ -305,9 +305,11 @@ class argument_parser { argument_group& group, const std::string_view name ) { this->_validate_group(group); - this->_verify_arg_name_pattern(name); - const detail::argument_name arg_name(std::make_optional(name)); + const auto full_name = std::format("{}{}", group._prefix, name); + this->_verify_arg_name_pattern(full_name); + + const detail::argument_name arg_name(std::make_optional(full_name)); if (this->_is_arg_name_used(arg_name)) throw invalid_configuration::argument_name_used(arg_name); @@ -366,14 +368,18 @@ class argument_parser { const detail::argument_name_discriminator name_discr = n_primary ) { this->_validate_group(group); - this->_verify_arg_name_pattern(name); + + const auto full_name = std::format("{}{}", group._prefix, name); + this->_verify_arg_name_pattern(full_name); const auto arg_name = name_discr == n_primary ? detail:: - argument_name{std::make_optional(name), std::nullopt, this->_flag_prefix_char} + argument_name{std::make_optional(full_name), std::nullopt, this->_flag_prefix_char} : detail::argument_name{ - std::nullopt, std::make_optional(name), this->_flag_prefix_char + std::nullopt, + std::make_optional(full_name), + this->_flag_prefix_char }; if (this->_is_arg_name_used(arg_name)) @@ -401,12 +407,16 @@ class argument_parser { const std::string_view secondary_name ) { this->_validate_group(group); - this->_verify_arg_name_pattern(primary_name); - this->_verify_arg_name_pattern(secondary_name); + + const auto full_primary_name = std::format("{}{}", group._prefix, primary_name); + this->_verify_arg_name_pattern(full_primary_name); + + const auto full_secondary_name = std::format("{}{}", group._prefix, secondary_name); + this->_verify_arg_name_pattern(full_secondary_name); const detail::argument_name arg_name( - std::make_optional(primary_name), - std::make_optional(secondary_name), + std::make_optional(full_primary_name), + std::make_optional(full_secondary_name), this->_flag_prefix_char ); if (this->_is_arg_name_used(arg_name)) diff --git a/tests/source/test_argument_parser_add_elements.cpp b/tests/source/test_argument_parser_args_cfg.cpp similarity index 92% rename from tests/source/test_argument_parser_add_elements.cpp rename to tests/source/test_argument_parser_args_cfg.cpp index 7edd9a34..e1207529 100644 --- a/tests/source/test_argument_parser_add_elements.cpp +++ b/tests/source/test_argument_parser_args_cfg.cpp @@ -9,9 +9,9 @@ using argon::argument_parser; using argon::default_argument; using argon::invalid_configuration; -TEST_SUITE_BEGIN("test_argument_parser_add_elements"); +TEST_SUITE_BEGIN("test_argument_parser_args_cfg"); -struct test_argument_parser_add_elements : public argument_parser_test_fixture { +struct test_argument_parser_args_cfg : public argument_parser_test_fixture { const char flag_char = '-'; const std::string_view primary_name_1 = "primary_name_1"; @@ -37,7 +37,7 @@ struct test_argument_parser_add_elements : public argument_parser_test_fixture { }; TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_{positional,optional}_argument(primary) should throw if the passed argument name is " "invalid" ) { @@ -80,7 +80,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_{positional,optional}_argument(primary, secondary) should throw if the primary name is " "invalid" ) { @@ -123,7 +123,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_optional_argument(primary, secondary) should throw if the secondary name is " "invalid" ) { @@ -160,7 +160,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_positional_argument should throw when adding an argument with a previously used name" ) { sut.add_positional_argument(primary_name_1); @@ -177,7 +177,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_optional_argument should throw when adding an argument with a previously used name" ) { sut.add_optional_argument(primary_name_1, secondary_name_1); @@ -210,7 +210,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_flag should return an optional argument reference with flag parameters" ) { const argument_test_fixture arg_fixture; @@ -237,7 +237,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "add_flag should throw when adding and argument with a previously used name" ) { sut.add_flag(primary_name_1, secondary_name_1); @@ -270,8 +270,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, - "default_arguments should add the specified positional arguments" + test_argument_parser_args_cfg, "default_arguments should add the specified positional arguments" ) { sut.default_arguments({default_argument::p_input, default_argument::p_output}); @@ -285,8 +284,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, - "default_arguments should add the specified optional arguments" + test_argument_parser_args_cfg, "default_arguments should add the specified optional arguments" ) { sut.default_arguments( default_argument::o_help, default_argument::o_input, default_argument::o_output @@ -327,7 +325,7 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, "argument adding functions should throw if a group does not belong to the parser" ) { argument_parser different_parser("different-program"); @@ -376,7 +374,17 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_add_elements, + test_argument_parser_args_cfg, + "argument adding functions should properly add the group's prefrix" +) { + // const std::string group_name = "An Argument Group"; + // auto& group = sut.add_group(group_name); + + // TODO +} + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, "add_subparser should throw if a subparser with the given name already exists" ) { constexpr std::string_view subparser_name = "subprogram"; @@ -395,4 +403,4 @@ TEST_CASE_FIXTURE( ); } -TEST_SUITE_END(); // test_argument_parser_add_elements +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 6f7fc5a1..6b26f9c6 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -1493,6 +1493,12 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.is_used(init_arg_name_primary(i))); } +TEST_CASE_FIXTURE( + test_argument_parser_parse_args, "parse_args should properly handle group prefixes" +) { + // TODO +} + // subparsers TEST_CASE_FIXTURE( From b21dd8e07db3c40ea5bfceaa9f16b02e853cff6d Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 19 Sep 2026 14:55:29 +0200 Subject: [PATCH 3/6] with_suffix impl --- include/argon/argument_group.hpp | 26 ++++++++++++++++++++++++++ include/argon/argument_parser.hpp | 8 ++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index 41df62a8..27eead9d 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -86,11 +86,32 @@ class argument_group { return *this; } + /** + * @brief Set the `prefix` attribute of the group. + * + * If set, each argument added to the group will have the given value prepended to its name. + * + * @param prefix The value to set for the attribute. + * @return Reference to the group instance. + */ argument_group& with_prefix(std::string_view prefix) noexcept { this->_prefix = prefix; return *this; } + /** + * @brief Set the `suffix` attribute of the group. + * + * If set, each argument added to the group will have the given value appended to its name. + * + * @param suffix The value to set for the attribute. + * @return Reference to the group instance. + */ + argument_group& with_suffix(std::string_view suffix) noexcept { + this->_suffix = suffix; + return *this; + } + friend class argument_parser; private: @@ -115,9 +136,14 @@ class argument_group { this->_arguments.emplace_back(std::move(arg)); } + [[nodiscard]] std::string _format_arg_name(std::string_view arg_name) const noexcept { + return std::format("{}{}{}", this->_prefix, arg_name, this->_suffix); + } + argument_parser* _parser; ///< Pointer to the owning parser. std::string _name; ///< Name of the group (used in help output). std::string _prefix = ""; ///< Common argument flag prefix. + std::string _suffix = ""; ///< Common argument flag suffix. arg_ptr_vec_t _arguments = {}; ///< A list of arguments that belong to this group. bool _hidden : 1 = false; ///< The hidden attribute value (default: false). diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 6614e523..9606c8dc 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -306,7 +306,7 @@ class argument_parser { ) { this->_validate_group(group); - const auto full_name = std::format("{}{}", group._prefix, name); + const auto full_name = group._format_arg_name(name); this->_verify_arg_name_pattern(full_name); const detail::argument_name arg_name(std::make_optional(full_name)); @@ -369,7 +369,7 @@ class argument_parser { ) { this->_validate_group(group); - const auto full_name = std::format("{}{}", group._prefix, name); + const auto full_name = group._format_arg_name(name); this->_verify_arg_name_pattern(full_name); const auto arg_name = @@ -408,10 +408,10 @@ class argument_parser { ) { this->_validate_group(group); - const auto full_primary_name = std::format("{}{}", group._prefix, primary_name); + const auto full_primary_name = group._format_arg_name(primary_name); this->_verify_arg_name_pattern(full_primary_name); - const auto full_secondary_name = std::format("{}{}", group._prefix, secondary_name); + const auto full_secondary_name = group._format_arg_name(secondary_name); this->_verify_arg_name_pattern(full_secondary_name); const detail::argument_name arg_name( From b20dee956f90926fb5937e53fca9a4a633ce2872 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 19 Sep 2026 15:44:32 +0200 Subject: [PATCH 4/6] _get_argument throws --- include/argon/argument_parser.hpp | 32 ++++------- tests/include/utility.hpp | 2 +- .../source/test_argument_parser_args_cfg.cpp | 32 +++++++++-- .../test_argument_parser_parse_args.cpp | 57 +++++++++---------- 4 files changed, 68 insertions(+), 55 deletions(-) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 9606c8dc..7aac46a9 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -752,9 +752,8 @@ class argument_parser { * @param arg_name The name of the argument. * @return `true` if the argument was used on the command line, `false` otherwise. */ - [[nodiscard]] bool is_used(std::string_view arg_name) const noexcept { - const auto arg = this->_get_argument(arg_name); - return arg ? arg->is_used() : false; + [[nodiscard]] bool is_used(std::string_view arg_name) const { + return this->_get_argument(arg_name)->is_used(); } /** @@ -762,9 +761,8 @@ class argument_parser { * @param arg_name The name of the argument. * @return `true` if the argument has a value, `false` otherwise. */ - [[nodiscard]] bool has_value(std::string_view arg_name) const noexcept { - const auto arg = this->_get_argument(arg_name); - return arg ? arg->has_value() : false; + [[nodiscard]] bool has_value(std::string_view arg_name) const { + return this->_get_argument(arg_name)->has_value(); } /** @@ -772,9 +770,8 @@ class argument_parser { * @param arg_name The name of the argument. * @return The number of times the argument has been used. */ - [[nodiscard]] std::size_t count(std::string_view arg_name) const noexcept { - const auto arg = this->_get_argument(arg_name); - return arg ? arg->count() : 0ull; + [[nodiscard]] std::size_t count(std::string_view arg_name) const { + return this->_get_argument(arg_name)->count(); } /** @@ -787,12 +784,9 @@ class argument_parser { template [[nodiscard]] T value(std::string_view arg_name) const { const auto arg = this->_get_argument(arg_name); - if (not arg) - throw lookup_failure::argument_not_found(arg_name); - const auto& arg_value = arg->value(); try { - return std::any_cast(arg_value); + return std::any_cast(arg->value()); } catch (const std::bad_any_cast&) { throw type_error::invalid_value_type(arg->name()); @@ -811,12 +805,9 @@ class argument_parser { template U> [[nodiscard]] T value_or(std::string_view arg_name, U&& fallback_value) const { const auto arg = this->_get_argument(arg_name); - if (not arg) - throw lookup_failure::argument_not_found(arg_name); try { - const auto& arg_value = arg->value(); - return std::any_cast(arg_value); + return std::any_cast(arg->value()); } catch (const std::logic_error&) { // positional: no value parsed @@ -839,8 +830,6 @@ class argument_parser { template [[nodiscard]] std::vector values(std::string_view arg_name) const { const auto arg = this->_get_argument(arg_name); - if (not arg) - throw lookup_failure::argument_not_found(arg_name); try { std::vector values; @@ -1469,8 +1458,9 @@ class argument_parser { * @brief Get the argument with the specified name. * @param arg_name The name of the argument. * @return The argument with the specified name, if found; otherwise, std::nullopt. + * @throws argon::lookup_failure if an argument with the given name cannot be found. */ - arg_ptr_t _get_argument(std::string_view arg_name) const noexcept { + arg_ptr_t _get_argument(std::string_view arg_name) const { const auto predicate = this->_name_match_predicate(arg_name); if (auto pos_arg_it = std::ranges::find_if(this->_positional_args, predicate); @@ -1483,7 +1473,7 @@ class argument_parser { return *opt_arg_it; } - return nullptr; + throw lookup_failure::argument_not_found(arg_name); } void _print_subparsers(std::ostream& os) const noexcept { diff --git a/tests/include/utility.hpp b/tests/include/utility.hpp index 916dea09..98b2cae3 100644 --- a/tests/include/utility.hpp +++ b/tests/include/utility.hpp @@ -5,7 +5,7 @@ namespace argon_testing { template -void discard_result(T&&) { +void discard(T&&) { // do nothing } diff --git a/tests/source/test_argument_parser_args_cfg.cpp b/tests/source/test_argument_parser_args_cfg.cpp index e1207529..1a118204 100644 --- a/tests/source/test_argument_parser_args_cfg.cpp +++ b/tests/source/test_argument_parser_args_cfg.cpp @@ -8,6 +8,7 @@ using argon::argument; using argon::argument_parser; using argon::default_argument; using argon::invalid_configuration; +using argon::lookup_failure; TEST_SUITE_BEGIN("test_argument_parser_args_cfg"); @@ -375,12 +376,35 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_argument_parser_args_cfg, - "argument adding functions should properly add the group's prefrix" + "argument adding functions should properly apply the argument group's name modifiers" ) { - // const std::string group_name = "An Argument Group"; - // auto& group = sut.add_group(group_name); + const auto group_name = "An Argument Group"; + const auto group_pre = "pre-"; + const auto group_suf = "-suf"; - // TODO + auto& group = sut.add_group(group_name).with_prefix(group_pre).with_suffix(group_suf); + + sut.add_positional_argument(group, "positional"); + sut.add_optional_argument(group, "optional"); + sut.add_flag(group, "flag"); + + const auto expected_err_msg = [](const auto& arg_name) { + return lookup_failure::argument_not_found(arg_name).what(); + }; + + CHECK_THROWS_WITH_AS( + discard(sut.value("positional")), expected_err_msg("positional"), lookup_failure + ); + CHECK_THROWS_WITH_AS( + discard(sut.value("optional")), expected_err_msg("optional"), lookup_failure + ); + CHECK_THROWS_WITH_AS( + discard(sut.value("flag")), expected_err_msg("flag"), lookup_failure + ); + + CHECK_FALSE(sut.has_value("pre-positional-suf")); + CHECK_FALSE(sut.has_value("pre-optional-suf")); + CHECK_FALSE(sut.value("pre-flag-suf")); } TEST_CASE_FIXTURE( diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index 6b26f9c6..3d8ee36c 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -5,6 +5,7 @@ using namespace argon_testing; using namespace argon::nargs; using argon::invalid_configuration; +using argon::lookup_failure; using argon::parsing_failure; using argon::unknown_policy; @@ -117,10 +118,10 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_argument_parser_parse_args, - "_get_argument should return nullopt if there is no argument with given name present" + "_get_argument should throw if there is no argument with given name present" ) { add_arguments(n_positional_args, n_optional_args); - CHECK_FALSE(get_argument(invalid_arg_name)); + CHECK_THROWS_AS(discard(get_argument(invalid_arg_name)), lookup_failure); } TEST_CASE_FIXTURE( @@ -577,7 +578,7 @@ TEST_CASE_FIXTURE( auto argv = init_argv(n_positional_args, n_optional_args); REQUIRE_NOTHROW(sut.parse_args(argc, argv)); - CHECK_FALSE(sut.has_value(invalid_arg_name)); + CHECK_THROWS_AS(discard(sut.has_value(invalid_arg_name)), lookup_failure); free_argv(argc, argv); } @@ -587,10 +588,12 @@ TEST_CASE_FIXTURE( ) { add_arguments(n_positional_args, n_optional_args); - for (std::size_t i = 0ull; i < n_args_total; ++i) { - const auto arg_name = init_arg_name(i); - CHECK_FALSE(sut.has_value(arg_name.primary.value())); - CHECK_FALSE(sut.has_value(arg_name.secondary.value())); + for (std::size_t i = 0ull; i < n_positional_args; ++i) + CHECK_FALSE(sut.has_value(init_arg_name_primary(i))); + + for (std::size_t i = first_opt_arg_idx; i < n_args_total; ++i) { + CHECK_FALSE(sut.has_value(init_arg_name_primary(i))); + CHECK_FALSE(sut.has_value(init_arg_name_secondary(i))); } } @@ -621,19 +624,21 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(test_argument_parser_parse_args, "count should return 0 by default") { add_arguments(n_positional_args, n_optional_args); - for (std::size_t i = 0ull; i < n_args_total; ++i) { - const auto arg_name = init_arg_name(i); - CHECK_EQ(sut.count(arg_name.primary.value()), 0ull); - CHECK_EQ(sut.count(arg_name.secondary.value()), 0ull); + for (std::size_t i = 0ull; i < n_positional_args; ++i) + CHECK_EQ(sut.count(init_arg_name_primary(i)), 0ull); + + for (std::size_t i = first_opt_arg_idx; i < n_args_total; ++i) { + CHECK_EQ(sut.count(init_arg_name_primary(i)), 0ull); + CHECK_EQ(sut.count(init_arg_name_secondary(i)), 0ull); } } TEST_CASE_FIXTURE( test_argument_parser_parse_args, - "count should return 0 if there is no argument with given name present" + "count should throw if there is no argument with given name present" ) { add_arguments(n_positional_args, n_optional_args); - CHECK_EQ(sut.count(invalid_arg_name), 0ull); + CHECK_THROWS_AS(discard(sut.count(invalid_arg_name)), lookup_failure); } TEST_CASE_FIXTURE( @@ -680,7 +685,7 @@ TEST_CASE_FIXTURE( "value() should throw if there is no argument with given name present" ) { add_arguments(n_positional_args, n_optional_args); - CHECK_THROWS_AS(discard_result(sut.value(invalid_arg_name)), argon::lookup_failure); + CHECK_THROWS_AS(discard(sut.value(invalid_arg_name)), argon::lookup_failure); } TEST_CASE_FIXTURE( @@ -690,11 +695,11 @@ TEST_CASE_FIXTURE( add_arguments(n_positional_args, n_optional_args); for (std::size_t i = 0ull; i < n_positional_args; ++i) - CHECK_THROWS_AS(discard_result(sut.value(init_arg_name_primary(i))), std::logic_error); + CHECK_THROWS_AS(discard(sut.value(init_arg_name_primary(i))), std::logic_error); for (std::size_t i = first_opt_arg_idx; i < n_args_total; ++i) { - CHECK_THROWS_AS(discard_result(sut.value(init_arg_name_primary(i))), std::logic_error); - CHECK_THROWS_AS(discard_result(sut.value(init_arg_name_secondary(i))), std::logic_error); + CHECK_THROWS_AS(discard(sut.value(init_arg_name_primary(i))), std::logic_error); + CHECK_THROWS_AS(discard(sut.value(init_arg_name_secondary(i))), std::logic_error); } } @@ -716,8 +721,7 @@ TEST_CASE_FIXTURE( REQUIRE(sut.has_value(arg_name.primary.value())); CHECK_THROWS_AS( - discard_result(sut.value(arg_name.primary.value())), - argon::type_error + discard(sut.value(arg_name.primary.value())), argon::type_error ); } @@ -784,9 +788,7 @@ TEST_CASE_FIXTURE( "value_or() should throw if there is no argument with given name present" ) { add_arguments(n_positional_args, n_optional_args); - CHECK_THROWS_AS( - discard_result(sut.value_or(invalid_arg_name, empty_str)), argon::lookup_failure - ); + CHECK_THROWS_AS(discard(sut.value_or(invalid_arg_name, empty_str)), argon::lookup_failure); } TEST_CASE_FIXTURE( @@ -807,8 +809,7 @@ TEST_CASE_FIXTURE( REQUIRE(sut.has_value(arg_name.primary.value())); CHECK_THROWS_AS( - discard_result( - sut.value_or(arg_name.primary.value(), invalid_value_type{}) + discard(sut.value_or(arg_name.primary.value(), invalid_value_type{}) ), argon::type_error ); @@ -947,7 +948,7 @@ TEST_CASE_FIXTURE( sut.parse_args(argc, argv); CHECK_THROWS_AS( - discard_result(sut.values(positional_name)), argon::type_error + discard(sut.values(positional_name)), argon::type_error ); free_argv(argc, argv); @@ -1040,12 +1041,10 @@ TEST_CASE_FIXTURE( sut.parse_args(argc, argv); CHECK_THROWS_AS( - discard_result(sut.values(optional_primary_name)), - argon::type_error + discard(sut.values(optional_primary_name)), argon::type_error ); CHECK_THROWS_AS( - discard_result(sut.values(optional_secondary_name)), - argon::type_error + discard(sut.values(optional_secondary_name)), argon::type_error ); free_argv(argc, argv); From 5e0146978ef49d4274d9b9f936464bbd63dd46eb Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 19 Sep 2026 15:57:14 +0200 Subject: [PATCH 5/6] name modifiers tests --- include/argon/argument_parser.hpp | 30 +++++------ .../source/test_argument_parser_args_cfg.cpp | 8 +-- .../test_argument_parser_parse_args.cpp | 54 ++++++++++++++++++- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 7aac46a9..c49030f2 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -246,13 +246,13 @@ class argument_parser { /** * @brief Add default arguments to the argument parser. - * @tparam AR Type of the positional argument discriminator range. + * @tparam ArgvRange Type of the positional argument discriminator range. * @param arg_discriminators A range of default positional argument discriminators. * @note `arg_discriminators` must be a `std::ranges::range` with the `argon::default_argument` value type. * @return Reference to the argument parser. */ - template AR> - argument_parser& default_arguments(const AR& arg_discriminators) noexcept { + template ArgvRange> + argument_parser& default_arguments(const ArgvRange& arg_discriminators) noexcept { for (const auto arg_discriminator : arg_discriminators) detail::add_default_argument(arg_discriminator, *this); return *this; @@ -556,14 +556,14 @@ class argument_parser { /** * @brief Parses the command-line arguments. - * @tparam AR The argument range type. + * @tparam ArgvRange The argument range type. * @param argv_rng A range of command-line argument values. * @note `argv_rng` must be a `std::ranges::forward_range` with a value type convertible to `std::string`. * @throws argon::invalid_configuration, argon::parsing_failure * @attention This overload of the `parse_args` function assumes that the program name argument has already been discarded. */ - template AR> - void parse_args(const AR& argv_rng) { + template ArgvRange> + void parse_args(const ArgvRange& argv_rng) { parsing_state state(*this); this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state); @@ -596,13 +596,13 @@ class argument_parser { * message and the parser are printed to `std::cerr` and the function exists with * `EXIT_FAILURE` status. * - * @tparam AR The argument range type. + * @tparam ArgvRange The argument range type. * @param argv_rng A range of command-line argument values. * @note `argv_rng` must be a `std::ranges::forward_range` with a value type convertible to `std::string`. * @attention This overload of the `try_parse_args` function assumes that the program name argument has already been discarded. */ - template AR> - void try_parse_args(const AR& argv_rng) { + template ArgvRange> + void try_parse_args(const ArgvRange& argv_rng) { try { this->parse_args(argv_rng); } @@ -643,14 +643,14 @@ class argument_parser { * - `add_optional_argument` * - `add_flag` * - * @tparam AR The argument range type. + * @tparam ArgvRange The argument range type. * @param argv_rng A range of command-line argument values. * @note `argv_rng` must be a `std::ranges::forward_range` with a value type convertible to `std::string`. * @throws argon::invalid_configuration, argon::parsing_failure * @attention This overload of the `parse_known_args` function assumes that the program name argument already been discarded. */ - template AR> - std::vector parse_known_args(const AR& argv_rng) { + template ArgvRange> + std::vector parse_known_args(const ArgvRange& argv_rng) { parsing_state state(*this, true); this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state); return std::move(state.unknown_args); @@ -680,14 +680,14 @@ class argument_parser { * and the parser are printed to `std::cerr` and the function exists with `EXIT_FAILURE` status. * Otherwise the result of `parse_known_args(argv_rng)` is returned. * - * @tparam AR The argument range type. + * @tparam ArgvRange The argument range type. * @param argv_rng A range of command-line argument values. * @note `argv_rng` must be a `std::ranges::forward_range` with a value type convertible to `std::string`. * @return A vector of unknown argument values. * @attention This overload of the `try_parse_known_args` function assumes that the program name argument has already been discarded. */ - template AR> - std::vector try_parse_known_args(const AR& argv_rng) { + template ArgvRange> + std::vector try_parse_known_args(const ArgvRange& argv_rng) { try { return this->parse_known_args(argv_rng); } diff --git a/tests/source/test_argument_parser_args_cfg.cpp b/tests/source/test_argument_parser_args_cfg.cpp index 1a118204..72ea0109 100644 --- a/tests/source/test_argument_parser_args_cfg.cpp +++ b/tests/source/test_argument_parser_args_cfg.cpp @@ -388,18 +388,18 @@ TEST_CASE_FIXTURE( sut.add_optional_argument(group, "optional"); sut.add_flag(group, "flag"); - const auto expected_err_msg = [](const auto& arg_name) { + const auto expected_err_msg = [](const auto& arg_name) -> std::string { return lookup_failure::argument_not_found(arg_name).what(); }; CHECK_THROWS_WITH_AS( - discard(sut.value("positional")), expected_err_msg("positional"), lookup_failure + discard(sut.value("positional")), expected_err_msg("positional").c_str(), lookup_failure ); CHECK_THROWS_WITH_AS( - discard(sut.value("optional")), expected_err_msg("optional"), lookup_failure + discard(sut.value("optional")), expected_err_msg("optional").c_str(), lookup_failure ); CHECK_THROWS_WITH_AS( - discard(sut.value("flag")), expected_err_msg("flag"), lookup_failure + discard(sut.value("flag")), expected_err_msg("flag").c_str(), lookup_failure ); CHECK_FALSE(sut.has_value("pre-positional-suf")); diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index 3d8ee36c..18cba109 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -1493,9 +1493,59 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_parse_args, "parse_args should properly handle group prefixes" + test_argument_parser_parse_args, "parse_args should properly handle group prefixes and suffixes" ) { - // TODO + const std::string prefix = "mod_"; + const std::string suffix = "_mod"; + auto& group = sut.add_group("Modified Group").with_prefix(prefix).with_suffix(suffix); + + const std::string pos_base_name = "pos_arg"; + const std::string opt_base_name = "opt_arg"; + const std::string flag_base_name = "flag"; + + // Add arguments to the group + sut.add_positional_argument(group, pos_base_name); + sut.add_optional_argument(group, opt_base_name); + sut.add_flag(group, flag_base_name); + + // Deducing the expected registered names + const std::string expected_pos_name = prefix + pos_base_name + suffix; + const std::string expected_opt_name = prefix + opt_base_name + suffix; + const std::string expected_flag_name = prefix + flag_base_name + suffix; + + const std::string pos_val = "positional-value"; + const std::string opt_val = "optional-value"; + + std::vector argv_vec{ + "program", + pos_val, + std::format("--{}", expected_opt_name), + opt_val, + std::format("--{}", expected_flag_name), + }; + + const int argc = static_cast(argv_vec.size()); + auto argv = to_char_2d_array(argv_vec); + + REQUIRE_NOTHROW(sut.parse_args(argc, argv)); + + // Verify arguments are parsed and accessible by their formatted names + CHECK(sut.has_value(expected_pos_name)); + CHECK_EQ(sut.value(expected_pos_name), pos_val); + + CHECK(sut.has_value(expected_opt_name)); + CHECK_EQ(sut.value(expected_opt_name), opt_val); + CHECK_EQ(sut.count(expected_opt_name), 1ull); + + CHECK(sut.has_value(expected_flag_name)); + CHECK(sut.value(expected_flag_name)); + + // Verify the arguments are NOT accessible by their unformatted base names + CHECK_THROWS_AS(discard(sut.has_value(pos_base_name)), lookup_failure); + CHECK_THROWS_AS(discard(sut.has_value(opt_base_name)), lookup_failure); + CHECK_THROWS_AS(discard(sut.has_value(flag_base_name)), lookup_failure); + + free_argv(argc, argv); } // subparsers From 5991e0acb3fc9f0e74232074e5891174cc965825 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 19 Sep 2026 16:16:02 +0200 Subject: [PATCH 6/6] resolved comments --- include/argon/argument_group.hpp | 9 ++-- include/argon/argument_parser.hpp | 74 +++++++++++++++---------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index 27eead9d..b6fe0e00 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -127,17 +127,18 @@ class argument_group { return std::unique_ptr(new argument_group(parser, name)); } - /// Construct a new argument group with the given name. + /// @brief Construct a new argument group with the given name. argument_group(argument_parser& parser, const std::string_view name) : _parser(&parser), _name(name) {} - /// Add a new argument to this group (called internally by parser). + /// @brief Add a new argument to this group (called internally by parser). void _add_argument(arg_ptr_t arg) noexcept { this->_arguments.emplace_back(std::move(arg)); } - [[nodiscard]] std::string _format_arg_name(std::string_view arg_name) const noexcept { - return std::format("{}{}{}", this->_prefix, arg_name, this->_suffix); + /// @brief Format the argument's base name by adding the group's prefix and suffix. + [[nodiscard]] std::string _format_arg_name(std::string_view arg_base_name) const noexcept { + return std::format("{}{}{}", this->_prefix, arg_base_name, this->_suffix); } argument_parser* _parser; ///< Pointer to the owning parser. diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index c49030f2..60baf404 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -284,29 +284,29 @@ class argument_parser { /** * @brief Adds a positional argument to the parser's configuration. * @tparam T Type of the argument value. - * @param name The name of the argument. + * @param base_name The base name of the argument. * @return Reference to the added positional argument. * @throws argon::invalid_configuration */ template - positional_argument& add_positional_argument(const std::string_view name) { - return this->add_positional_argument(this->_gr_positional_args, name); + positional_argument& add_positional_argument(const std::string_view base_name) { + return this->add_positional_argument(this->_gr_positional_args, base_name); } /** * @brief Adds a positional argument to the parser's configuration and binds it to the given group. * @tparam T Type of the argument value. - * @param name The name of the argument. + * @param base_name The base name of the argument. * @return Reference to the added positional argument. * @throws argon::invalid_configuration */ template positional_argument& add_positional_argument( - argument_group& group, const std::string_view name + argument_group& group, const std::string_view base_name ) { this->_validate_group(group); - const auto full_name = group._format_arg_name(name); + const auto full_name = group._format_arg_name(base_name); this->_verify_arg_name_pattern(full_name); const detail::argument_name arg_name(std::make_optional(full_name)); @@ -322,33 +322,33 @@ class argument_parser { /** * @brief Adds an optional argument to the parser's configuration. * @tparam T Type of the argument value. - * @param name The name of the argument. + * @param base_name The base name of the argument. * @param name_discr The discriminator value specifying whether the given name should be treated as primary or secondary. * @return Reference to the added optional argument. * @throws argon::invalid_configuration */ template optional_argument& add_optional_argument( - const std::string_view name, + const std::string_view base_name, const detail::argument_name_discriminator name_discr = n_primary ) { - return this->add_optional_argument(this->_gr_optional_args, name, name_discr); + return this->add_optional_argument(this->_gr_optional_args, base_name, name_discr); } /** * @brief Adds an optional argument to the parser's configuration. * @tparam T Type of the argument value. - * @param primary_name The primary name of the argument. - * @param secondary_name The secondary name of the argument. + * @param base_primary_name The base primary name of the argument. + * @param base_secondary_name The base secondary name of the argument. * @return Reference to the added optional argument. * @throws argon::invalid_configuration */ template optional_argument& add_optional_argument( - const std::string_view primary_name, const std::string_view secondary_name + const std::string_view base_primary_name, const std::string_view base_secondary_name ) { return this->add_optional_argument( - this->_gr_optional_args, primary_name, secondary_name + this->_gr_optional_args, base_primary_name, base_secondary_name ); } @@ -356,7 +356,7 @@ class argument_parser { * @brief Adds an optional argument to the parser's configuration and binds it to the given group. * @tparam T Type of the argument value. * @param group The argument group to bind the new argument to. - * @param name The name of the argument. + * @param base_name The base name of the argument. * @param name_discr The discriminator value specifying whether the given name should be treated as primary or secondary. * @return Reference to the added optional argument. * @throws std::logic_error, argon::invalid_configuration @@ -364,12 +364,12 @@ class argument_parser { template optional_argument& add_optional_argument( argument_group& group, - const std::string_view name, + const std::string_view base_name, const detail::argument_name_discriminator name_discr = n_primary ) { this->_validate_group(group); - const auto full_name = group._format_arg_name(name); + const auto full_name = group._format_arg_name(base_name); this->_verify_arg_name_pattern(full_name); const auto arg_name = @@ -395,23 +395,23 @@ class argument_parser { * @brief Adds an optional argument to the parser's configuration and binds it to the given group. * @tparam T Type of the argument value. * @param group The argument group to bind the new argument to. - * @param primary_name The primary name of the argument. - * @param secondary_name The secondary name of the argument. + * @param base_primary_name The base primary name of the argument. + * @param base_secondary_name The base secondary name of the argument. * @return Reference to the added optional argument. * @throws argon::invalid_configuration */ template optional_argument& add_optional_argument( argument_group& group, - const std::string_view primary_name, - const std::string_view secondary_name + const std::string_view base_primary_name, + const std::string_view base_secondary_name ) { this->_validate_group(group); - const auto full_primary_name = group._format_arg_name(primary_name); + const auto full_primary_name = group._format_arg_name(base_primary_name); this->_verify_arg_name_pattern(full_primary_name); - const auto full_secondary_name = group._format_arg_name(secondary_name); + const 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( @@ -432,16 +432,16 @@ class argument_parser { * @brief Adds a boolean flag argument (an optional argument with `value_type = bool`) to the parser's configuration. * @tparam StoreImplicitly A boolean value used as the `implicit_values` parameter of the argument. * @note The argument's `default_values` attribute will be set to `not StoreImplicitly`. - * @param name The primary name of the flag. + * @param base_name The primary base name of the flag. * @param name_discr The discriminator value specifying whether the given name should be treated as primary or secondary. * @return Reference to the added boolean flag argument. */ template optional_argument& add_flag( - const std::string_view name, + const std::string_view base_name, const detail::argument_name_discriminator name_discr = n_primary ) { - return this->add_optional_argument(name, name_discr) + return this->add_optional_argument(base_name, name_discr) .default_values(not StoreImplicitly) .implicit_values(StoreImplicitly) .nargs(0ull); @@ -451,15 +451,15 @@ class argument_parser { * @brief Adds a boolean flag argument (an optional argument with `value_type = bool`) to the parser's configuration. * @tparam StoreImplicitly A boolean value used as the `implicit_values` parameter of the argument. * @note The argument's `default_values` attribute will be set to `not StoreImplicitly`. - * @param primary_name The primary name of the flag. - * @param secondary_name The secondary name of the flag. + * @param base_primary_name The base primary name of the flag. + * @param base_secondary_name The base secondary name of the flag. * @return Reference to the added boolean flag argument. */ template optional_argument& add_flag( - const std::string_view primary_name, const std::string_view secondary_name + const std::string_view base_primary_name, const std::string_view base_secondary_name ) { - return this->add_optional_argument(primary_name, secondary_name) + return this->add_optional_argument(base_primary_name, base_secondary_name) .default_values(not StoreImplicitly) .implicit_values(StoreImplicitly) .nargs(0ull); @@ -470,17 +470,17 @@ class argument_parser { * @tparam StoreImplicitly A boolean value used as the `implicit_values` parameter of the argument. * @note The argument's `default_values` attribute will be set to `not StoreImplicitly`. * @param group The argument group to bind the new argument to. - * @param name The primary name of the flag. + * @param base_name The base name of the flag. * @param name_discr The discriminator value specifying whether the given name should be treated as primary or secondary. * @return Reference to the added boolean flag argument. */ template optional_argument& add_flag( argument_group& group, - const std::string_view name, + const std::string_view base_name, const detail::argument_name_discriminator name_discr = n_primary ) { - return this->add_optional_argument(group, name, name_discr) + return this->add_optional_argument(group, base_name, name_discr) .default_values(not StoreImplicitly) .implicit_values(StoreImplicitly) .nargs(0ull); @@ -491,17 +491,17 @@ class argument_parser { * @tparam StoreImplicitly A boolean value used as the `implicit_values` parameter of the argument. * @note The argument's `default_values` attribute will be set to `not StoreImplicitly`. * @param group The argument group to bind the new argument to. - * @param primary_name The primary name of the flag. - * @param secondary_name The secondary name of the flag. + * @param base_primary_name The base primary name of the flag. + * @param base_secondary_name The base secondary name of the flag. * @return Reference to the added boolean flag argument. */ template optional_argument& add_flag( argument_group& group, - const std::string_view primary_name, - const std::string_view secondary_name + const std::string_view base_primary_name, + const std::string_view base_secondary_name ) { - return this->add_optional_argument(group, primary_name, secondary_name) + return this->add_optional_argument(group, base_primary_name, base_secondary_name) .default_values(not StoreImplicitly) .implicit_values(StoreImplicitly) .nargs(0ull);