From ca20c10a9f2b28853e5599933bdb1d8cd7d7257e Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 21:35:36 +0200 Subject: [PATCH 1/4] value getters for the argument class --- include/argon/argument.hpp | 111 +++++++++++++--------- include/argon/argument_group.hpp | 15 +++ include/argon/argument_parser.hpp | 29 ++++++ tests/include/argument_test_fixture.hpp | 25 ----- tests/source/test_optional_argument.cpp | 104 ++++++++++++++------ tests/source/test_positional_argument.cpp | 82 +++++++++++----- 6 files changed, 246 insertions(+), 120 deletions(-) diff --git a/include/argon/argument.hpp b/include/argon/argument.hpp index 8bc60f32..a81145f9 100644 --- a/include/argon/argument.hpp +++ b/include/argon/argument.hpp @@ -417,6 +417,72 @@ class argument : public detail::typed_argument_base { return *this; } + // --- value and state getters --- + + /// @return `true` if the argument is used, `false` otherwise. + [[nodiscard]] bool is_used() const noexcept override { + return this->count() > 0ull; + } + + /** + * @return The number of times the argument has been used. + * @note - For positional arguments, the count is either `0` (not used) or `1` (used). + * @note - For optional arguments, the count reflects the number of times the argument's flag has been used. + */ + [[nodiscard]] std::size_t count() const noexcept override { + if constexpr (type == argument_type::optional) + return this->_count; + else + return static_cast(this->has_parsed_values()); + } + + /// @return `true` if the argument has a value, `false` otherwise. + /// @note An argument is considered to have a value if it has parsed values or predefined values (default/implicit). + [[nodiscard]] bool has_value() const noexcept override { + return this->has_parsed_values() or 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. + * @throws std::logic_error if no values are available. + */ + [[nodiscard]] traits::argument_result_type value() const override { + if (this->has_parsed_values()) + return this->_values.front(); + + if constexpr (traits::c_is_none) + throw std::logic_error( + std::format("No values parsed for argument '{}'.", this->_name.str()) + ); + else + return this->_predefined_values().front(); + } + + /** + * @brief Get the value of the argument, if it has any, or a fallback value, if not. + * @tparam U The fallback value type. + * @param fallback_value The fallback value. + * @return The value of the argument. + */ + template U> + [[nodiscard]] value_type value_or(U&& fallback_value) const + requires(not traits::c_is_none) + { + try { + return this->value(); + } + catch (const std::logic_error&) { + // No parsed values and no predefined (default/implicit) values + return value_type{std::forward(fallback_value)}; + } + } + + /// @return Reference to the vector of parsed values for the argument. + [[nodiscard]] const std::vector& values() const override { + return this->_values_impl(); + } + #ifdef AP_TESTING friend struct ::argon_testing::argument_test_fixture; #endif @@ -495,23 +561,6 @@ class argument : public detail::typed_argument_base { return this->_accepts_further_values(); } - /// @return `true` if the argument is used, `false` otherwise. - [[nodiscard]] bool is_used() const noexcept override { - return this->count() > 0ull; - } - - /** - * @return The number of times the argument has been used. - * @note - For positional arguments, the count is either `0` (not used) or `1` (used). - * @note - For optional arguments, the count reflects the number of times the argument's flag has been used. - */ - [[nodiscard]] std::size_t count() const noexcept override { - if constexpr (type == argument_type::optional) - return this->_count; - else - return static_cast(this->has_parsed_values()); - } - /** * @brief Set the value for the optional argument. * @param str_value The string value to use. @@ -522,12 +571,6 @@ class argument : public detail::typed_argument_base { return this->_set_value_impl(str_value); } - /// @return `true` if the argument has a value, `false` otherwise. - /// @note An argument is considered to have a value if it has parsed values or predefined values (default/implicit). - [[nodiscard]] bool has_value() const noexcept override { - 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(); @@ -546,28 +589,6 @@ class argument : public detail::typed_argument_base { return this->_values.size() <=> this->_nargs_range; } - /** - * @return Reference to the stored value of the argument. - * @note If multiple values are available, the first one is returned. - * @throws std::logic_error if no values are available. - */ - [[nodiscard]] traits::argument_result_type value() const override { - if (this->has_parsed_values()) - return this->_values.front(); - - if constexpr (traits::c_is_none) - throw std::logic_error( - std::format("No values parsed for argument '{}'.", this->_name.str()) - ); - else - return this->_predefined_values().front(); - } - - /// @return Reference to the vector of parsed values for the argument. - [[nodiscard]] const std::vector& values() const override { - return this->_values_impl(); - } - /// @return Reference to the vector of parsed values for the argument. /// @note For none-type arguments, the method always returns an empty vector. [[nodiscard]] const std::vector& _values_impl() const noexcept diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index b6fe0e00..e078009e 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -112,6 +112,21 @@ class argument_group { return *this; } + // --- argument value and state getters --- + + // template + // [[nodiscard]] traits::argument_result_type value(std::string_view arg_base_name) const; + + // template U> + // [[nodiscard]] T value_or(std::string_view arg_base_name, U&& fallback_value) const; + + // template + // [[nodiscard]] const std::vector& values(std::string_view arg_base_name) const; + + // [[nodiscard]] bool is_used(std::string_view arg_base_name) const; + // [[nodiscard]] bool has_value(std::string_view arg_base_name) const; + // [[nodiscard]] std::size_t count(std::string_view arg_base_name) const; + friend class argument_parser; private: diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index f1d556f2..1a8f36fd 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -1586,6 +1586,35 @@ class argument_parser { static constexpr std::uint8_t _indent_width = 2u; }; +// --- argument_group inline implementations --- + +// template +// inline traits::argument_result_type argument_group::value(std::string_view arg_base_name) const { +// return this->_parser->value(this->_format_arg_name(arg_base_name)); +// } + +// template U> +// inline T argument_group::value_or(std::string_view arg_base_name, U&& fallback_value) const { +// return this->_parser->value_or(this->_format_arg_name(arg_base_name), std::forward(fallback_value)); +// } + +// template +// inline const std::vector& argument_group::values(std::string_view arg_base_name) const { +// return this->_parser->values(this->_format_arg_name(arg_base_name)); +// } + +// inline bool argument_group::is_used(std::string_view arg_base_name) const { +// return this->_parser->is_used(this->_format_arg_name(arg_base_name)); +// } + +// inline bool argument_group::has_value(std::string_view arg_base_name) const { +// return this->_parser->has_value(this->_format_arg_name(arg_base_name)); +// } + +// inline std::size_t argument_group::count(std::string_view arg_base_name) const { +// return this->_parser->count(this->_format_arg_name(arg_base_name)); +// } + namespace detail { /** diff --git a/tests/include/argument_test_fixture.hpp b/tests/include/argument_test_fixture.hpp index e2f5b2b4..4ea3e860 100644 --- a/tests/include/argument_test_fixture.hpp +++ b/tests/include/argument_test_fixture.hpp @@ -22,16 +22,6 @@ struct argument_test_fixture { return arg.mark_used(); } - template - bool is_used(const argument& arg) const { - return arg.is_used(); - } - - template - std::size_t get_count(const argument& arg) const { - return arg.count(); - } - template bool set_value(argument& arg, const T& value) const { return set_value(arg, as_string(value)); @@ -52,11 +42,6 @@ struct argument_test_fixture { arg._values.clear(); } - template - [[nodiscard]] bool has_value(const argument& arg) const { - return arg.has_value(); - } - template [[nodiscard]] bool has_parsed_values(const argument& arg) const { return arg.has_parsed_values(); @@ -72,16 +57,6 @@ struct argument_test_fixture { return arg.nvalues_ordering(); } - template - [[nodiscard]] argument_result_type get_value(const argument& arg) const { - return arg.value(); - } - - template - [[nodiscard]] const std::vector& get_values(const argument& arg) const { - return arg.values(); - } - template [[nodiscard]] const argument_name& get_name(const argument& arg) const { return arg.name(); diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index 321d10a6..b8cb3c93 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -276,22 +276,22 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(argument_test_fixture, "is_used() should return false by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(is_used(sut)); + CHECK_FALSE(sut.is_used()); } TEST_CASE_FIXTURE( argument_test_fixture, "is_used() should return true if the argument's flag has been used" ) { auto sut = sut_type(arg_name_primary); - REQUIRE_FALSE(is_used(sut)); + REQUIRE_FALSE(sut.is_used()); mark_used(sut); - CHECK(is_used(sut)); + CHECK(sut.is_used()); } TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 0 by default") { const auto sut = sut_type(arg_name_primary); - CHECK_EQ(get_count(sut), 0ull); + CHECK_EQ(sut.count(), 0ull); } TEST_CASE_FIXTURE( @@ -305,7 +305,7 @@ TEST_CASE_FIXTURE( for (std::size_t n = 0ull; n < count; ++n) mark_used(sut); - CHECK_EQ(get_count(sut), count); + CHECK_EQ(sut.count(), count); } TEST_CASE_FIXTURE(argument_test_fixture, "argument flag usage should trigger the on-flag actions") { @@ -319,13 +319,13 @@ TEST_CASE_FIXTURE(argument_test_fixture, "argument flag usage should trigger the TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return false by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return true if value is set") { auto sut = sut_type(arg_name_primary); set_value(sut, arbitrary_value); - CHECK(has_value(sut)); + CHECK(sut.has_value()); } TEST_CASE_FIXTURE( @@ -336,8 +336,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.default_values(default_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), default_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), default_value); } TEST_CASE_FIXTURE( @@ -347,7 +347,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.implicit_values(implicit_value); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE( @@ -359,8 +359,8 @@ TEST_CASE_FIXTURE( mark_used(sut); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), implicit_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), implicit_value); } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { @@ -452,8 +452,8 @@ TEST_CASE_FIXTURE( ) { auto sut = sut_type(arg_name_primary); - REQUIRE_FALSE(has_value(sut)); - CHECK_THROWS_AS(discard(get_value(sut)), std::logic_error); + REQUIRE_FALSE(sut.has_value()); + CHECK_THROWS_AS(discard(sut.value()), std::logic_error); } TEST_CASE_FIXTURE( @@ -463,8 +463,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name_primary); sut.default_values(arbitrary_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), arbitrary_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), arbitrary_value); } TEST_CASE_FIXTURE( @@ -476,8 +476,8 @@ TEST_CASE_FIXTURE( mark_used(sut); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), implicit_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), implicit_value); } TEST_CASE_FIXTURE( @@ -488,8 +488,58 @@ TEST_CASE_FIXTURE( sut.implicit_values(implicit_value); set_value(sut, arbitrary_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), arbitrary_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), arbitrary_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the fallback value if the argument's value has not been set" +) { + auto sut = sut_type(arg_name_primary); + constexpr sut_value_type fallback_value = 999; + + REQUIRE_FALSE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), fallback_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the default value if one has been provided and argument is not used" +) { + auto sut = sut_type(arg_name_primary); + sut.default_values(arbitrary_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), arbitrary_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the implicit value if one has been provided and argument is used" +) { + auto sut = sut_type(arg_name_primary); + sut.implicit_values(implicit_value); + constexpr sut_value_type fallback_value = 999; + + mark_used(sut); + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), implicit_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, "value_or() should return the argument's value if it has been set" +) { + auto sut = sut_type(arg_name_primary); + sut.default_values(default_value); + sut.implicit_values(implicit_value); + set_value(sut, arbitrary_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), arbitrary_value); } TEST_CASE_FIXTURE( @@ -505,7 +555,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name_primary, empty_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } SUBCASE("given string is non-convertible to value_type") { REQUIRE_THROWS_WITH_AS( @@ -513,7 +563,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name_primary, invalid_value_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } } @@ -530,7 +580,7 @@ TEST_CASE_FIXTURE( parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE( @@ -543,7 +593,7 @@ TEST_CASE_FIXTURE( for (const auto value : choices) REQUIRE_NOTHROW(set_value(sut, value)); - REQUIRE_EQ(get_values(sut), choices); + REQUIRE_EQ(sut.values(), choices); CHECK_THROWS_WITH_AS( set_value(sut, arbitrary_value), @@ -570,7 +620,7 @@ TEST_CASE_FIXTURE( sut_value_type valid_value = 16; REQUIRE_NOTHROW(set_value(sut, valid_value)); - CHECK_EQ(get_value(sut), valid_value); + CHECK_EQ(sut.value(), valid_value); } SUBCASE("transform action") { @@ -579,7 +629,7 @@ TEST_CASE_FIXTURE( set_value(sut, arbitrary_value); - CHECK_EQ(get_value(sut), double_action(arbitrary_value)); + CHECK_EQ(sut.value(), double_action(arbitrary_value)); } SUBCASE("modify action") { @@ -591,7 +641,7 @@ TEST_CASE_FIXTURE( set_value(sut, test_value); double_action(test_value); - CHECK_EQ(get_value(sut), test_value); + CHECK_EQ(sut.value(), test_value); } } diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index 9c8a6af9..a6e7afef 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -242,41 +242,41 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(argument_test_fixture, "is_used() should return false by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(is_used(sut)); + CHECK_FALSE(sut.is_used()); } TEST_CASE_FIXTURE( argument_test_fixture, "is_used() should return true when argument contains a value" ) { auto sut = sut_type(arg_name); - REQUIRE_FALSE(is_used(sut)); + REQUIRE_FALSE(sut.is_used()); set_value(sut, valid_value); - CHECK(is_used(sut)); + CHECK(sut.is_used()); } TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 0 by default") { const auto sut = sut_type(arg_name); - CHECK_EQ(get_count(sut), 0ull); + CHECK_EQ(sut.count(), 0ull); } TEST_CASE_FIXTURE(argument_test_fixture, "count() should return 1 when argument contains a value") { auto sut = sut_type(arg_name); set_value(sut, valid_value); - CHECK_EQ(get_count(sut), 1ull); + CHECK_EQ(sut.count(), 1ull); } TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return false by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_value() should return true if the value is set") { auto sut = sut_type(arg_name); set_value(sut, valid_value); - CHECK(has_value(sut)); + CHECK(sut.has_value()); } TEST_CASE_FIXTURE( @@ -285,7 +285,7 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); sut.default_values(default_value); - CHECK(has_value(sut)); + CHECK(sut.has_value()); } TEST_CASE_FIXTURE(argument_test_fixture, "has_parsed_values() should return false by default") { @@ -329,8 +329,8 @@ TEST_CASE_FIXTURE( ) { auto sut = sut_type(arg_name); - REQUIRE_FALSE(has_value(sut)); - CHECK_THROWS_AS(discard(get_value(sut)), std::logic_error); + REQUIRE_FALSE(sut.has_value()); + CHECK_THROWS_AS(discard(sut.value()), std::logic_error); } TEST_CASE_FIXTURE( @@ -339,8 +339,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); set_value(sut, valid_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), valid_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), valid_value); } TEST_CASE_FIXTURE( @@ -351,8 +351,8 @@ TEST_CASE_FIXTURE( auto sut = sut_type(arg_name); sut.default_values(default_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), default_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), default_value); } TEST_CASE_FIXTURE( @@ -362,8 +362,44 @@ TEST_CASE_FIXTURE( sut.default_values(default_value); set_value(sut, valid_value); - REQUIRE(has_value(sut)); - CHECK_EQ(get_value(sut), valid_value); + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value(), valid_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the fallback value if the argument's value has not been set" +) { + auto sut = sut_type(arg_name); + constexpr sut_value_type fallback_value = 999; + + REQUIRE_FALSE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), fallback_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, "value_or() should return the argument's value if it has been set" +) { + auto sut = sut_type(arg_name); + set_value(sut, valid_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), valid_value); +} + +TEST_CASE_FIXTURE( + argument_test_fixture, + "value_or() should return the default argument's default value if it has been set and no " + "values " + "were parsed" +) { + auto sut = sut_type(arg_name); + sut.default_values(default_value); + constexpr sut_value_type fallback_value = 999; + + REQUIRE(sut.has_value()); + CHECK_EQ(sut.value_or(fallback_value), default_value); } TEST_CASE_FIXTURE( @@ -379,7 +415,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name, empty_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } SUBCASE("given string is non-convertible to value_type") { @@ -388,7 +424,7 @@ TEST_CASE_FIXTURE( invalid_value_msg(arg_name, invalid_value_str).c_str(), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } } @@ -404,7 +440,7 @@ TEST_CASE_FIXTURE( doctest::Contains(invalid_choice_msg(arg_name, as_string(invalid_choice)).c_str()), parsing_failure ); - CHECK_FALSE(has_value(sut)); + CHECK_FALSE(sut.has_value()); } TEST_CASE_FIXTURE( @@ -417,7 +453,7 @@ TEST_CASE_FIXTURE( for (const auto value : choices) REQUIRE_NOTHROW(set_value(sut, value)); - REQUIRE_EQ(get_values(sut), choices); + REQUIRE_EQ(sut.values(), choices); CHECK_THROWS_WITH_AS( set_value(sut, valid_value), @@ -442,7 +478,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec sut_value_type valid_value = 16; REQUIRE_NOTHROW(set_value(sut, valid_value)); - CHECK_EQ(get_value(sut), valid_value); + CHECK_EQ(sut.value(), valid_value); } SUBCASE("transform action") { @@ -451,7 +487,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec set_value(sut, valid_value); - CHECK_EQ(get_value(sut), double_action(valid_value)); + CHECK_EQ(sut.value(), double_action(valid_value)); } SUBCASE("modify action") { @@ -463,7 +499,7 @@ TEST_CASE_FIXTURE(argument_test_fixture, "set_value(any) should perform the spec set_value(sut, test_value); double_action(test_value); - CHECK_EQ(get_value(sut), test_value); + CHECK_EQ(sut.value(), test_value); } } From 4017d665087afb0e6fa40e31d0336e7c22ba3f43 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 22:07:01 +0200 Subject: [PATCH 2/4] argument group getters --- include/argon/argument_group.hpp | 18 ++--- include/argon/argument_parser.hpp | 54 ++++++------- .../test_argument_parser_parse_args.cpp | 75 +++++++++++++++++++ 3 files changed, 112 insertions(+), 35 deletions(-) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index e078009e..2be40660 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -114,18 +114,18 @@ class argument_group { // --- argument value and state getters --- - // template - // [[nodiscard]] traits::argument_result_type value(std::string_view arg_base_name) const; + [[nodiscard]] bool is_used(std::string_view arg_base_name) const noexcept; + [[nodiscard]] std::size_t count(std::string_view arg_base_name) const noexcept; + [[nodiscard]] bool has_value(std::string_view arg_base_name) const noexcept; - // template U> - // [[nodiscard]] T value_or(std::string_view arg_base_name, U&& fallback_value) const; + template + [[nodiscard]] traits::argument_result_type value(std::string_view arg_base_name) const; - // template - // [[nodiscard]] const std::vector& values(std::string_view arg_base_name) const; + template U> + [[nodiscard]] T value_or(std::string_view arg_base_name, U&& fallback_value) const; - // [[nodiscard]] bool is_used(std::string_view arg_base_name) const; - // [[nodiscard]] bool has_value(std::string_view arg_base_name) const; - // [[nodiscard]] std::size_t count(std::string_view arg_base_name) const; + template + [[nodiscard]] const std::vector& values(std::string_view arg_base_name) const; friend class argument_parser; diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 1a8f36fd..208b8d3f 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -1588,32 +1588,34 @@ class argument_parser { // --- argument_group inline implementations --- -// template -// inline traits::argument_result_type argument_group::value(std::string_view arg_base_name) const { -// return this->_parser->value(this->_format_arg_name(arg_base_name)); -// } - -// template U> -// inline T argument_group::value_or(std::string_view arg_base_name, U&& fallback_value) const { -// return this->_parser->value_or(this->_format_arg_name(arg_base_name), std::forward(fallback_value)); -// } - -// template -// inline const std::vector& argument_group::values(std::string_view arg_base_name) const { -// return this->_parser->values(this->_format_arg_name(arg_base_name)); -// } - -// inline bool argument_group::is_used(std::string_view arg_base_name) const { -// return this->_parser->is_used(this->_format_arg_name(arg_base_name)); -// } - -// inline bool argument_group::has_value(std::string_view arg_base_name) const { -// return this->_parser->has_value(this->_format_arg_name(arg_base_name)); -// } - -// inline std::size_t argument_group::count(std::string_view arg_base_name) const { -// return this->_parser->count(this->_format_arg_name(arg_base_name)); -// } +inline bool argument_group::is_used(std::string_view arg_base_name) const noexcept { + return this->_parser->is_used(this->_format_arg_name(arg_base_name)); +} + +inline std::size_t argument_group::count(std::string_view arg_base_name) const noexcept { + return this->_parser->count(this->_format_arg_name(arg_base_name)); +} + +inline bool argument_group::has_value(std::string_view arg_base_name) const noexcept { + return this->_parser->has_value(this->_format_arg_name(arg_base_name)); +} + +template +inline traits::argument_result_type argument_group::value(std::string_view arg_base_name) const { + return this->_parser->value(this->_format_arg_name(arg_base_name)); +} + +template U> +inline T argument_group::value_or(std::string_view arg_base_name, U&& fallback_value) const { + return this->_parser->value_or( + this->_format_arg_name(arg_base_name), std::forward(fallback_value) + ); +} + +template +inline const std::vector& argument_group::values(std::string_view arg_base_name) const { + return this->_parser->values(this->_format_arg_name(arg_base_name)); +} namespace detail { diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index e642af12..b4bca471 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -1580,6 +1580,81 @@ TEST_CASE_FIXTURE( free_argv(argc, argv); } +TEST_CASE_FIXTURE( + test_argument_parser_parse_args, + "argument_group's argument value and state getters should automatically apply prefixes and " + "suffixes and return correct values" +) { + 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"; + const std::string unused_opt_base = "unused_opt"; + + // Add arguments to the group + sut.add_positional_argument(group, pos_base_name); + sut.add_optional_argument(group, opt_base_name).nargs(argon::nargs::any()); + sut.add_flag(group, flag_base_name); + sut.add_optional_argument(group, unused_opt_base); + + 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_val1 = "opt1"; + const std::string opt_val2 = "opt2"; + const std::string fallback_str = "fallback_value"; + + std::vector argv_vec{ + "program", + pos_val, + std::format("--{}", expected_opt_name), + opt_val1, + opt_val2, + 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 group getters for a positional argument + CHECK(group.has_value(pos_base_name)); + CHECK(group.is_used(pos_base_name)); + CHECK_EQ(group.count(pos_base_name), 1ull); + CHECK_EQ(group.value(pos_base_name), pos_val); + CHECK_EQ(group.value_or(pos_base_name, fallback_str), pos_val); + CHECK_EQ(group.values(pos_base_name), std::vector{pos_val}); + + // Verify group getters for a multi-value optional argument + CHECK(group.has_value(opt_base_name)); + CHECK(group.is_used(opt_base_name)); + CHECK_EQ(group.count(opt_base_name), 1ull); // Used once (one flag provided) + CHECK_EQ(group.value(opt_base_name), opt_val1); + CHECK_EQ(group.value_or(opt_base_name, fallback_str), opt_val1); + CHECK_EQ(group.values(opt_base_name), (std::vector{opt_val1, opt_val2})); + + // Verify group getters for a boolean flag + CHECK(group.has_value(flag_base_name)); + CHECK(group.is_used(flag_base_name)); + CHECK_EQ(group.count(flag_base_name), 1ull); + CHECK(group.value(flag_base_name)); + CHECK(group.value_or(flag_base_name, false)); + + // Verify group getters for an unused optional argument + CHECK_FALSE(group.has_value(unused_opt_base)); + CHECK_FALSE(group.is_used(unused_opt_base)); + CHECK_EQ(group.count(unused_opt_base), 0ull); + CHECK_EQ(group.value_or(unused_opt_base, fallback_str), fallback_str); + CHECK_THROWS_AS(discard(group.value(unused_opt_base)), std::logic_error); + + free_argv(argc, argv); +} + // subparsers TEST_CASE_FIXTURE( From d9d0547ae0dbebc45afb243f4465d4f66cbe3121 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 21 Sep 2026 10:35:39 +0200 Subject: [PATCH 3/4] docs alignment --- docs/tutorial.md | 144 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 29 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 13745142..21ee1d71 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -31,6 +31,9 @@ - [Creating New Groups](#creating-new-groups) - [Adding Arguments to Groups](#adding-arguments-to-groups) - [Group Attributes](#group-attributes) + - [Validation Rules](#validation-rules) + - [Naming Modifiers](#naming-modifiers) + - [Visibility](#visibility) - [Complete Example](#complete-example) - [Suppressing Argument Group Checks](#suppressing-argument-group-checks) - [Parsing Arguments](#parsing-arguments) @@ -42,6 +45,9 @@ - [Compound Arguments](#compound-arguments) - [Parsing Known Arguments](#parsing-known-arguments) - [Retrieving Argument Values](#retrieving-argument-values) + - [Using the Parser](#using-the-parser) + - [Using Argument References](#using-argument-references) + - [Using Argument Groups](#using-argument-groups) - [Subparsers](#subparsers) - [Creating Subparsers](#creating-subparsers) - [Using Multiple Subparsers](#using-multiple-subparsers) @@ -894,12 +900,14 @@ parser.add_flag(out_opts, "print", "p") ### Group Attributes -User-defined groups can be configured with special attributes that change how the parser enforces their usage: +User-defined groups can be configured with special attributes that change how the parser enforces their usage, modifies their arguments' names, or handles their visibility in the help output: + +#### Validation Rules - `required()` – at least one argument from the group must be provided by the user, otherwise parsing will fail. - `mutually_exclusive()` – at most one argument from the group can be provided; using more than one at the same time results in an error. -Both attributes are **off by default**, and they can be combined (e.g., a group can require that exactly one argument is chosen). +Both attributes are **off by default**, and they can be combined (i.e., a group can require that exactly one argument is chosen). ```cpp auto& out_opts = parser.add_group("Output Options") @@ -911,16 +919,34 @@ auto& out_opts = parser.add_group("Output Options") > > If a group is defined as **mutually exclusive** and an argument from this group is used, then the `required` and `nargs` attribute requirements of other arguments from the group **will NOT be verified**. > -> Consider the example in the section below. Normally the `--output, -o` argument would expect a value to be given in the command-line. However, if the `--print, -p` flag is used, then the `nargs` requirement of the `--output, -o` argument will not be verified, and therefore no exception will be thrown, even though the `nargs` requirement is not satisfied. +> Consider the example in the [Complete Example](#complete-example) section. Normally the `--out-file, -out-f` argument would expect a value to be given in the command-line due to the `.nargs(1)` parameter. However, if the `--out-console, -out-c` flag is used, then the `nargs` requirement of the `--out-file, -out-f` argument will not be verified, and therefore no exception will be thrown, even though the `nargs` requirement is not satisfied. + +#### Naming Modifiers + +Groups can automatically apply modifiers to the names of all arguments registered to them. This is especially useful for preventing name collisions or grouping related arguments under a common namespace. + +* `with_prefix("str")` – prepends the specified string to the argument's base name. +* `with_suffix("str")` – appends the specified string to the argument's base name. + +```cpp +auto& net_opts = parser.add_group("Network").with_prefix("net-").with_suffix("-cfg"); +net_opts.add_optional_argument("port"); // Registered in the parser as "net-port-cfg" +``` + +> [!NOTE] +> +> When using naming modifiers, the argument is registered in the main parser under its fully modified name. To learn how to easily retrieve values using only the base names, see [Retrieving Values from Groups](https://www.google.com/search?q=%2523using-argument-groups&utm_source=gemini). + +#### Visibility -Additionally, argument groups can be marked `hidden` - a hidden group will not be visible in the parser's help output (even if it has visible arguments). +* `hidden()` – If this option is set, the entire group (including all of its visible arguments) will be hidden from the program's help description. ```cpp auto& hidden_opts = parser.add_group("Hidden Options").hidden(); -parser.add_optional_argument("visible").help("A visible arg"); +parser.add_optional_argument(hidden_opts, "visible").help("A visible arg"); ``` -In the example above, neither the `Hidden Options` group nor the `visible` arg will be visible in the parser's help output. +In the example above, neither the `Hidden Options` group nor the `visible` arg will be printed in the parser's help output. ### Complete Example @@ -935,15 +961,16 @@ int main(int argc, char* argv[]) { // create the argument group auto& out_opts = parser.add_group("Output Options") + .with_prefix("out-") .required() .mutually_exclusive(); // add arguments to the custom group - parser.add_optional_argument(out_opts, "output", "o") + parser.add_optional_argument(out_opts, "file", "f") .nargs(1) .help("Print output to a given file"); - parser.add_flag(out_opts, "print", "p") + parser.add_flag(out_opts, "console", "c") .help("Print output to the console"); parser.try_parse_args(argc, argv); @@ -957,10 +984,14 @@ When invoked with the `--help` flag, the above program produces a help message t ``` Program: myprog +Optional Arguments: + + --help, -h : Display the help message + Output Options: (required, mutually exclusive) - --output, -o : Print output to a given file - --print, -p : Print output to the console + --out-file, -out-f : Print output to a given file + --out-console, -out-c : Print output to the console ``` ### Suppressing Argument Group Checks @@ -1402,40 +1433,95 @@ Now all the values, that caused an exception for the `parse_args` example, are c ## Retrieving Argument Values -You can retrieve the argument's value(s) with: +Once parsing is complete, you can extract your data using three different interfaces depending on how you organized your arguments: via the parser, via direct argument references, or via argument groups. + +### Using the Parser + +You can retrieve an argument's value(s) using the parser instance by providing the argument's registered name: ```cpp -/*const*/ value_type value = parser.value("argument_name"); // (1) +/*const*/ value_type value /*&*/ = parser.value("argument_name"); // (1) /*const*/ value_type value = parser.value_or("argument_name", fallback_value); // (2) const std::vector& values = parser.values("argument_name"); // (3) + ``` 1. Returns the given argument's value. +* Returns the argument's parsed value if it has one. +* If more than one value has been parsed for the argument, this function will return the first parsed value. +* Returns the argument's predefined value if no value has been parsed for the argument. +> **NOTE:** For simple/small types (e.g. booleans, integers) this method returns by value. Otherwise, the argument's value is returned by reference. - - Returns the argument's parsed value if it has one. - - If more than one value has been parsed for the argument, this function will return the first parsed value. - - Returns the argument's predefined value if no value has been parsed for the argument. +1. Returns the given argument's value or the specified fallback value if the argument has no values. +* If the argument has a value (parsed or predefined), the behavior is the same as in case **(1)**. +* If the argument has no values, this will return `value_type{std::forward(fallback_value)}` (where `U` is the deduced type of `fallback_value`). +> **NOTE:** Because of the fallback value, the function always returns by value, even if the argument's value type is large. -2. Returns the given argument's value or the specified fallback value if the argument has no values. +1. Returns a vector of the given argument's values. +* If the argument has any values (parsed or predefined), they will be returned as a `std::vector`. +* If the argument has no values an empty vector will be returned. - - If the argument has a value (parsed or predefined), the behavior is the same as in case **(1)**. - - If the argument has no values, this will return `value_type{std::forward(fallback_value)}` (where `U` is the deduced type of `fallback_value`). -3. Returns a vector of the given argument's values. - - - If the argument has any values (parsed or predefined), they will be returned as a `std::vector`. - - If the argument has no values an empty vector will be returned. > [!NOTE] -> > The argument value getter functions might throw an exception if: -> - An argument with the given name does not exist -> - The argument does not contain any values - parsed or predefined (only getter function `(1)`) -> - The specified `value_type` does not match the value type of the argument +> * An argument with the given name does not exist +> * The argument does not contain any values - parsed or predefined (only getter function `(1)`) +> * The specified `value_type` does not match the value type of the argument -
-
-
+### Using Argument References + +When you define an argument, the parser returns a strongly-typed reference to the argument object. You can use this reference to retrieve the argument's state and values directly. + +Because the argument reference is inherently aware of its own `value_type`, you **do not** need to provide template parameters when calling these methods, making it entirely type-safe. + +```cpp +// Capture the returned references +auto& port_arg = parser.add_optional_argument("port", "p"); +auto& verbose_arg = parser.add_optional_argument("verbose", "v") + .help("Set the verbosity level (e.g. -vvv)"); + +parser.try_parse_args(argc, argv); + +// Retrieve values directly without template parameters +int port = port_arg.value_or(8080); + +// Query argument state (count tracks the number of times the flag was used) +std::size_t verbosity_level = verbose_arg.count(); + +if (port_arg.is_used() and verbosity_level >= 2) { + std::cout << "Port explicitly set to " << port << '\n'; +} +``` + +The argument instance provides the exact same API as the parser (`value()`, `value_or()`, `values()`, `has_value()`, `is_used()`, `count()`), but tied directly to itself. + +### Using Argument Groups + +If you organize your arguments into an `argument_group` configured with a `prefix` or `suffix`, retrieving values via the `argument_parser` requires you to use the fully formatted name (e.g., `parser.value("mod_output_mod")`). + +To simplify this, the `argument_group` class provides its own value and state getters. These methods automatically apply the group's naming modifiers, allowing you to query arguments using their simple base names. + +```cpp +auto& out_opts = parser.add_group("Output").with_prefix("out-"); + +out_opts.add_optional_argument("file"); // Registered in the parser as "out-file" +out_opts.add_optional_argument("verbose", "v"); // Registered as "out-verbose" + +parser.try_parse_args(argc, argv); + +// Querying the parser requires the full name: +std::size_t verbosity_level = parser.count("out-verbose"); + +// Querying the group requires ONLY the base name: +std::size_t verbosity_level_grp = out_opts.count("verbose"); +std::string out_file = out_opts.value_or("file", "default.txt"); + +// State getters are also available: +if (out_opts.is_used("file")) { + std::cout << "Writing to " << out_file << " at verbosity level " << verbosity_level_grp << '\n'; +} +```

From 01d7dc249353804939a90dfa6debcf9c6870e6a6 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 21 Sep 2026 10:44:12 +0200 Subject: [PATCH 4/4] resolved comments --- docs/tutorial.md | 8 +++++--- include/argon/argument_parser.hpp | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 21ee1d71..5d5da2c2 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -929,7 +929,7 @@ Groups can automatically apply modifiers to the names of all arguments registere * `with_suffix("str")` – appends the specified string to the argument's base name. ```cpp -auto& net_opts = parser.add_group("Network").with_prefix("net-").with_suffix("-cfg"); +auto& net_opts = parser.add_group("Network Options").with_prefix("net-").with_suffix("-cfg"); net_opts.add_optional_argument("port"); // Registered in the parser as "net-port-cfg" ``` @@ -1450,12 +1450,14 @@ const std::vector& values = parser.values("argument_name * Returns the argument's parsed value if it has one. * If more than one value has been parsed for the argument, this function will return the first parsed value. * Returns the argument's predefined value if no value has been parsed for the argument. -> **NOTE:** For simple/small types (e.g. booleans, integers) this method returns by value. Otherwise, the argument's value is returned by reference. +> [!NOTE] +> For simple/small types (e.g. booleans, integers) this method returns by value. Otherwise, the argument's value is returned by reference. 1. Returns the given argument's value or the specified fallback value if the argument has no values. * If the argument has a value (parsed or predefined), the behavior is the same as in case **(1)**. * If the argument has no values, this will return `value_type{std::forward(fallback_value)}` (where `U` is the deduced type of `fallback_value`). -> **NOTE:** Because of the fallback value, the function always returns by value, even if the argument's value type is large. +> [!NOTE] +> Because of the fallback value, the function always returns by value, even if the argument's value type is large. 1. Returns a vector of the given argument's values. * If the argument has any values (parsed or predefined), they will be returned as a `std::vector`. diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 208b8d3f..ea40c1ee 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -1586,7 +1586,7 @@ class argument_parser { static constexpr std::uint8_t _indent_width = 2u; }; -// --- argument_group inline implementations --- +// --- argument_group method implementations --- inline bool argument_group::is_used(std::string_view arg_base_name) const noexcept { return this->_parser->is_used(this->_format_arg_name(arg_base_name));