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/cpp-argon-demo b/cpp-argon-demo index 90dd0c13..c0cdbaf3 160000 --- a/cpp-argon-demo +++ b/cpp-argon-demo @@ -1 +1 @@ -Subproject commit 90dd0c13cf27ceef49b7288ba4f83879f3974c9c +Subproject commit c0cdbaf36fd344a7d44523dd2a171ebd3365f17e diff --git a/docs/tutorial.md b/docs/tutorial.md index 54893d77..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) @@ -150,8 +152,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 +170,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 +311,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") @@ -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(); @@ -1073,8 +1103,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 +1265,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 +1647,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 +1667,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..32ee7b38 100644 --- a/include/argon/argument_group.hpp +++ b/include/argon/argument_group.hpp @@ -12,160 +12,219 @@ 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. - */ +/// @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; - /** - * @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. - */ + /// @return The name of the argument group. + [[nodiscard]] const std::string& name() const noexcept { + return this->_name; + } + + /// @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; + } + + /// @return The description of the argument group. + [[nodiscard]] const std::string& description() const noexcept { + 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. argument_group& hidden(const bool h = true) noexcept { this->_hidden = h; return *this; } - /** - * @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. - */ + /// @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. + /// + /// - 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; } - /** - * @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. - */ + /// @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. + /// + /// - 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; } - /** - * @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. - */ + /// @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. + /// + /// If set, each argument added to the group will have the given value prepended to its name. + /// + /// @param prefix The value to set for the attribute. + /// @return Reference to the group instance. argument_group& with_prefix(std::string_view prefix) noexcept { this->_prefix = prefix; return *this; } - /** - * @brief Set the `suffix` attribute of the group. - * - * If set, each argument added to the group will have the given value appended to its name. - * - * @param suffix The value to set for the attribute. - * @return Reference to the group instance. - */ + /// @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. + /// + /// 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; } + /// @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 --- + /// @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); } - argument_parser* _parser; ///< Pointer to the owning parser. - std::string _name; ///< Name of the group (used in help output). - std::string _prefix = ""; ///< Common argument flag prefix. - std::string _suffix = ""; ///< Common argument flag suffix. - arg_ptr_vec_t _arguments = {}; ///< A list of arguments that belong to this group. + // --- element references --- + + argument_parser* _parser; // The owning parser. + arg_ptr_vec_t _arguments = {}; + + // --- attributes --- + + 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 diff --git a/include/argon/argument_parser.hpp b/include/argon/argument_parser.hpp index 29575d60..c53ee8f8 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._description.empty()) + os << '\n' << std::string(this->_indent_width, ' ') << group._description << '\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_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( diff --git a/tests/source/test_argument_parser_cfg.cpp b/tests/source/test_argument_parser_cfg.cpp index 7c61ae84..469fc7d0 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(); - REQUIRE(stored_program_version); - CHECK_EQ(stored_program_version.value(), test_version.str()); +TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() setter should update the version member") { + sut.version(test_version); + auto stored_version = sut.version(); + REQUIRE(stored_version); + CHECK_EQ(stored_version.value(), test_version.str()); - sut.program_version(test_str_version); - stored_program_version = sut.program_version(); - REQUIRE(stored_program_version); - CHECK_EQ(stored_program_version.value(), test_str_version); + sut.version(test_str_version); + stored_version = sut.version(); + REQUIRE(stored_version); + CHECK_EQ(stored_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