From 5bf7555d072febf7d277af07dd749a0605fb302e Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 26 Sep 2026 17:49:59 +0200 Subject: [PATCH 1/6] impl + other stuff --- docs/tutorial.md | 34 +++++++------- include/argon/argument.hpp | 7 +-- include/argon/argument_base.hpp | 2 +- include/argon/argument_group.hpp | 54 ++++++++++++++++++++++- include/argon/argument_parser.hpp | 48 ++++++++++---------- include/argon/detail/help_builder.hpp | 11 +++-- tests/source/test_argument_parser_cfg.cpp | 40 ++++++++--------- tests/source/test_help_builder.cpp | 17 +++---- tests/source/test_optional_argument.cpp | 20 +++------ tests/source/test_positional_argument.cpp | 20 +++------ 10 files changed, 146 insertions(+), 107 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 54893d77..253868ae 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -150,8 +150,8 @@ To use the argument parser in your code you need to use the `argon::argument_par ```cpp argon::argument_parser parser("program"); -parser.program_version("alpha") - .program_description("Description of the program") +parser.version("alpha") + .description("Description of the program") .verbose(); ``` @@ -168,10 +168,10 @@ parser.program_version("alpha") > [!TIP] > You can specify the program version using a string (like in the example above) or using the `argon::version` structure: > ```cpp -> parser.program_version({0u, 0u, 0u}) -> parser.program_version({ .major = 1u, .minor = 1u, .patch = 1u }); +> parser.version({0u, 0u, 0u}) +> parser.version({ .major = 1u, .minor = 1u, .patch = 1u }); > argon::version ver{2u, 2u, 2u}; -> parser.program_version(ver); +> parser.version(ver); > ``` > > **NOTE:** The `argon::version` struct @@ -309,7 +309,7 @@ By default all arguments are visible, but this can be modified using the `hidden ```cpp argon::argument_parser("hidden-test") -parser.program_description("A simple test program for argument hiding") +parser.description("A simple test program for argument hiding") .default_arguments(argon::default_argument::o_help); parser.add_optional_argument("hidden") @@ -1073,8 +1073,8 @@ int main(int argc, char* argv[]) { argon::argument_parser parser("some-program"); // define the parser's attributes and default arguments - parser.program_version({0u, 0u, 0u}) - .program_description("The program does something with command-line arguments") + parser.version({0u, 0u, 0u}) + .description("The program does something with command-line arguments") .default_arguments(argon::default_argument::o_help); // define the program arguments @@ -1235,7 +1235,7 @@ This behavior can be modified using the `unknown_arguments_policy` method of the int main(int argc, char* argv[]) { argon::argument_parser parser("unknown-policy-test"); - parser.program_description("A simple test program for unknwon argument handling policies") + parser.description("A simple test program for unknwon argument handling policies") .default_arguments(argon::default_argument::o_help) // set the unknown argument flags handling policy .unknown_arguments_policy(argon::unknown_policy::); @@ -1617,14 +1617,14 @@ For example: ```cpp // top-level parser argon::argument_parser git("ap-git"); -git.program_version({.major = 2u, .minor = 43u, .patch = 0u}) - .program_description("A version control system built with CPP-ARGON") +git.version({.major = 2u, .minor = 43u, .patch = 0u}) + .description("A version control system built with CPP-ARGON") .default_arguments(argon::default_argument::o_help, argon::default_argument::o_version); // subcommand: status auto& status = git.add_subparser("status"); status.default_arguments(argon::default_argument::o_help) - .program_description("Show the working tree status"); + .description("Show the working tree status"); status.add_flag("short", "s") .help("Give the output in the short-format"); ``` @@ -1637,19 +1637,19 @@ You can add as many subparsers as you like, each corresponding to a different co ```cpp auto& init = git.add_subparser("init"); -init.program_description("Create an empty Git repository or reinitialize an existing one"); +init.description("Create an empty Git repository or reinitialize an existing one"); auto& add = git.add_subparser("add"); -add.program_description("Add file contents to the index"); +add.description("Add file contents to the index"); auto& commit = git.add_subparser("commit"); -commit.program_description("Record changes to the repository"); +commit.description("Record changes to the repository"); auto& status = git.add_subparser("status"); -status.program_description("Show the working tree status"); +status.description("Show the working tree status"); auto& push = git.add_subparser("push"); -push.program_description("Update remote refs along with associated objects"); +push.description("Update remote refs along with associated objects"); ``` All defined subparsers will be included in the parent parser's help message: diff --git a/include/argon/argument.hpp b/include/argon/argument.hpp index a7308169..fcc6b702 100644 --- a/include/argon/argument.hpp +++ b/include/argon/argument.hpp @@ -101,8 +101,8 @@ class argument : public typed_argument_base { return this->_name; } - /// @return Optional help message for the positional argument. - [[nodiscard]] const std::optional& help() const noexcept override { + /// @return The help message for the argument. + [[nodiscard]] const std::string& help() const noexcept override { return this->_help_msg; } @@ -769,7 +769,8 @@ class argument : public typed_argument_base { // attributes const argon::argument_name _name; ///< The argument's name. - std::optional _help_msg; ///< The argument's help message. + std::string _help_msg; ///< The argument's help message. + nargs::range _nargs_range; ///< The argument's nargs range attribute value. [[no_unique_address]] value_arg_specific_type> _default_values; ///< The argument's default value list. diff --git a/include/argon/argument_base.hpp b/include/argon/argument_base.hpp index f48821fe..50c797fe 100644 --- a/include/argon/argument_base.hpp +++ b/include/argon/argument_base.hpp @@ -38,7 +38,7 @@ class argument_base { virtual const argument_name& name() const noexcept = 0; /// @return Returns the argument's help message. - virtual const std::optional& help() const noexcept = 0; + virtual const std::string& help() const noexcept = 0; /// @return `true` if the argument is hidden from help output, `false` otherwise. virtual bool is_hidden() const noexcept = 0; diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index 9985bf3e..8e6431de 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -38,10 +38,31 @@ namespace argon { * @endcode * Here `out_opts` is a mutually exclusive group, so using both arguments at the same time would cause an error. */ +// TODO: add attribute getters ??? class argument_group { public: argument_group() = delete; + /// @return The name of the argument group. + [[nodiscard]] const std::string& name() const noexcept { + return this->_name; + } + + /** + * @brief Set the help message for the argument group. + * @param help_msg The help message to set. + * @return Reference to the argument group instance. + */ + argument_group& help(std::string_view help_msg) noexcept { + this->_help_msg = help_msg; + return *this; + } + + /// @return The help message of the argument group. + [[nodiscard]] const std::string& help() const noexcept { + return this->_help_msg; + } + /** * @brief Set the `hidden` attribute of the group. * @@ -56,6 +77,11 @@ class argument_group { return *this; } + /// @return `true` if the group is hidden from the help output, `false` otherwise. + [[nodiscard]] bool is_hidden() const noexcept { + return this->_hidden; + } + /** * @brief Set the `required` attribute of the group. * @@ -71,6 +97,11 @@ class argument_group { return *this; } + /// @return `true` if the group is required, `false` otherwise. + [[nodiscard]] bool is_required() const noexcept { + return this->_required; + } + /** * @brief Set the `mutually_exclusive` attribute of the group. * @@ -86,6 +117,11 @@ class argument_group { return *this; } + /// @return `true` if the group is mutually exclusive, `false` otherwise. + [[nodiscard]] bool is_mutually_exclusive() const noexcept { + return this->_mutually_exclusive; + } + /** * @brief Set the `prefix` attribute of the group. * @@ -99,6 +135,11 @@ class argument_group { return *this; } + /// @return The common prefix applied to arguments in this group. + [[nodiscard]] const std::string& prefix() const noexcept { + return this->_prefix; + } + /** * @brief Set the `suffix` attribute of the group. * @@ -112,6 +153,11 @@ class argument_group { return *this; } + /// @return The common suffix applied to arguments in this group. + [[nodiscard]] const std::string& suffix() const noexcept { + return this->_suffix; + } + // --- argument value and state getters --- [[nodiscard]] bool is_used(std::string_view arg_base_name) const noexcept; @@ -156,11 +202,17 @@ class argument_group { return std::format("{}{}{}", this->_prefix, arg_base_name, this->_suffix); } + // --- element references --- + argument_parser* _parser; ///< Pointer to the owning parser. + arg_ptr_vec_t _arguments = {}; ///< A list of arguments that belong to this group. + + // --- attributes --- + std::string _name; ///< Name of the group (used in help output). std::string _prefix = ""; ///< Common argument flag prefix. std::string _suffix = ""; ///< Common argument flag suffix. - arg_ptr_vec_t _arguments = {}; ///< A list of arguments that belong to this group. + std::string _help_msg = ""; ///< The argument's help message. bool _hidden : 1 = false; ///< The hidden attribute value (default: false). bool _required : 1 = false; ///< The required attribute value (default: false). diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 29575d60..322068c0 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -154,8 +154,8 @@ void add_default_argument(const default_argument, argument_parser&) noexcept; * int main(int argc, char* argv[]) { * // Create the argument parser instance * argon::argument_parser parser("fcopy"); - * parser.program_version({ .major = 1, .minor = 0, .patch = 0 }) - * .program_description("A simple file copy utility.") + * parser.version({ .major = 1, .minor = 0, .patch = 0 }) + * .description("A simple file copy utility.") * .default_arguments( * argon::default_argument::o_help, * argon::default_argument::o_input, @@ -216,8 +216,8 @@ class argument_parser { * @param version The version of the program. * @return Reference to the argument parser. */ - argument_parser& program_version(const version& version) noexcept { - this->_program_version.emplace(version.str()); + argument_parser& version(const version& version) noexcept { + this->_version.emplace(version.str()); return *this; } @@ -226,18 +226,18 @@ class argument_parser { * @param version The version of the program. * @return Reference to the argument parser. */ - argument_parser& program_version(std::string_view version) { + argument_parser& version(std::string_view version) { if (util::contains_whitespaces(version)) throw invalid_configuration("The program version cannot contain whitespace characters!" ); - this->_program_version.emplace(version); + this->_version.emplace(version); return *this; } /// @return The program version if set, std::nullopt otherwise. - [[nodiscard]] const std::optional& program_version() const noexcept { - return this->_program_version; + [[nodiscard]] const std::optional& version() const noexcept { + return this->_version; } /** @@ -245,14 +245,14 @@ class argument_parser { * @param description The description of the program. * @return Reference to the argument parser. */ - argument_parser& program_description(std::string_view description) noexcept { - this->_program_description.emplace(description); + argument_parser& description(std::string_view description) noexcept { + this->_description = description; return *this; } - /// @return The program description if set, std::nullopt otherwise. - [[nodiscard]] const std::optional& program_description() const noexcept { - return this->_program_description; + /// @return The program description if set, empty string otherwise. + [[nodiscard]] const std::string& description() const noexcept { + return this->_description; } /** @@ -983,14 +983,12 @@ class argument_parser { */ void print_help(const bool verbose, std::ostream& os = std::cout) const noexcept { os << "Program: " << this->_program_name; - if (this->_program_version) - os << " (" << this->_program_version.value() << ')'; + if (this->_version) + os << " (" << this->_version.value() << ')'; os << '\n'; - if (this->_program_description) - os << '\n' - << std::string(this->_indent_width, ' ') << this->_program_description.value() - << '\n'; + if (not this->_description.empty()) + os << '\n' << std::string(this->_indent_width, ' ') << this->_description << '\n'; this->_print_subparsers(os); for (const auto& group : this->_argument_groups) @@ -1005,7 +1003,7 @@ class argument_parser { * @param os The output stream. */ void print_version(std::ostream& os = std::cout) const noexcept { - os << this->_program_name << " : version " << this->_program_version.value_or("unspecified") + os << this->_program_name << " : version " << this->_version.value_or("unspecified") << std::endl; } @@ -1569,7 +1567,7 @@ class argument_parser { builders.reserve(this->_subparsers.size()); for (const auto& subparser : this->_subparsers) - builders.emplace_back(subparser->_name, subparser->_program_description); + builders.emplace_back(subparser->_name, subparser->_description); std::size_t max_subparser_name_length = 0ull; for (const auto& bld : builders) @@ -1605,6 +1603,9 @@ class argument_parser { os << " (" << util::join(group_attrs) << ')'; os << '\n'; + if (not group._help_msg.empty()) + os << '\n' << std::string(this->_indent_width, ' ') << group._help_msg << '\n'; + if (verbose) { for (const auto& arg : visible_args) os << '\n' << arg->help_builder(verbose).get(this->_indent_width) << '\n'; @@ -1632,8 +1633,9 @@ class argument_parser { std::string _name = ""; std::string _program_name = ""; // The name of the program in the format "... ". - std::optional _program_version = std::nullopt; - std::optional _program_description = std::nullopt; + std::string _description = ""; + std::optional _version = std::nullopt; + unknown_policy _unknown_policy = unknown_policy::fail; char _flag_char = '-'; diff --git a/include/argon/detail/help_builder.hpp b/include/argon/detail/help_builder.hpp index 779f71d0..8a45575c 100644 --- a/include/argon/detail/help_builder.hpp +++ b/include/argon/detail/help_builder.hpp @@ -33,10 +33,9 @@ class help_builder { public: /** * @param name The string representation of the argument's name. - * @param help An optional help message string. + * @param help A help message string. */ - help_builder(const std::string& name, const std::optional& help = std::nullopt) - : name(name), help(help) {} + help_builder(const std::string& name, const std::string& help = "") : name(name), help(help) {} /** * @brief Adds a parameter descriptor with the given string value. @@ -93,8 +92,8 @@ class help_builder { oss << std::setw(static_cast(align_to.value())) << std::left; oss << this->name; - if (this->help.has_value()) - oss << " : " << this->help.value(); + if (not this->help.empty()) + oss << " : " << this->help; return oss.str(); } @@ -135,7 +134,7 @@ class help_builder { } std::string name; - std::optional help; + std::string help; std::vector params; private: diff --git a/tests/source/test_argument_parser_cfg.cpp b/tests/source/test_argument_parser_cfg.cpp index 7c61ae84..bff3615a 100644 --- a/tests/source/test_argument_parser_cfg.cpp +++ b/tests/source/test_argument_parser_cfg.cpp @@ -47,56 +47,52 @@ TEST_CASE_FIXTURE( CHECK_EQ(subparser.program_name(), std::format("{} {}", sut.name(), subparser_name)); } -// --- program_version --- +// --- version --- -TEST_CASE_FIXTURE( - test_argument_parser_cfg, "program_version() getter should return nullopt by default" -) { - CHECK_FALSE(sut.program_version()); +TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() getter should return nullopt by default") { + CHECK_FALSE(sut.version()); } TEST_CASE_FIXTURE( test_argument_parser_cfg, - "program_version() setter should throw if the version string contains whitespaces" + "version() setter should throw if the version string contains whitespaces" ) { CHECK_THROWS_WITH_AS( - sut.program_version("invalid version"), + sut.version("invalid version"), "The program version cannot contain whitespace characters!", invalid_configuration ); } -TEST_CASE_FIXTURE( - test_argument_parser_cfg, "program_version() setter should update the version member" -) { - sut.program_version(test_version); - auto stored_program_version = sut.program_version(); +TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() setter should update the version member") { + sut.version(test_version); + auto stored_program_version = sut.version(); REQUIRE(stored_program_version); CHECK_EQ(stored_program_version.value(), test_version.str()); - sut.program_version(test_str_version); - stored_program_version = sut.program_version(); + sut.version(test_str_version); + stored_program_version = sut.version(); REQUIRE(stored_program_version); CHECK_EQ(stored_program_version.value(), test_str_version); } -// --- program_description --- +// --- description --- TEST_CASE_FIXTURE( - test_argument_parser_cfg, "program_description() getter should return nullopt by default" + test_argument_parser_cfg, "description() getter should return an empty string by default" ) { - CHECK_FALSE(sut.program_description()); + CHECK(sut.description().empty()); } TEST_CASE_FIXTURE( - test_argument_parser_cfg, "program_description() setter should update the description member" + test_argument_parser_cfg, "description() setter should update the description member" ) { - sut.program_description(test_description); + sut.description(test_description); - const auto stored_program_description = sut.program_description(); + const auto stored_description = sut.description(); - REQUIRE(stored_program_description); - CHECK_EQ(stored_program_description.value(), test_description); + REQUIRE_FALSE(stored_description.empty()); + CHECK_EQ(stored_description, test_description); } // --- is_verbose --- diff --git a/tests/source/test_help_builder.cpp b/tests/source/test_help_builder.cpp index 245b5b82..6bf2c11b 100644 --- a/tests/source/test_help_builder.cpp +++ b/tests/source/test_help_builder.cpp @@ -12,6 +12,7 @@ namespace { const std::string arg_name = "test-arg"; const std::string help_msg = "test-arg help message"; +const std::string empty_msg = ""; constexpr uint8_t indent_width = 2; constexpr std::size_t align_to = 15ull; @@ -19,21 +20,21 @@ constexpr std::size_t align_to = 15ull; } // namespace TEST_CASE("help_builder should construct with name and optional help correctly") { - sut_type no_help(arg_name, std::nullopt); + sut_type no_help(arg_name, empty_msg); CHECK_EQ(no_help.name, arg_name); - CHECK_FALSE(no_help.help.has_value()); + CHECK(no_help.help.empty()); sut_type with_help(arg_name, help_msg); CHECK_EQ(with_help.name, arg_name); - CHECK(with_help.help.has_value()); - CHECK_EQ(with_help.help.value(), help_msg); + CHECK_FALSE(with_help.help.empty()); + CHECK_EQ(with_help.help, help_msg); } TEST_CASE("add_param should add string parameters correctly") { const std::string param_name = "param"; const std::string param_value = "value"; - sut_type sut(arg_name, std::nullopt); + sut_type sut(arg_name, empty_msg); sut.add_param(param_name, param_value); REQUIRE_EQ(sut.params.size(), 1ull); @@ -51,7 +52,7 @@ TEST_CASE("add_param should add writable parameters correctly") { const std::string double_param = "double-param"; const double double_value = 3.14; - sut_type sut(arg_name, std::nullopt); + sut_type sut(arg_name, empty_msg); sut.add_param(bool_param, bool_value); sut.add_param(int_param, int_value); @@ -71,7 +72,7 @@ TEST_CASE("add_range_param should properly add a joined range parameter") { const std::vector values = {1, 2, 3}; const std::string_view delimiter = "; "; - sut_type sut("range_test", std::nullopt); + sut_type sut("range_test", empty_msg); sut.add_range_param(param_name, values, delimiter); REQUIRE(sut.params.size() == 1); @@ -80,7 +81,7 @@ TEST_CASE("add_range_param should properly add a joined range parameter") { } TEST_CASE("get_basic should return only the argument name if no help is provided") { - sut_type sut(arg_name, std::nullopt); + sut_type sut(arg_name, empty_msg); std::string basic = sut.get_basic(indent_width); CHECK_EQ(basic, std::string(indent_width, ' ') + arg_name); diff --git a/tests/source/test_optional_argument.cpp b/tests/source/test_optional_argument.cpp index b284b142..59be36a3 100644 --- a/tests/source/test_optional_argument.cpp +++ b/tests/source/test_optional_argument.cpp @@ -62,19 +62,15 @@ TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argume } } -TEST_CASE_FIXTURE(argument_test_fixture, "help() should return nullopt by default") { +TEST_CASE_FIXTURE(argument_test_fixture, "help() should return an empty string by default") { const auto sut = sut_type(arg_name_primary); - CHECK_FALSE(sut.help()); + CHECK(sut.help().empty()); } TEST_CASE_FIXTURE(argument_test_fixture, "help() should return a massage set for the argument") { auto sut = sut_type(arg_name_primary); sut.help(help_msg); - - const auto stored_help_msg = sut.help(); - - REQUIRE(stored_help_msg); - CHECK_EQ(stored_help_msg, help_msg); + CHECK_EQ(sut.help(), help_msg); } TEST_CASE_FIXTURE( @@ -87,15 +83,14 @@ TEST_CASE_FIXTURE( auto bld = get_help_builder(sut, verbose); REQUIRE_EQ(bld.name, sut.name().str()); - CHECK_FALSE(bld.help); + CHECK(bld.help.empty()); CHECK(bld.params.empty()); // with a help msg sut.help(help_msg); bld = get_help_builder(sut, verbose); REQUIRE_EQ(bld.name, sut.name().str()); - CHECK(bld.help); - CHECK_EQ(bld.help.value(), help_msg); + CHECK_EQ(bld.help, help_msg); CHECK(bld.params.empty()); } @@ -109,15 +104,14 @@ TEST_CASE_FIXTURE( auto bld = get_help_builder(sut, verbose); REQUIRE_EQ(bld.name, sut.name().str()); - CHECK_FALSE(bld.help); + CHECK(bld.help.empty()); CHECK(bld.params.empty()); // with a help msg sut.help(help_msg); bld = get_help_builder(sut, verbose); - REQUIRE(bld.help); - CHECK_EQ(bld.help.value(), help_msg); + CHECK_EQ(bld.help, help_msg); CHECK(bld.params.empty()); // with the required flag enabled diff --git a/tests/source/test_positional_argument.cpp b/tests/source/test_positional_argument.cpp index ccb3f7d3..3be16663 100644 --- a/tests/source/test_positional_argument.cpp +++ b/tests/source/test_positional_argument.cpp @@ -40,19 +40,15 @@ TEST_CASE_FIXTURE(argument_test_fixture, "name() should return the proper argume CHECK_EQ(sut.name(), arg_name); } -TEST_CASE_FIXTURE(argument_test_fixture, "help() should return nullopt by default") { +TEST_CASE_FIXTURE(argument_test_fixture, "help() should return an empty string by default") { const auto sut = sut_type(arg_name); - CHECK_FALSE(sut.help()); + CHECK(sut.help().empty()); } TEST_CASE_FIXTURE(argument_test_fixture, "help() should return a massage set for the argument") { auto sut = sut_type(arg_name); sut.help(help_msg); - - const auto stored_help_msg = sut.help(); - - REQUIRE(stored_help_msg); - CHECK_EQ(stored_help_msg, help_msg); + CHECK_EQ(sut.help(), help_msg); } TEST_CASE_FIXTURE( @@ -65,15 +61,14 @@ TEST_CASE_FIXTURE( auto bld = get_help_builder(sut, verbose); REQUIRE_EQ(bld.name, sut.name().str()); - CHECK_FALSE(bld.help); + CHECK(bld.help.empty()); CHECK(bld.params.empty()); // with a help msg sut.help(help_msg); bld = get_help_builder(sut, verbose); REQUIRE_EQ(bld.name, sut.name().str()); - CHECK(bld.help); - CHECK_EQ(bld.help.value(), help_msg); + CHECK_EQ(bld.help, help_msg); CHECK(bld.params.empty()); } @@ -87,15 +82,14 @@ TEST_CASE_FIXTURE( auto bld = get_help_builder(sut, verbose); REQUIRE_EQ(bld.name, sut.name().str()); - CHECK_FALSE(bld.help); + CHECK(bld.help.empty()); CHECK(bld.params.empty()); // with a help msg sut.help(help_msg); bld = get_help_builder(sut, verbose); - REQUIRE(bld.help); - CHECK_EQ(bld.help.value(), help_msg); + CHECK_EQ(bld.help, help_msg); CHECK(bld.params.empty()); // other parameters From 611e43239a340381cc6dfb7916116b5b576ff160 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 26 Sep 2026 17:57:38 +0200 Subject: [PATCH 2/6] gr.help -> gr.description --- include/argon/argument_group.hpp | 16 ++++++++-------- include/argon/argument_parser.hpp | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index 8e6431de..d5911986 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -49,18 +49,18 @@ class argument_group { } /** - * @brief Set the help message for the argument group. - * @param help_msg The help message to set. + * @brief Set the description for the argument group. + * @param description The description to set. * @return Reference to the argument group instance. */ - argument_group& help(std::string_view help_msg) noexcept { - this->_help_msg = help_msg; + argument_group& description(std::string_view description) noexcept { + this->_description = description; return *this; } - /// @return The help message of the argument group. - [[nodiscard]] const std::string& help() const noexcept { - return this->_help_msg; + /// @return The description of the argument group. + [[nodiscard]] const std::string& description() const noexcept { + return this->_description; } /** @@ -212,7 +212,7 @@ class argument_group { std::string _name; ///< Name of the group (used in help output). std::string _prefix = ""; ///< Common argument flag prefix. std::string _suffix = ""; ///< Common argument flag suffix. - std::string _help_msg = ""; ///< The argument's help message. + std::string _description = ""; ///< The argument's help message. bool _hidden : 1 = false; ///< The hidden attribute value (default: false). bool _required : 1 = false; ///< The required attribute value (default: false). diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 322068c0..c53ee8f8 100644 --- a/include/argon/argument_parser.hpp +++ b/include/argon/argument_parser.hpp @@ -1603,8 +1603,8 @@ class argument_parser { os << " (" << util::join(group_attrs) << ')'; os << '\n'; - if (not group._help_msg.empty()) - os << '\n' << std::string(this->_indent_width, ' ') << group._help_msg << '\n'; + if (not group._description.empty()) + os << '\n' << std::string(this->_indent_width, ' ') << group._description << '\n'; if (verbose) { for (const auto& arg : visible_args) From 5382abcb29a725bb552c5eaeb8e1f3d2c11e748b Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 26 Sep 2026 17:59:19 +0200 Subject: [PATCH 3/6] tests --- .../source/test_argument_parser_args_cfg.cpp | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/source/test_argument_parser_args_cfg.cpp b/tests/source/test_argument_parser_args_cfg.cpp index 10c82c98..fb5feea4 100644 --- a/tests/source/test_argument_parser_args_cfg.cpp +++ b/tests/source/test_argument_parser_args_cfg.cpp @@ -473,6 +473,57 @@ TEST_CASE_FIXTURE( CHECK_NOTHROW(discard(const_sut.group(group_name))); } +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "argument_group attribute getters should return correct default values" +) { + const std::string group_name = "Test Group"; + const auto& group = sut.add_group(group_name); + + CHECK_EQ(group.name(), group_name); + CHECK(group.description().empty()); + CHECK_FALSE(group.is_hidden()); + CHECK_FALSE(group.is_required()); + CHECK_FALSE(group.is_mutually_exclusive()); + CHECK(group.prefix().empty()); + CHECK(group.suffix().empty()); +} + +TEST_CASE_FIXTURE( + test_argument_parser_args_cfg, + "argument_group setters should correctly update the group attributes" +) { + const std::string group_name = "Test Group"; + auto& group = sut.add_group(group_name); + + const std::string description = "This is a test group description."; + const std::string prefix = "pre-"; + const std::string suffix = "-suf"; + + // Set all attributes + group.description(description) + .hidden() + .required() + .mutually_exclusive() + .with_prefix(prefix) + .with_suffix(suffix); + + // Verify getters reflect the updated state + CHECK_EQ(group.description(), description); + CHECK(group.is_hidden()); + CHECK(group.is_required()); + CHECK(group.is_mutually_exclusive()); + CHECK_EQ(group.prefix(), prefix); + CHECK_EQ(group.suffix(), suffix); + + // Verify boolean setters can explicitly toggle flags off + group.hidden(false).required(false).mutually_exclusive(false); + + CHECK_FALSE(group.is_hidden()); + CHECK_FALSE(group.is_required()); + CHECK_FALSE(group.is_mutually_exclusive()); +} + // subparser getters TEST_CASE_FIXTURE( From bdc0e63ed814b4f9f3adea45cf854d0f303c26f7 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 26 Sep 2026 18:03:24 +0200 Subject: [PATCH 4/6] demo update --- cpp-argon-demo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp-argon-demo b/cpp-argon-demo index 90dd0c13..744dad3f 160000 --- a/cpp-argon-demo +++ b/cpp-argon-demo @@ -1 +1 @@ -Subproject commit 90dd0c13cf27ceef49b7288ba4f83879f3974c9c +Subproject commit 744dad3f46fa2bf7da94e5c6af8ef3c4d658b58a From 5e479262961f4f39c7b1755a28dd3f80d6242a0a Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 26 Sep 2026 18:07:05 +0200 Subject: [PATCH 5/6] docs + demo update --- cpp-argon-demo | 2 +- docs/tutorial.md | 40 ++++++++++++++++++++--- tests/source/test_argument_parser_cfg.cpp | 12 +++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/cpp-argon-demo b/cpp-argon-demo index 744dad3f..c0cdbaf3 160000 --- a/cpp-argon-demo +++ b/cpp-argon-demo @@ -1 +1 @@ -Subproject commit 744dad3f46fa2bf7da94e5c6af8ef3c4d658b58a +Subproject commit c0cdbaf36fd344a7d44523dd2a171ebd3365f17e diff --git a/docs/tutorial.md b/docs/tutorial.md index 253868ae..e49a1f9d 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -34,7 +34,9 @@ - [Group Attributes](#group-attributes) - [Validation Rules](#validation-rules) - [Naming Modifiers](#naming-modifiers) + - [Description](#description) - [Visibility](#visibility) + - [Inspecting Group Attributes](#inspecting-group-attributes) - [Complete Example](#complete-example) - [Suppressing Argument Group Checks](#suppressing-argument-group-checks) - [Parsing Arguments](#parsing-arguments) @@ -914,12 +916,12 @@ 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, modifies their arguments' names, or handles their visibility in the help output: +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 and descriptions 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. +* `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 (i.e., a group can require that exactly one argument is chosen). @@ -951,6 +953,15 @@ net_opts.add_optional_argument("port"); // Registered in the parser as "net-port > > 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). +#### Description + +You can attach a text description to an argument group to provide extra context to the user. This description will be printed immediately underneath the group's name and requirements in the generated help text. + +```cpp +auto& auth_opts = parser.add_group("Authentication") + .description("Provide credentials to access the remote server."); +``` + #### Visibility * `hidden()` – If this option is set, the entire group (including all of its visible arguments) will be hidden from the program's help description. @@ -962,6 +973,22 @@ 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 printed in the parser's help output. +### Inspecting Group Attributes + +If you need to dynamically query a group's configuration at runtime (e.g., when generating a GUI wrapper or a shell auto-completion script), you can use the group's attribute getters: + +```cpp +const std::string& name = group.name(); +const std::string& desc = group.description(); + +bool is_hidden = group.is_hidden(); +bool is_required = group.is_required(); +bool is_mutex = group.is_mutually_exclusive(); + +const std::string& prefix = group.prefix(); +const std::string& suffix = group.suffix(); +``` + ### Complete Example Below is a small program that demonstrates how to use a mutually exclusive group of required arguments: @@ -975,6 +1002,7 @@ int main(int argc, char* argv[]) { // create the argument group auto& out_opts = parser.add_group("Output Options") + .description("Select exactly one destination for the output.") .with_prefix("out-") .required() .mutually_exclusive(); @@ -993,7 +1021,7 @@ int main(int argc, char* argv[]) { } ``` -When invoked with the `--help` flag, the above program produces a help message that clearly shows the group and its rules: +When invoked with the `--help` flag, the above program produces a help message that clearly shows the group, its description, and its rules: ``` Program: myprog @@ -1004,13 +1032,15 @@ Optional Arguments: Output Options: (required, mutually exclusive) + Select exactly one destination for the output. + --out-file, -out-f : Print output to a given file --out-console, -out-c : Print output to the console ``` ### Suppressing Argument Group Checks -Similarly to [suppressing argument checks](#4-suppress_arg_checks---using-a-suppressing-argument-results-in-suppressing-requirement-checks-for-other-arguments), an argument can suppress the requirement checks of argument groups: +Similarly to [suppressing argument checks](#argument-checks-suppression---using-a-suppressing-argument-results-in-suppressing-requirement-checks-for-other-arguments), an argument can suppress the requirement checks of argument groups: ```c++ argument.suppress_group_checks(); diff --git a/tests/source/test_argument_parser_cfg.cpp b/tests/source/test_argument_parser_cfg.cpp index bff3615a..469fc7d0 100644 --- a/tests/source/test_argument_parser_cfg.cpp +++ b/tests/source/test_argument_parser_cfg.cpp @@ -66,14 +66,14 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() setter should update the version member") { sut.version(test_version); - auto stored_program_version = sut.version(); - REQUIRE(stored_program_version); - CHECK_EQ(stored_program_version.value(), test_version.str()); + auto stored_version = sut.version(); + REQUIRE(stored_version); + CHECK_EQ(stored_version.value(), test_version.str()); sut.version(test_str_version); - stored_program_version = sut.version(); - REQUIRE(stored_program_version); - CHECK_EQ(stored_program_version.value(), test_str_version); + stored_version = sut.version(); + REQUIRE(stored_version); + CHECK_EQ(stored_version.value(), test_str_version); } // --- description --- From a2db0fa9a4dd39e01c022684535cd54d31a4b13f Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 26 Sep 2026 19:00:40 +0200 Subject: [PATCH 6/6] resovled comments --- README.md | 1 + include/argon/argument_group.hpp | 203 ++++++++++++++++--------------- 2 files changed, 106 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index d2c2a55e..0353a242 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ - [Creating New Groups](/docs/tutorial.md#creating-new-groups) - [Adding Arguments to Groups](/docs/tutorial.md#adding-arguments-to-groups) - [Group Attributes](/docs/tutorial.md#group-attributes) + - [Inspecting Group Attributes](/docs/tutorial.md#inspecting-group-attributes) - [Complete Example](/docs/tutorial.md#complete-example) - [Suppressing Argument Group Checks](/docs/tutorial.md#suppressing-argument-group-checks) - [Parsing Arguments](/docs/tutorial.md#parsing-arguments) diff --git a/include/argon/argument_group.hpp b/include/argon/argument_group.hpp index d5911986..32ee7b38 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -12,33 +12,30 @@ namespace argon { -/** - * @brief Represents a group of arguments. - * - * Groups allow arguments to be organized under a dedicated section in the parser's help message. - * - * A group can be marked as: - * - required: **at least one** argument from the group must be used in the command-line - * - mutually exclusive: **at most one** argument from the group can be used in the command-line - * - * @note - This class is not intended to be constructed directly, but rather through the `add_group` method of @ref argon::argument_parser. - * @note - User defined groups may contain only optional arguments (and flags). - * - * Example usage: - * @code{.cpp} - * argon::argument_parser parser("myprog"); - * auto& out_opts = parser.add_group("Output Options").mutually_exclusive(); - * - * group.add_optional_argument(out_opts, "output", "o") - * .nargs(1) - * .help("Print output to the given file"); - * - * group.add_optional_argument(out_opts, "print", "p") - * .help("Print output to the console"); - * @endcode - * Here `out_opts` is a mutually exclusive group, so using both arguments at the same time would cause an error. - */ -// TODO: add attribute getters ??? +/// @brief Represents a group of arguments. +/// +/// Groups allow arguments to be organized under a dedicated section in the parser's help message. +/// +/// A group can be marked as: +/// - required: **at least one** argument from the group must be used in the command-line +/// - mutually exclusive: **at most one** argument from the group can be used in the command-line +/// +/// @note - This class is not intended to be constructed directly, but rather through the `add_group` method of @ref argon::argument_parser. +/// @note - User defined groups may contain only optional arguments (and flags). +/// +/// Example usage: +/// @code{.cpp} +/// argon::argument_parser parser("myprog"); +/// auto& out_opts = parser.add_group("Output Options").mutually_exclusive(); +/// +/// group.add_optional_argument(out_opts, "output", "o") +/// .nargs(1) +/// .help("Print output to the given file"); +/// +/// group.add_optional_argument(out_opts, "print", "p") +/// .help("Print output to the console"); +/// @endcode +/// Here `out_opts` is a mutually exclusive group, so using both arguments at the same time would cause an error. class argument_group { public: argument_group() = delete; @@ -48,11 +45,9 @@ class argument_group { return this->_name; } - /** - * @brief Set the description for the argument group. - * @param description The description to set. - * @return Reference to the argument group instance. - */ + /// @brief Set the description for the argument group. + /// @param description The description to set. + /// @return Reference to the argument group instance. argument_group& description(std::string_view description) noexcept { this->_description = description; return *this; @@ -63,15 +58,13 @@ class argument_group { return this->_description; } - /** - * @brief Set the `hidden` attribute of the group. - * - * - If set to true, the group will be hidden from the help output. - * - Groups are NOT hidden by default. - * - * @param h The value to set for the attribute (default: true). - * @return Reference to the group instance. - */ + /// @brief Set the `hidden` attribute of the group. + /// + /// - If set to true, the group will be hidden from the help output. + /// - Groups are NOT hidden by default. + /// + /// @param h The value to set for the attribute (default: true). + /// @return Reference to the group instance. argument_group& hidden(const bool h = true) noexcept { this->_hidden = h; return *this; @@ -82,16 +75,14 @@ class argument_group { return this->_hidden; } - /** - * @brief Set the `required` attribute of the group. - * - * - If set to true, the parser will require at least one argument from the group to be used in the command-line. - * - If no arguments from the group are used, an exception will be thrown. - * - Argument groups are NOT required by default. - * - * @param r The value to set for the attribute (default: true). - * @return Reference to the group instance. - */ + /// @brief Set the `required` attribute of the group. + /// + /// - If set to true, the parser will require at least one argument from the group to be used in the command-line. + /// - If no arguments from the group are used, an exception will be thrown. + /// - Argument groups are NOT required by default. + /// + /// @param r The value to set for the attribute (default: true). + /// @return Reference to the group instance. argument_group& required(const bool r = true) noexcept { this->_required = r; return *this; @@ -102,16 +93,14 @@ class argument_group { return this->_required; } - /** - * @brief Set the `mutually_exclusive` attribute of the group. - * - * - If set to true, the parser will allow at most one argument from the group to be used in the command-line. - * - If more than one argument from the group is used, an exception will be thrown. - * - Argument groups are NOT mutually exclusive by default. - * - * @param me The value to set for the attribute (default: true). - * @return Reference to the group instance. - */ + /// @brief Set the `mutually_exclusive` attribute of the group. + /// + /// - If set to true, the parser will allow at most one argument from the group to be used in the command-line. + /// - If more than one argument from the group is used, an exception will be thrown. + /// - Argument groups are NOT mutually exclusive by default. + /// + /// @param me The value to set for the attribute (default: true). + /// @return Reference to the group instance. argument_group& mutually_exclusive(const bool me = true) noexcept { this->_mutually_exclusive = me; return *this; @@ -122,14 +111,12 @@ class argument_group { return this->_mutually_exclusive; } - /** - * @brief Set the `prefix` attribute of the group. - * - * If set, each argument added to the group will have the given value prepended to its name. - * - * @param prefix The value to set for the attribute. - * @return Reference to the group instance. - */ + /// @brief Set the `prefix` attribute of the group. + /// + /// If set, each argument added to the group will have the given value prepended to its name. + /// + /// @param prefix The value to set for the attribute. + /// @return Reference to the group instance. argument_group& with_prefix(std::string_view prefix) noexcept { this->_prefix = prefix; return *this; @@ -140,14 +127,12 @@ class argument_group { return this->_prefix; } - /** - * @brief Set the `suffix` attribute of the group. - * - * If set, each argument added to the group will have the given value appended to its name. - * - * @param suffix The value to set for the attribute. - * @return Reference to the group instance. - */ + /// @brief Set the `suffix` attribute of the group. + /// + /// If set, each argument added to the group will have the given value appended to its name. + /// + /// @param suffix The value to set for the attribute. + /// @return Reference to the group instance. argument_group& with_suffix(std::string_view suffix) noexcept { this->_suffix = suffix; return *this; @@ -160,64 +145,86 @@ class argument_group { // --- argument value and state getters --- + /// @brief Check if a specific argument in the group was used in the command-line. + /// @param arg_base_name The base name of the argument (without the group's prefix or suffix). + /// @return `true` if the argument was used on the command line, `false` otherwise. [[nodiscard]] bool is_used(std::string_view arg_base_name) const noexcept; + + /// @brief Get the usage count of the given argument in the group. + /// @param arg_base_name The base name of the argument (without the group's prefix or suffix). + /// @return The number of times the argument has been used. [[nodiscard]] std::size_t count(std::string_view arg_base_name) const noexcept; + + /// @brief Check if the given argument in the group has a value. + /// @param arg_base_name The base name of the argument (without the group's prefix or suffix). + /// @return `true` if the argument has a value, `false` otherwise. [[nodiscard]] bool has_value(std::string_view arg_base_name) const noexcept; + /// @brief Get the value of the given argument in the group. + /// @tparam T Type of the argument value. + /// @param arg_base_name The base name of the argument (without the group's prefix or suffix). + /// @return The value of the argument. + /// @throws argon::lookup_failure if the argument cannot be found. + /// @throws argon::type_error if the requested type does not match the argument's type. template [[nodiscard]] traits::argument_result_type value(std::string_view arg_base_name) const; + /// @brief Get the value of the given argument in the group, if it has any, or a fallback value if not. + /// @tparam T Type of the argument value. + /// @tparam U The fallback value type. + /// @param arg_base_name The base name of the argument (without the group's prefix or suffix). + /// @param fallback_value The fallback value to return if the argument has no values. + /// @return The value of the argument, or the fallback value. + /// @throws argon::lookup_failure if the argument cannot be found. + /// @throws argon::type_error if the requested type does not match the argument's type. template U> [[nodiscard]] T value_or(std::string_view arg_base_name, U&& fallback_value) const; + /// @brief Get all values of the given argument in the group. + /// @tparam T Type of the argument values. + /// @param arg_base_name The base name of the argument (without the group's prefix or suffix). + /// @return The values of the argument as a vector. + /// @throws argon::lookup_failure if the argument cannot be found. + /// @throws argon::type_error if the requested type does not match the argument's type. template [[nodiscard]] const std::vector& values(std::string_view arg_base_name) const; friend class argument_parser; private: - using arg_ptr_t = std::shared_ptr; ///< The argument pointer type alias. - using arg_ptr_vec_t = std::vector; ///< The argument pointer list type alias. - - /** - * @brief Factory method to create an argument group. - * @param parser The owning parser. - * @param name Name of the group. - */ + using arg_ptr_t = std::shared_ptr; + using arg_ptr_vec_t = std::vector; + static std::unique_ptr create(argument_parser& parser, std::string_view name) { return std::unique_ptr(new argument_group(parser, name)); } - /// @brief Construct a new argument group with the given name. argument_group(argument_parser& parser, const std::string_view name) : _parser(&parser), _name(name) {} - /// @brief Add a new argument to this group (called internally by parser). void _add_argument(arg_ptr_t arg) noexcept { this->_arguments.emplace_back(std::move(arg)); } - /// @brief Format the argument's base name by adding the group's prefix and suffix. [[nodiscard]] std::string _format_arg_name(std::string_view arg_base_name) const noexcept { return std::format("{}{}{}", this->_prefix, arg_base_name, this->_suffix); } // --- element references --- - argument_parser* _parser; ///< Pointer to the owning parser. - arg_ptr_vec_t _arguments = {}; ///< A list of arguments that belong to this group. + argument_parser* _parser; // The owning parser. + arg_ptr_vec_t _arguments = {}; // --- attributes --- - std::string _name; ///< Name of the group (used in help output). - std::string _prefix = ""; ///< Common argument flag prefix. - std::string _suffix = ""; ///< Common argument flag suffix. - std::string _description = ""; ///< The argument's help message. + std::string _name; + std::string _prefix = ""; + std::string _suffix = ""; + std::string _description = ""; - bool _hidden : 1 = false; ///< The hidden attribute value (default: false). - bool _required : 1 = false; ///< The required attribute value (default: false). - bool _mutually_exclusive : 1 = - false; ///< The mutually exclusive attribute value (default: false). + bool _hidden : 1 = false; + bool _required : 1 = false; + bool _mutually_exclusive : 1 = false; }; } // namespace argon