diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 00d43371..0045737e 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -243,6 +243,27 @@ class argument_parser { return *this; } + /** + * @brief Set the flag character. + * @param chr The flag character. + * @return Reference to the argument parser. + * @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_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_char = chr; + this->_primary_flag_prefix = std::string(this->_primary_flag_prefix_length, chr); + return *this; + } + /** * @brief Add default arguments to the argument parser. * @tparam ArgvRange Type of the positional argument discriminator range. @@ -305,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); @@ -368,18 +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_prefix_char} - : detail::argument_name{ - std::nullopt, - std::make_optional(full_name), - this->_flag_prefix_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); @@ -407,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_prefix_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); @@ -954,12 +968,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 ) ); @@ -985,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; @@ -1167,10 +1171,10 @@ 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)) + if (arg_value.starts_with(this->_flag_char)) return detail::argument_token::t_flag_secondary; return detail::argument_token::t_value; @@ -1453,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); @@ -1543,14 +1547,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_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 +1568,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/include/argon/detail/argument_name.hpp b/include/argon/detail/argument_name.hpp index f3fa0ae2..2abdfbce 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 @@ -40,15 +41,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"); } @@ -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,11 +82,12 @@ struct argument_name { const noexcept { switch (m_type) { case m_any: - return this->primary == arg_name or this->secondary == arg_name; + return (this->has_primary() and this->primary == arg_name) + or (this->has_secondary() and this->secondary == arg_name); case m_primary: - return this->primary == arg_name; + return this->has_primary() and this->primary == arg_name; case m_secondary: - return this->secondary == arg_name; + return this->has_secondary() and this->secondary == arg_name; } return false; @@ -88,34 +96,31 @@ 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. */ - [[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())) + [[nodiscard]] bool match(const argument_name& arg_name) const noexcept { + if (arg_name.has_primary() and this->match(arg_name.primary)) return true; - if (arg_name.secondary) - return this->match(arg_name.secondary.value()); + if (arg_name.has_secondary()) + 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 ? ", " : ""; + this->has_primary() ? std::format("{}{}{}", fc, fc, this->primary) : ""; + std::string separator = this->has_primary() and this->has_secondary() ? ", " : ""; std::string secondary_str = - this->secondary ? std::format("{}{}", fc, this->secondary.value()) : ""; + this->has_secondary() ? std::format("{}{}", fc, this->secondary) : ""; return std::format("{}{}{}", primary_str, separator, secondary_str); } @@ -131,9 +136,12 @@ 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). + + // --- constants --- + static constexpr char flag_char_sentinel = '\0'; ///< Sentinel value for the flag character. }; /** 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..4d56d3bd 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); @@ -69,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 @@ -130,17 +136,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_info.cpp b/tests/source/test_argument_parser_cfg.cpp similarity index 64% rename from tests/source/test_argument_parser_info.cpp rename to tests/source/test_argument_parser_cfg.cpp index b21e4832..d0b6cf31 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,28 @@ 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_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_char() should throw if the given character is not a printable ASCII character" +) { + CHECK_THROWS_WITH_AS( + sut.flag_char('\n'), + "The flag 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..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); } } @@ -1382,6 +1369,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_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_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( 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;