From ccdbd102dcb0d090452de76f4ab3995409871c15 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 12:48:55 +0200 Subject: [PATCH 1/6] flag_prefix_char impl --- include/argon/argument_parser.hpp | 43 +++++++++++++++--- ..._info.cpp => test_argument_parser_cfg.cpp} | 31 ++++++++----- .../test_argument_parser_parse_args.cpp | 45 +++++++++++++++++++ 3 files changed, 102 insertions(+), 17 deletions(-) rename tests/source/{test_argument_parser_info.cpp => test_argument_parser_cfg.cpp} (70%) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 00d43371..c4ff7fa5 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -243,6 +243,23 @@ class argument_parser { return *this; } + /** + * @brief Set the flag prefix character. + * @param prefix_char The flag prefix character. + * @return Reference to the argument parser. + * @throws argon::invalid_configuration + * @note The default flag prefix character is `'-'`. + */ + argument_parser& flag_prefix_char(const char prefix_char) { + if (not std::isprint(static_cast(prefix_char))) + throw invalid_configuration("The flag prefix character must be a printable ASCII " + "character!"); + + this->_flag_prefix_char = prefix_char; + this->_primary_flag_prefix = std::string(2ull, prefix_char); + return *this; + } + /** * @brief Add default arguments to the argument parser. * @tparam ArgvRange Type of the positional argument discriminator range. @@ -1167,7 +1184,7 @@ class argument_parser { if (util::contains_whitespaces(arg_value)) return detail::argument_token::t_value; - if (arg_value.starts_with(this->_flag_prefix)) + if (arg_value.starts_with(this->_primary_flag_prefix)) return detail::argument_token::t_flag_primary; if (arg_value.starts_with(this->_flag_prefix_char)) @@ -1543,14 +1560,19 @@ class argument_parser { } } + // --- attributes --- + 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. + char _flag_prefix_char = '-'; ///< The character used as a flag prefix. + std::string _primary_flag_prefix = "--"; ///< The primary flag prefix. + + // --- parsing cfg & state --- arg_ptr_vec_t _positional_args = {}; ///< The list of positional arguments. arg_ptr_vec_t _optional_args = {}; ///< The list of optional arguments. @@ -1559,15 +1581,22 @@ class argument_parser { argument_group& _gr_optional_args; ///< The optional argument group. arg_parser_ptr_vec_t _subparsers = {}; ///< The list of subparsers. - bool _invoked = + // --- cfg flags --- + + bool _verbose : 1 = false; ///< Verbosity flag. + + // --- parsing state flags --- + + bool _invoked : 1 = false; ///< A flag indicating whether the parser has been invoked to parse arguments. - bool _finalized = false; ///< A flag indicating whether the parsing process has been finalized. + bool _finalized : 1 = + false; ///< A flag indicating whether the parsing process has been finalized. + + // --- constants --- static constexpr std::uint8_t _primary_flag_prefix_length = 2u; static constexpr std::uint8_t _secondary_flag_prefix_length = 1u; - static constexpr char _flag_prefix_char = '-'; - static constexpr std::string_view _flag_prefix = "--"; - static constexpr std::uint8_t _indent_width = 2; + static constexpr std::uint8_t _indent_width = 2u; }; namespace detail { diff --git a/tests/source/test_argument_parser_info.cpp b/tests/source/test_argument_parser_cfg.cpp similarity index 70% rename from tests/source/test_argument_parser_info.cpp rename to tests/source/test_argument_parser_cfg.cpp index b21e4832..cef7e602 100644 --- a/tests/source/test_argument_parser_info.cpp +++ b/tests/source/test_argument_parser_cfg.cpp @@ -6,9 +6,9 @@ using namespace argon_testing; using argon::argument_parser; using argon::invalid_configuration; -TEST_SUITE_BEGIN("test_argument_parser_info"); +TEST_SUITE_BEGIN("test_argument_parser_cfg"); -struct test_argument_parser_info : public argument_parser_test_fixture { +struct test_argument_parser_cfg : public argument_parser_test_fixture { const std::string test_description = "test program description"; const argon::version test_version{1u, 2u, 3u}; const std::string test_str_version = "alpha"; @@ -29,14 +29,14 @@ TEST_CASE("argument_parser() should throw if the name contains whitespaces") { } TEST_CASE_FIXTURE( - test_argument_parser_info, "argument_parser() should set the name and program name members" + test_argument_parser_cfg, "argument_parser() should set the name and program name members" ) { CHECK_EQ(sut.name(), program_name); CHECK_EQ(sut.program_name(), program_name); } TEST_CASE_FIXTURE( - test_argument_parser_info, + test_argument_parser_cfg, "subparser's program name should be a concatenation of the parent parser's name and its own " "name" ) { @@ -48,13 +48,13 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE( - test_argument_parser_info, "parser's program version member should be nullopt by default" + test_argument_parser_cfg, "parser's program version member should be nullopt by default" ) { CHECK_FALSE(get_program_version()); } TEST_CASE_FIXTURE( - test_argument_parser_info, + test_argument_parser_cfg, "program_version() should throw if the version string contains whitespaces" ) { CHECK_THROWS_WITH_AS( @@ -64,7 +64,7 @@ TEST_CASE_FIXTURE( ); } -TEST_CASE_FIXTURE(test_argument_parser_info, "version() should set the program version member") { +TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() should set the program version member") { sut.program_version(test_version); auto stored_program_version = get_program_version(); REQUIRE(stored_program_version); @@ -77,14 +77,14 @@ TEST_CASE_FIXTURE(test_argument_parser_info, "version() should set the program v } TEST_CASE_FIXTURE( - test_argument_parser_info, "parser's program description member should be nullopt by default" + test_argument_parser_cfg, "parser's program description member should be nullopt by default" ) { const auto stored_program_description = get_program_description(); CHECK_FALSE(stored_program_description); } TEST_CASE_FIXTURE( - test_argument_parser_info, "program_description() should set the program description member" + test_argument_parser_cfg, "program_description() should set the program description member" ) { sut.program_description(test_description); @@ -94,4 +94,15 @@ TEST_CASE_FIXTURE( CHECK_EQ(stored_program_description.value(), test_description); } -TEST_SUITE_END(); // test_argument_parser_info +TEST_CASE_FIXTURE( + test_argument_parser_cfg, + "flag_prefix_char() should throw if the given character is not a printable ASCII character" +) { + CHECK_THROWS_WITH_AS( + sut.flag_prefix_char('\n'), + "The flag prefix character must be a printable ASCII character!", + invalid_configuration + ); +} + +TEST_SUITE_END(); // test_argument_parser_cfg diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index 18cba109..5e0d5453 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -1382,6 +1382,51 @@ TEST_CASE_FIXTURE( free_argv(argc, argv); } +// flag prefix char + +TEST_CASE_FIXTURE( + test_argument_parser_parse_args, + "parse_args should throw when an argument flag is used with a prefix character that is not " + "recognized by the parser" +) { + sut.flag_prefix_char('/'); + + const auto invalid_flag = "--invalid"; + const std::vector argv_vec{"program", invalid_flag}; + + const auto argc = static_cast(argv_vec.size()); + auto argv = to_char_2d_array(argv_vec); + + CHECK_THROWS_WITH_AS( + sut.parse_args(argc, argv), + std::format("Failed to deduce the argument for values [{}]", invalid_flag).c_str(), + parsing_failure + ); + + free_argv(argc, argv); +} + +TEST_CASE_FIXTURE( + test_argument_parser_parse_args, + "parse_args should not throw when an argument flag is used with a prefix character that is " + "recognized by the parser" +) { + sut.flag_prefix_char('/'); + + const auto valid_flag = "/valid"; + sut.add_optional_argument("valid", argon::n_secondary); + + const std::vector argv_vec{"program", valid_flag}; + + const auto argc = static_cast(argv_vec.size()); + auto argv = to_char_2d_array(argv_vec); + + REQUIRE_NOTHROW(sut.parse_args(argc, argv)); + CHECK(sut.is_used("valid")); + + free_argv(argc, argv); +} + // argument groups TEST_CASE_FIXTURE( From 401c055e4944b8da53de79598fdd5de2cc7fb03d Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 14:52:31 +0200 Subject: [PATCH 2/6] flag char cfg order --- include/argon/argument_parser.hpp | 40 ++++++++++++++------------ include/argon/detail/argument_name.hpp | 2 +- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index c4ff7fa5..4e6a7379 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -244,19 +244,23 @@ class argument_parser { } /** - * @brief Set the flag prefix character. - * @param prefix_char The flag prefix character. + * @brief Set the flag character. + * @param chr The flag character. * @return Reference to the argument parser. - * @throws argon::invalid_configuration - * @note The default flag prefix character is `'-'`. + * @throws argon::invalid_configuration if the flag character is not a printable + * ASCII character or if any arguments have already been added to the parser. + * @note The default flag character is `'-'`. */ - argument_parser& flag_prefix_char(const char prefix_char) { - if (not std::isprint(static_cast(prefix_char))) - throw invalid_configuration("The flag prefix character must be a printable ASCII " - "character!"); + argument_parser& flag_char(const char chr) { + if (not this->_positional_args.empty() or not this->_optional_args.empty()) + throw invalid_configuration("The flag character must be set before adding any " + "arguments!"); + + if (not std::isprint(static_cast(chr))) + throw invalid_configuration("The flag character must be a printable ASCII character!"); - this->_flag_prefix_char = prefix_char; - this->_primary_flag_prefix = std::string(2ull, prefix_char); + this->_flag_char = chr; + this->_primary_flag_prefix = std::string(2ull, chr); return *this; } @@ -391,11 +395,9 @@ class argument_parser { const auto arg_name = name_discr == n_primary ? detail:: - argument_name{std::make_optional(full_name), std::nullopt, this->_flag_prefix_char} + argument_name{std::make_optional(full_name), std::nullopt, this->_flag_char} : detail::argument_name{ - std::nullopt, - std::make_optional(full_name), - this->_flag_prefix_char + std::nullopt, std::make_optional(full_name), this->_flag_char }; if (this->_is_arg_name_used(arg_name)) @@ -433,7 +435,7 @@ class argument_parser { const detail::argument_name arg_name( std::make_optional(full_primary_name), std::make_optional(full_secondary_name), - this->_flag_prefix_char + this->_flag_char ); if (this->_is_arg_name_used(arg_name)) throw invalid_configuration::argument_name_used(arg_name); @@ -971,12 +973,12 @@ class argument_parser { arg_name, "An argument name cannot contain whitespaces." ); - if (arg_name.front() == this->_flag_prefix_char) + if (arg_name.front() == this->_flag_char) throw invalid_configuration::invalid_argument_name( arg_name, std::format( "An argument name cannot begin with a flag prefix character ({}).", - this->_flag_prefix_char + this->_flag_char ) ); @@ -1187,7 +1189,7 @@ class argument_parser { if (arg_value.starts_with(this->_primary_flag_prefix)) return detail::argument_token::t_flag_primary; - if (arg_value.starts_with(this->_flag_prefix_char)) + if (arg_value.starts_with(this->_flag_char)) return detail::argument_token::t_flag_secondary; return detail::argument_token::t_value; @@ -1569,7 +1571,7 @@ class argument_parser { std::optional _program_description = std::nullopt; ///< The description of the program. unknown_policy _unknown_policy = unknown_policy::fail; ///< Policy for unknown arguments. - char _flag_prefix_char = '-'; ///< The character used as a flag prefix. + char _flag_char = '-'; ///< The character used as a flag prefix. std::string _primary_flag_prefix = "--"; ///< The primary flag prefix. // --- parsing cfg & state --- diff --git a/include/argon/detail/argument_name.hpp b/include/argon/detail/argument_name.hpp index f3fa0ae2..ae4540ec 100644 --- a/include/argon/detail/argument_name.hpp +++ b/include/argon/detail/argument_name.hpp @@ -109,7 +109,7 @@ struct argument_name { */ [[nodiscard]] std::string str() const noexcept { // if flag_char = nullopt, then the fallback character doesn't matter - the string will be empty - const std::string fc(this->flag_char.has_value(), this->flag_char.value_or(char())); + const std::string fc(this->flag_char.has_value(), this->flag_char.value_or(char{})); std::string primary_str = this->primary ? std::format("{}{}{}", fc, fc, this->primary.value()) : ""; From 8fec9453576210bc0ac6ec83020724c470cf0a46 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 14:55:47 +0200 Subject: [PATCH 3/6] aligned flag char tests --- tests/source/test_argument_parser_cfg.cpp | 19 ++++++++++++++++--- .../test_argument_parser_parse_args.cpp | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/source/test_argument_parser_cfg.cpp b/tests/source/test_argument_parser_cfg.cpp index cef7e602..d0b6cf31 100644 --- a/tests/source/test_argument_parser_cfg.cpp +++ b/tests/source/test_argument_parser_cfg.cpp @@ -94,13 +94,26 @@ TEST_CASE_FIXTURE( CHECK_EQ(stored_program_description.value(), test_description); } +TEST_CASE_FIXTURE( + test_argument_parser_cfg, "flag_char() should throw if the parser already has arguments" +) { + sut.add_positional_argument("arg1"); + sut.add_optional_argument("arg2"); + + CHECK_THROWS_WITH_AS( + sut.flag_char('/'), + "The flag character must be set before adding any arguments!", + invalid_configuration + ); +} + TEST_CASE_FIXTURE( test_argument_parser_cfg, - "flag_prefix_char() should throw if the given character is not a printable ASCII character" + "flag_char() should throw if the given character is not a printable ASCII character" ) { CHECK_THROWS_WITH_AS( - sut.flag_prefix_char('\n'), - "The flag prefix character must be a printable ASCII character!", + sut.flag_char('\n'), + "The flag character must be a printable ASCII character!", invalid_configuration ); } diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index 5e0d5453..166ef682 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -1389,7 +1389,7 @@ TEST_CASE_FIXTURE( "parse_args should throw when an argument flag is used with a prefix character that is not " "recognized by the parser" ) { - sut.flag_prefix_char('/'); + sut.flag_char('/'); const auto invalid_flag = "--invalid"; const std::vector argv_vec{"program", invalid_flag}; @@ -1411,7 +1411,7 @@ TEST_CASE_FIXTURE( "parse_args should not throw when an argument flag is used with a prefix character that is " "recognized by the parser" ) { - sut.flag_prefix_char('/'); + sut.flag_char('/'); const auto valid_flag = "/valid"; sut.add_optional_argument("valid", argon::n_secondary); From 0e7cb7f5233b86912d93d5ad0e5b51d0d4c09ec7 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 14:59:12 +0200 Subject: [PATCH 4/6] resolved comments --- include/argon/argument_parser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 4e6a7379..225ff357 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -260,7 +260,7 @@ class argument_parser { throw invalid_configuration("The flag character must be a printable ASCII character!"); this->_flag_char = chr; - this->_primary_flag_prefix = std::string(2ull, chr); + this->_primary_flag_prefix = std::string(this->_primary_flag_prefix_length, chr); return *this; } From b292c5dfd98619a03062dd9940b571d94b83f8ba Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 15:33:17 +0200 Subject: [PATCH 5/6] refactored argument_name to use sentinels instead of optionals --- include/argon/argument_parser.hpp | 21 ++--- include/argon/detail/argument_name.hpp | 51 ++++++------ .../include/argument_parser_test_fixture.hpp | 6 +- tests/source/test_argument_name.cpp | 44 +++++----- .../source/test_argument_parser_args_cfg.cpp | 40 ++++----- .../test_argument_parser_parse_args.cpp | 81 ++++++++----------- tests/source/test_argument_token.cpp | 3 +- tests/source/test_none_type_argument.cpp | 9 +-- tests/source/test_optional_argument.cpp | 11 +-- tests/source/test_positional_argument.cpp | 4 +- 10 files changed, 115 insertions(+), 155 deletions(-) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 225ff357..ff6b3be4 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -326,10 +326,10 @@ class argument_parser { ) { this->_validate_group(group); - const auto full_name = group._format_arg_name(base_name); + 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)); + const detail::argument_name arg_name(std::move(full_name)); if (this->_is_arg_name_used(arg_name)) throw invalid_configuration::argument_name_used(arg_name); @@ -389,16 +389,13 @@ class argument_parser { ) { this->_validate_group(group); - const auto full_name = group._format_arg_name(base_name); + auto full_name = group._format_arg_name(base_name); this->_verify_arg_name_pattern(full_name); const auto arg_name = name_discr == n_primary - ? detail:: - argument_name{std::make_optional(full_name), std::nullopt, this->_flag_char} - : detail::argument_name{ - std::nullopt, std::make_optional(full_name), this->_flag_char - }; + ? detail::argument_name{std::move(full_name), "", this->_flag_char} + : detail::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); @@ -426,16 +423,14 @@ class argument_parser { ) { this->_validate_group(group); - const auto full_primary_name = group._format_arg_name(base_primary_name); + 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(base_secondary_name); + 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( - std::make_optional(full_primary_name), - std::make_optional(full_secondary_name), - this->_flag_char + std::move(full_primary_name), std::move(full_secondary_name), this->_flag_char ); if (this->_is_arg_name_used(arg_name)) throw invalid_configuration::argument_name_used(arg_name); diff --git a/include/argon/detail/argument_name.hpp b/include/argon/detail/argument_name.hpp index ae4540ec..bdbc171d 100644 --- a/include/argon/detail/argument_name.hpp +++ b/include/argon/detail/argument_name.hpp @@ -8,7 +8,8 @@ #include #include -#include +#include +#include #include #include @@ -26,6 +27,9 @@ struct argument_name { }; using enum match_type; + // --- constants --- + static constexpr char flag_char_sentinel = '\0'; ///< Sentinel value for the flag character. + argument_name() = delete; argument_name& operator=(const argument_name&) = delete; @@ -40,15 +44,11 @@ struct argument_name { * @param secondary The secondary (short) name of the argument. * @param flag_char The flag character (used for optional argument names). */ - argument_name( - std::optional primary, - std::optional secondary = std::nullopt, - std::optional flag_char = std::nullopt + 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(std::move(flag_char)) { - if (not (this->primary or this->secondary)) + : 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"); } @@ -75,11 +75,12 @@ struct argument_name { const noexcept { switch (m_type) { case m_any: - return this->primary == arg_name or this->secondary == arg_name; + return (not this->primary.empty() and this->primary == arg_name) + or (not this->secondary.empty() and this->secondary == arg_name); case m_primary: - return this->primary == arg_name; + return not this->primary.empty() and this->primary == arg_name; case m_secondary: - return this->secondary == arg_name; + return not this->secondary.empty() and this->secondary == arg_name; } return false; @@ -90,32 +91,34 @@ struct argument_name { * @param arg_name The argument_name instance to match. * @param m_type UNUSED - necessary to match the signature of the `string_view` overload of the `match` function. * @return True if arg_name's primary or secondary value matches the argument_name instance. + * @todo Remove the m_type parameter */ [[nodiscard]] bool match( const argument_name& arg_name, [[maybe_unused]] const match_type m_type = m_any ) const noexcept { - if (arg_name.primary and this->match(arg_name.primary.value())) + if (not arg_name.primary.empty() and this->match(arg_name.primary)) return true; - if (arg_name.secondary) - return this->match(arg_name.secondary.value()); + if (not arg_name.secondary.empty()) + return this->match(arg_name.secondary); return false; } /** * @brief Get a string representation of the argument_name. - * @param flag_char The character used for the argument flag prefix. */ [[nodiscard]] std::string str() const noexcept { - // if flag_char = nullopt, then the fallback character doesn't matter - the string will be empty - const std::string fc(this->flag_char.has_value(), this->flag_char.value_or(char{})); + const std::string fc( + static_cast(this->flag_char != flag_char_sentinel), this->flag_char + ); std::string primary_str = - this->primary ? std::format("{}{}{}", fc, fc, this->primary.value()) : ""; - std::string separator = this->primary and this->secondary ? ", " : ""; + not this->primary.empty() ? std::format("{}{}{}", fc, fc, this->primary) : ""; + std::string separator = + not this->primary.empty() and not this->secondary.empty() ? ", " : ""; std::string secondary_str = - this->secondary ? std::format("{}{}", fc, this->secondary.value()) : ""; + not this->secondary.empty() ? std::format("{}{}", fc, this->secondary) : ""; return std::format("{}{}{}", primary_str, separator, secondary_str); } @@ -131,9 +134,9 @@ struct argument_name { return os; } - const std::optional primary; ///< The primary name of the argument. - const std::optional secondary; ///< The optional (short) name of the argument. - const std::optional flag_char; ///< The flag character (used for optional argument names). + 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). }; /** diff --git a/tests/include/argument_parser_test_fixture.hpp b/tests/include/argument_parser_test_fixture.hpp index c4a67dbc..09a68e61 100644 --- a/tests/include/argument_parser_test_fixture.hpp +++ b/tests/include/argument_parser_test_fixture.hpp @@ -108,11 +108,9 @@ struct argument_parser_test_fixture { } [[nodiscard]] argument_name init_arg_name( - const std::size_t i, std::optional flag_char = std::nullopt + const std::size_t i, char flag_char = argument_name::flag_char_sentinel ) const { - return argument_name( - init_arg_name_primary(i), init_arg_name_secondary(i), std::move(flag_char) - ); + return argument_name{init_arg_name_primary(i), init_arg_name_secondary(i), flag_char}; } template < diff --git a/tests/source/test_argument_name.cpp b/tests/source/test_argument_name.cpp index 6cd83bbd..5250b49a 100644 --- a/tests/source/test_argument_name.cpp +++ b/tests/source/test_argument_name.cpp @@ -10,31 +10,25 @@ TEST_SUITE_BEGIN("test_argument_name"); namespace { -constexpr std::string_view primary_1 = "primary_1"; -constexpr std::string_view secondary_1 = "s1"; +const std::string primary_1 = "primary_1"; +const std::string secondary_1 = "s1"; -const auto primary_1_opt = std::make_optional(primary_1); -const auto secondary_1_opt = std::make_optional(secondary_1); +const argument_name arg_name_primary_1{primary_1}; +const argument_name arg_name_secondary_1{"", secondary_1}; +const argument_name arg_name_full_1{primary_1, secondary_1}; -const argument_name arg_name_primary_1{primary_1_opt}; -const argument_name arg_name_secondary_1{std::nullopt, secondary_1_opt}; -const argument_name arg_name_full_1{primary_1_opt, secondary_1_opt}; +const std::string primary_2 = "primary_2"; +const std::string secondary_2 = "s2"; -constexpr std::string_view primary_2 = "primary_2"; -constexpr std::string_view secondary_2 = "s2"; - -const auto primary_2_opt = std::make_optional(primary_2); -const auto secondary_2_opt = std::make_optional(secondary_2); - -const argument_name arg_name_primary_2{primary_2_opt}; -const argument_name arg_name_secondary_2{std::nullopt, secondary_2_opt}; -const argument_name arg_name_full_2{primary_2_opt, secondary_2_opt}; +const argument_name arg_name_primary_2{primary_2}; +const argument_name arg_name_secondary_2{"", secondary_2}; +const argument_name arg_name_full_2{primary_2, secondary_2}; } // namespace TEST_CASE("arugment_name construction should throw if both primary and secondary names are null") { CHECK_THROWS_WITH_AS( - (argument_name{std::nullopt, std::nullopt}), + (argument_name{"", ""}), "An argument name cannot be empty! At least one of primary/secondary must be specified", std::logic_error ); @@ -42,9 +36,9 @@ 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_FALSE(arg_name_primary_1.secondary.has_value()); + CHECK(arg_name_primary_1.secondary.empty()); - CHECK_FALSE(arg_name_secondary_1.primary.has_value()); + 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); @@ -130,17 +124,17 @@ TEST_CASE("match(argument_name) should return true if either the primary or the "the passed argument_name matches at least one name") { // argument_name with primary name only CHECK(arg_name_primary_1.match(arg_name_primary_1)); - CHECK(arg_name_primary_1.match(argument_name{primary_2_opt, primary_1_opt})); + CHECK(arg_name_primary_1.match(argument_name{primary_2, primary_1})); // argument_name with primary name only CHECK(arg_name_secondary_1.match(arg_name_secondary_1)); - CHECK(arg_name_secondary_1.match(argument_name{secondary_1_opt, secondary_2_opt})); + CHECK(arg_name_secondary_1.match(argument_name{secondary_1, secondary_2})); // argument_name with both names - CHECK(arg_name_full_1.match(argument_name{primary_1_opt, secondary_2_opt})); - CHECK(arg_name_full_1.match(argument_name{secondary_1_opt, primary_1_opt})); - CHECK(arg_name_full_1.match(argument_name{primary_2_opt, primary_1_opt})); - CHECK(arg_name_full_1.match(argument_name{primary_2_opt, secondary_1_opt})); + CHECK(arg_name_full_1.match(argument_name{primary_1, secondary_2})); + CHECK(arg_name_full_1.match(argument_name{secondary_1, primary_1})); + CHECK(arg_name_full_1.match(argument_name{primary_2, primary_1})); + CHECK(arg_name_full_1.match(argument_name{primary_2, secondary_1})); } TEST_CASE("match(argument_name) should return false if neither the primary nor the secondary name " diff --git a/tests/source/test_argument_parser_args_cfg.cpp b/tests/source/test_argument_parser_args_cfg.cpp index 72ea0109..2d4e2af7 100644 --- a/tests/source/test_argument_parser_args_cfg.cpp +++ b/tests/source/test_argument_parser_args_cfg.cpp @@ -15,26 +15,16 @@ TEST_SUITE_BEGIN("test_argument_parser_args_cfg"); 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"; - const std::string_view secondary_name_1 = "s1"; - - const std::optional primary_name_1_opt = - std::make_optional(primary_name_1); - const std::optional secondary_name_1_opt = - std::make_optional(secondary_name_1); - - const std::string_view primary_name_2 = "primary_name_2"; - const std::string_view secondary_name_2 = "s2"; - - const std::optional primary_name_2_opt = - std::make_optional(primary_name_2); - const std::optional secondary_name_2_opt = - std::make_optional(secondary_name_2); - - const std::string_view invalid_name_empty = ""; - const std::string_view invalid_name_whitespace = "invalid name"; - const std::string_view invalid_name_flag_prefix = "-invalid"; - const std::string_view invalid_name_digit = "1invalid"; + const std::string primary_name_1 = "primary_name_1"; + const std::string secondary_name_1 = "s1"; + + const std::string primary_name_2 = "primary_name_2"; + const std::string secondary_name_2 = "s2"; + + const std::string invalid_name_empty = ""; + const std::string invalid_name_whitespace = "invalid name"; + const std::string invalid_name_flag_prefix = "-invalid"; + const std::string invalid_name_digit = "1invalid"; }; TEST_CASE_FIXTURE( @@ -172,7 +162,7 @@ TEST_CASE_FIXTURE( // adding argument with a previously used name CHECK_THROWS_WITH_AS( sut.add_positional_argument(primary_name_1), - invalid_configuration::argument_name_used({primary_name_1_opt}).what(), + invalid_configuration::argument_name_used(argument_name{primary_name_1}).what(), invalid_configuration ); } @@ -191,7 +181,7 @@ TEST_CASE_FIXTURE( CHECK_THROWS_WITH_AS( sut.add_optional_argument(primary_name_1, secondary_name_2), invalid_configuration::argument_name_used( - {primary_name_1_opt, secondary_name_2_opt, flag_char} + argument_name{primary_name_1, secondary_name_2, flag_char} ) .what(), invalid_configuration @@ -202,7 +192,7 @@ TEST_CASE_FIXTURE( CHECK_THROWS_WITH_AS( sut.add_optional_argument(primary_name_2, secondary_name_1), invalid_configuration::argument_name_used( - {primary_name_2_opt, secondary_name_1_opt, flag_char} + argument_name{primary_name_2, secondary_name_1, flag_char} ) .what(), invalid_configuration @@ -251,7 +241,7 @@ TEST_CASE_FIXTURE( CHECK_THROWS_WITH_AS( sut.add_flag(primary_name_1, secondary_name_2), invalid_configuration::argument_name_used( - {primary_name_1_opt, secondary_name_2_opt, flag_char} + argument_name{primary_name_1, secondary_name_2, flag_char} ) .what(), invalid_configuration @@ -262,7 +252,7 @@ TEST_CASE_FIXTURE( CHECK_THROWS_WITH_AS( sut.add_flag(primary_name_2, secondary_name_1), invalid_configuration::argument_name_used( - {primary_name_2_opt, secondary_name_1_opt, flag_char} + argument_name{primary_name_2, secondary_name_1, flag_char} ) .what(), invalid_configuration diff --git a/tests/source/test_argument_parser_parse_args.cpp b/tests/source/test_argument_parser_parse_args.cpp index 166ef682..e642af12 100644 --- a/tests/source/test_argument_parser_parse_args.cpp +++ b/tests/source/test_argument_parser_parse_args.cpp @@ -158,7 +158,8 @@ TEST_CASE_FIXTURE( CHECK_THROWS_WITH_AS( sut.parse_args(argc, argv), - required_argument_not_parsed_msg({init_arg_name_primary(last_pos_arg_idx)}).c_str(), + required_argument_not_parsed_msg(argument_name{init_arg_name_primary(last_pos_arg_idx)}) + .c_str(), parsing_failure ); @@ -228,10 +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.value(), required_arg_name.secondary.value() - ) - .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); @@ -255,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.value(), range_arg_name.secondary.value()) + sut.add_optional_argument(range_arg_name.primary, range_arg_name.secondary) .nargs(at_least(1ull)); CHECK_THROWS_WITH_AS( @@ -307,10 +305,10 @@ TEST_CASE_FIXTURE( std::string invalid_flag; SUBCASE("primary name with a secondary flag prefix") { - invalid_flag = "-" + opt_arg_name.primary.value(); + invalid_flag = "-" + opt_arg_name.primary; } SUBCASE("secondary name with a primary flag prefix") { - invalid_flag = "--" + opt_arg_name.secondary.value(); + invalid_flag = "--" + opt_arg_name.secondary; } CAPTURE(invalid_flag); @@ -339,10 +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.value(), required_arg_name.secondary.value() - ) - .required(); + sut.add_optional_argument(required_arg_name.primary, required_arg_name.secondary).required(); int argc; char** argv; @@ -379,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.value(); + 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.value()).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()); @@ -406,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.value(), bypass_required_arg_name.secondary.value() + bypass_required_arg_name.primary, bypass_required_arg_name.secondary ) .default_values(false) .implicit_values(true) @@ -428,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.value())); + CHECK(sut.value(bypass_required_arg_name.primary)); free_argv(argc, argv); } @@ -502,10 +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.value(), required_arg_name.secondary.value() - ) - .required(); + sut.add_optional_argument(required_arg_name.primary, required_arg_name.secondary).required(); int argc; char** argv; @@ -719,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.value())); + REQUIRE(sut.has_value(arg_name.primary)); CHECK_THROWS_AS( - discard(sut.value(arg_name.primary.value())), argon::type_error + discard(sut.value(arg_name.primary)), argon::type_error ); } @@ -752,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.value())); - CHECK_EQ(sut.value(arg_name.primary.value()), arg_value); - CHECK_EQ(sut.value(arg_name.secondary.value()), 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); @@ -767,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.value(), arg_name.secondary.value()) + sut.add_optional_argument(arg_name.primary, arg_name.secondary) .default_values(init_arg_value(i)); } @@ -775,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.value())); - CHECK_EQ(sut.value(arg_name.primary.value()), arg_value); - CHECK_EQ(sut.value(arg_name.secondary.value()), 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); } } @@ -807,10 +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.value())); + REQUIRE(sut.has_value(arg_name.primary)); CHECK_THROWS_AS( - discard(sut.value_or(arg_name.primary.value(), invalid_value_type{}) - ), + discard(sut.value_or(arg_name.primary, invalid_value_type{})), argon::type_error ); } @@ -842,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.value())); - CHECK_EQ(sut.value_or(arg_name.primary.value(), empty_str), arg_value); - CHECK_EQ(sut.value_or(arg_name.secondary.value(), 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); @@ -857,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.value(), arg_name.secondary.value()) + sut.add_optional_argument(arg_name.primary, arg_name.secondary) .default_values(init_arg_value(i)); } @@ -865,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.value())); - CHECK_EQ(sut.value_or(arg_name.primary.value(), empty_str), arg_value); - CHECK_EQ(sut.value_or(arg_name.secondary.value(), 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); } } @@ -893,12 +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.value(), fallback_value), fallback_value - ); - CHECK_EQ( - sut.value_or(arg_name.secondary.value(), 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); } } @@ -918,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.value(), fallback_value), fallback_value); - CHECK_EQ(sut.value_or(arg_name.secondary.value(), 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); } } diff --git a/tests/source/test_argument_token.cpp b/tests/source/test_argument_token.cpp index f63ea827..9b6c897e 100644 --- a/tests/source/test_argument_token.cpp +++ b/tests/source/test_argument_token.cpp @@ -56,8 +56,7 @@ TEST_CASE("is_valid_flag_token should return true if the token is a flag token a CHECK_FALSE(sut_type{t_flag_primary, ""}.is_valid_flag_token()); CHECK_FALSE(sut_type{t_flag_secondary, ""}.is_valid_flag_token()); - std::shared_ptr arg_ptr = - std::make_shared>(argument_name{""}); + auto arg_ptr = std::make_shared>(argument_name{"name"}); CHECK(sut_type{t_flag_primary, "", {arg_ptr}}.is_valid_flag_token()); CHECK(sut_type{t_flag_secondary, "", {arg_ptr}}.is_valid_flag_token()); diff --git a/tests/source/test_none_type_argument.cpp b/tests/source/test_none_type_argument.cpp index 1384e3fa..1a4f2b89 100644 --- a/tests/source/test_none_type_argument.cpp +++ b/tests/source/test_none_type_argument.cpp @@ -11,13 +11,10 @@ using argon::detail::argument_name; namespace { -constexpr std::string_view primary_name = "test"; -const auto primary_name_opt = std::make_optional(primary_name); +const std::string primary_name = "test"; +const std::string secondary_name = "t"; -constexpr std::string_view secondary_name = "t"; -const auto secondary_name_opt = std::make_optional(secondary_name); - -const argument_name arg_name(primary_name_opt, secondary_name_opt); +const argument_name arg_name{primary_name, secondary_name}; using sut_value_type = argon::none_type; using sut_type = optional_argument; diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index 12246e02..d779f995 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -18,14 +18,11 @@ namespace { constexpr char flag_char = '-'; -constexpr std::string_view primary_name = "test"; -const auto primary_name_opt = std::make_optional(primary_name); +const std::string primary_name = "test"; +const std::string secondary_name = "t"; -constexpr std::string_view secondary_name = "t"; -const auto secondary_name_opt = std::make_optional(secondary_name); - -const argument_name arg_name(primary_name_opt, secondary_name_opt, flag_char); -const argument_name arg_name_primary(primary_name_opt, std::nullopt, flag_char); +const argument_name arg_name{primary_name, secondary_name, flag_char}; +const argument_name arg_name_primary{primary_name, "", flag_char}; constexpr std::string_view help_msg = "test help msg"; diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index 932da1c2..c81d1353 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -16,8 +16,8 @@ namespace { constexpr std::string_view help_msg = "test help msg"; -constexpr std::string_view name_value = "test"; -const argument_name arg_name(std::make_optional(name_value), std::nullopt); +const std::string name_value = "test"; +const argument_name arg_name{name_value}; using sut_value_type = int; using sut_type = positional_argument; From 699fc3bea35df90a9bdc5a1ef3582887c5ea1272 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 20 Sep 2026 15:45:07 +0200 Subject: [PATCH 6/6] resolved comments --- include/argon/argument_parser.hpp | 20 ++++--------- include/argon/detail/argument_name.hpp | 41 +++++++++++++++----------- tests/source/test_argument_name.cpp | 12 ++++++++ 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index ff6b3be4..0045737e 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -999,29 +999,19 @@ class argument_parser { /** * @brief Returns a unary predicate function which checks if the given name matches the argument's name * @param arg_name The name of the argument. - * @param m_type The match type used within the predicate. * @return Argument predicate based on the provided name. */ - [[nodiscard]] auto _name_match_predicate( - const detail::argument_name& arg_name, - const detail::argument_name::match_type m_type = detail::argument_name::m_any - ) const noexcept { - return [&arg_name, m_type](const arg_ptr_t& arg) { - return arg->name().match(arg_name, m_type); - }; + [[nodiscard]] auto _name_match_predicate(const detail::argument_name& arg_name) const noexcept { + return [&arg_name](const arg_ptr_t& arg) { return arg->name().match(arg_name); }; } /** * @brief Check if an argument name is already used. * @param arg_name The name of the argument. - * @param m_type The match type used to find 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 detail::argument_name::match_type m_type = detail::argument_name::m_any - ) const noexcept { - const auto predicate = this->_name_match_predicate(arg_name, m_type); + [[nodiscard]] bool _is_arg_name_used(const detail::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()) return true; @@ -1467,7 +1457,7 @@ class argument_parser { * @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 { + [[nodiscard]] 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); diff --git a/include/argon/detail/argument_name.hpp b/include/argon/detail/argument_name.hpp index bdbc171d..2abdfbce 100644 --- a/include/argon/detail/argument_name.hpp +++ b/include/argon/detail/argument_name.hpp @@ -27,9 +27,6 @@ struct argument_name { }; using enum match_type; - // --- constants --- - static constexpr char flag_char_sentinel = '\0'; ///< Sentinel value for the flag character. - argument_name() = delete; argument_name& operator=(const argument_name&) = delete; @@ -65,6 +62,16 @@ struct argument_name { 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(); + } + + /// @brief Checks if the argument name instance has a secondary name. + [[nodiscard]] bool has_secondary() const noexcept { + return not this->secondary.empty(); + } + /** * @brief Matches the given string to the argument_name instance. * @param arg_name The name string to match. @@ -75,12 +82,12 @@ struct argument_name { const noexcept { switch (m_type) { case m_any: - return (not this->primary.empty() and this->primary == arg_name) - or (not this->secondary.empty() 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 not this->primary.empty() and this->primary == arg_name; + return this->has_primary() and this->primary == arg_name; case m_secondary: - return not this->secondary.empty() and this->secondary == arg_name; + return this->has_secondary() and this->secondary == arg_name; } return false; @@ -89,17 +96,13 @@ struct argument_name { /** * @brief Matches the given argument name to the argument_name instance. * @param arg_name The argument_name instance to match. - * @param m_type UNUSED - necessary to match the signature of the `string_view` overload of the `match` function. * @return True if arg_name's primary or secondary value matches the argument_name instance. - * @todo Remove the m_type parameter */ - [[nodiscard]] bool match( - const argument_name& arg_name, [[maybe_unused]] const match_type m_type = m_any - ) const noexcept { - if (not arg_name.primary.empty() and this->match(arg_name.primary)) + [[nodiscard]] bool match(const argument_name& arg_name) const noexcept { + if (arg_name.has_primary() and this->match(arg_name.primary)) return true; - if (not arg_name.secondary.empty()) + if (arg_name.has_secondary()) return this->match(arg_name.secondary); return false; @@ -114,11 +117,10 @@ struct argument_name { ); std::string primary_str = - not this->primary.empty() ? std::format("{}{}{}", fc, fc, this->primary) : ""; - std::string separator = - not this->primary.empty() and not this->secondary.empty() ? ", " : ""; + this->has_primary() ? std::format("{}{}{}", fc, fc, this->primary) : ""; + std::string separator = this->has_primary() and this->has_secondary() ? ", " : ""; std::string secondary_str = - not this->secondary.empty() ? std::format("{}{}", fc, this->secondary) : ""; + this->has_secondary() ? std::format("{}{}", fc, this->secondary) : ""; return std::format("{}{}{}", primary_str, separator, secondary_str); } @@ -137,6 +139,9 @@ struct argument_name { 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. }; /** diff --git a/tests/source/test_argument_name.cpp b/tests/source/test_argument_name.cpp index 5250b49a..4d56d3bd 100644 --- a/tests/source/test_argument_name.cpp +++ b/tests/source/test_argument_name.cpp @@ -63,6 +63,18 @@ TEST_CASE("operator==(argument_name) should return true only if both primary and CHECK_NE(arg_name_full_1, arg_name_full_2); } +TEST_CASE("has_primary() should return true only if the primary name is set") { + CHECK(arg_name_primary_1.has_primary()); + CHECK_FALSE(arg_name_secondary_1.has_primary()); + CHECK(arg_name_full_1.has_primary()); +} + +TEST_CASE("has_secondary() should return true only if the secondary name is set") { + CHECK_FALSE(arg_name_primary_1.has_secondary()); + CHECK(arg_name_secondary_1.has_secondary()); + CHECK(arg_name_full_1.has_secondary()); +} + TEST_CASE("match(string_view, any) should return true if the given string matches at least one name" ) { // argument_name with primary name only