From 42d7b7484f4d2c1dcbdd48eab7118d44f3d8b459 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Thu, 8 Feb 2024 19:21:11 +0100 Subject: [PATCH 1/2] [MISC] Use vector for arguments --- include/sharg/detail/format_parse.hpp | 7 +- include/sharg/parser.hpp | 122 ++++++++++++++------------ 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/include/sharg/detail/format_parse.hpp b/include/sharg/detail/format_parse.hpp index 3af883f9..5c3da2f2 100644 --- a/include/sharg/detail/format_parse.hpp +++ b/include/sharg/detail/format_parse.hpp @@ -57,13 +57,10 @@ class format_parse : public format_base ~format_parse() = default; //!< Defaulted. /*!\brief The constructor of the parse format. - * \param[in] argc_ The number of command line arguments. * \param[in] argv_ The command line arguments to parse. */ - format_parse(int const argc_, std::vector argv_) : argv{std::move(argv_)} - { - (void)argc_; - } + format_parse(std::vector argv_) : argv{std::move(argv_)} + {} //!\} /*!\brief Adds an sharg::detail::get_option call to be evaluated later on. diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 26169797..3d7a8726 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -165,8 +165,7 @@ class parser /*!\brief Initializes an sharg::parser object from the command line arguments. * * \param[in] app_name The name of the app that is displayed on the help page. - * \param[in] argc The number of command line arguments. - * \param[in] argv The command line arguments to parse. + * \param[in] arguments The command line arguments to parse. * \param[in] version_updates Notify users about version updates (default sharg::update_notifications::on). * \param[in] subcommands A list of subcommands (see \link subcommand_parse subcommand parsing \endlink). * @@ -182,12 +181,12 @@ class parser * \stableapi{Since version 1.0.} */ parser(std::string const & app_name, - int const argc, - char const * const * const argv, + std::vector const & arguments, update_notifications version_updates = update_notifications::on, std::vector subcommands = {}) : version_check_dev_decision{version_updates}, - subcommands{std::move(subcommands)} + subcommands{std::move(subcommands)}, + arguments{arguments} { for (auto & sub : this->subcommands) { @@ -200,9 +199,18 @@ class parser info.app_name = app_name; - init(argc, argv); + init(); } + //!\overload + parser(std::string const & app_name, + int const argc, + char const * const * const argv, + update_notifications version_updates = update_notifications::on, + std::vector subcommands = {}) : + parser{app_name, std::vector{argv, argv + argc}, version_updates, std::move(subcommands)} + {} + //!\brief The destructor. ~parser() { @@ -517,10 +525,10 @@ class parser if (std::find(used_option_ids.begin(), used_option_ids.end(), std::string{id}) == used_option_ids.end()) throw design_error{"You can only ask for option identifiers that you added with add_option() before."}; - // we only need to search for an option before the `end_of_options_indentifier` (`--`) - auto end_of_options = std::find(cmd_arguments.begin(), cmd_arguments.end(), end_of_options_indentifier); - auto option_it = detail::format_parse::find_option_id(cmd_arguments.begin(), end_of_options, short_or_long_id); - return option_it != end_of_options; + // we only need to search for an option before the `option_end_identifier` (`--`) + auto option_end = std::find(format_arguments.begin(), format_arguments.end(), option_end_identifier); + auto option_it = detail::format_parse::find_option_id(format_arguments.begin(), option_end, short_or_long_id); + return option_it != option_end; } //!\name Structuring the Help Page @@ -689,7 +697,7 @@ class parser std::regex app_name_regex{"^[a-zA-Z0-9_-]+$"}; //!\brief Signals the parser that no options follow this string but only positional arguments. - static constexpr std::string_view const end_of_options_indentifier{"--"}; + static constexpr std::string_view const option_end_identifier{"--"}; //!\brief Stores the sub-parser in case \link subcommand_parse subcommand parsing \endlink is enabled. std::unique_ptr sub_parser{nullptr}; @@ -717,26 +725,24 @@ class parser //!\brief List of option/flag identifiers that are already used. std::set used_option_ids{"h", "hh", "help", "advanced-help", "export-help", "version", "copyright"}; - //!\brief The command line arguments. - std::vector cmd_arguments{}; + //!\brief The command line arguments that will be passed to the format. + std::vector format_arguments{}; + + //!\brief The original command line arguments. + std::vector arguments{}; //!\brief The command that lead to calling this parser, e.g. [./build/bin/raptor, build] std::vector executable_name{}; /*!\brief Initializes the sharg::parser class on construction. - * - * \param[in] argc The number of command line arguments. - * \param[in] argv The command line arguments. - * * \throws sharg::too_few_arguments if option --export-help was specified without a value * \throws sharg::too_few_arguments if option --version-check was specified without a value * \throws sharg::validation_error if the value passed to option --export-help was invalid. * \throws sharg::validation_error if the value passed to option --version-check was invalid. * \throws sharg::too_few_arguments if a sub parser was configured at construction but a subcommand is missing. - * * \details * - * This function adds all command line parameters to the cmd_arguments member variable + * This function adds all command line parameters to the format_arguments member variable * to take advantage of the vector functionality later on. Additionally, * the format member variable is set, depending on which parameters are given * by the user: @@ -755,27 +761,34 @@ class parser * * If `--export-help` is specified with a value other than html, man, cwl or ctd, an sharg::parser_error is thrown. */ - void init(int argc, char const * const * const argv) + void init() { - assert(argc > 0); - executable_name.emplace_back(argv[0]); + assert(!arguments.empty()); + + executable_name.emplace_back(arguments[0]); bool special_format_was_set{false}; - for (int i = 1, argv_len = argc; i < argv_len; ++i) // start at 1 to skip binary name + // Helper function for going to the next argument. This makes it more obvious that we are + // incrementing `it` (version-check, and export-help). + auto go_to_next_arg = [this](auto & it, std::string_view message) -> auto + { + if (++it == arguments.end()) + throw too_few_arguments{message.data()}; + }; + + // start at 1 to skip binary name + for (auto it = ++arguments.begin(); it != arguments.end(); ++it) { - std::string_view arg{argv[i]}; + std::string_view arg{*it}; if (!subcommands.empty()) // this is a top_level parser { if (std::ranges::find(subcommands, arg) != subcommands.end()) // identified subparser { - // LCOV_EXCL_START sub_parser = std::make_unique(info.app_name + "-" + arg.data(), - argc - i, - argv + i, + std::vector{it, arguments.end()}, update_notifications::off); - // LCOV_EXCL_STOP // Add the original calls to the front, e.g. ["raptor"], // s.t. ["raptor", "build"] will be the list after constructing the subparser @@ -790,28 +803,38 @@ class parser // Flags starting with '-' are allowed for the top-level parser. // Otherwise, this is a wrongly spelled subcommand. The error will be thrown in parse(). if (!arg.empty() && arg[0] != '-') + { + format_arguments.emplace_back(arg); break; + } } } if (arg == "-h" || arg == "--help") { - format = detail::format_help{subcommands, version_check_dev_decision, false}; special_format_was_set = true; + format = detail::format_help{subcommands, version_check_dev_decision, false}; } else if (arg == "-hh" || arg == "--advanced-help") { - format = detail::format_help{subcommands, version_check_dev_decision, true}; special_format_was_set = true; + format = detail::format_help{subcommands, version_check_dev_decision, true}; } else if (arg == "--version") { + special_format_was_set = true; format = detail::format_version{}; + } + else if (arg == "--copyright") + { special_format_was_set = true; + format = detail::format_copyright{}; } else if (arg.substr(0, 13) == "--export-help") // --export-help=man is also allowed { - std::string export_format; + special_format_was_set = true; + + std::string_view export_format; if (arg.size() > 13) { @@ -819,9 +842,8 @@ class parser } else { - if (argv_len <= i + 1) - throw too_few_arguments{"Option --export-help must be followed by a value."}; - export_format = std::string{argv[i + 1]}; + go_to_next_arg(it, "Option --export-help must be followed by a value."); + export_format = *it; } if (export_format == "html") @@ -836,19 +858,11 @@ class parser throw validation_error{"Validation failed for option --export-help: " "Value must be one of " + detail::supported_exports + "."}; - special_format_was_set = true; - } - else if (arg == "--copyright") - { - format = detail::format_copyright{}; - special_format_was_set = true; } else if (arg == "--version-check") { - if (++i >= argv_len) - throw too_few_arguments{"Option --version-check must be followed by a value."}; - - arg = argv[i]; + go_to_next_arg(it, "Option --version-check must be followed by a value."); + arg = *it; if (arg == "1" || arg == "true") version_check_user_decision = true; @@ -856,26 +870,22 @@ class parser version_check_user_decision = false; else throw validation_error{"Value for option --version-check must be true (1) or false (0)."}; - - // in case --version-check is specified it shall not be passed to format_parse() - argc -= 2; } else { - cmd_arguments.emplace_back(arg); + format_arguments.emplace_back(arg); } } - // all special options have been identified, which might involve deleting them from argv (e.g. version-check) - // check if no actual options remain and then call the short help page. - if (argc <= 1) // no arguments provided - { - format = detail::format_short_help{}; + if (special_format_was_set) return; - } - if (!special_format_was_set) - format = detail::format_parse(argc, cmd_arguments); + // All special options have been handled. If there are no arguments left and we do not have a subparser, + // we call the short help. + if (format_arguments.empty() && !sub_parser) + format = detail::format_short_help{}; + else + format = detail::format_parse(format_arguments); } /*!\brief Checks whether the long identifier has already been used before. From bb67f43892e93218b0e02bb0b9dd19a5f2f1b6f4 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Tue, 13 Feb 2024 10:05:07 +0100 Subject: [PATCH 2/2] [MISC] Rename argv to arguments --- include/sharg/detail/format_parse.hpp | 88 +++++++++++++-------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/include/sharg/detail/format_parse.hpp b/include/sharg/detail/format_parse.hpp index 5c3da2f2..1b3d896c 100644 --- a/include/sharg/detail/format_parse.hpp +++ b/include/sharg/detail/format_parse.hpp @@ -38,7 +38,7 @@ namespace sharg::detail * -#. Positional Options (order within as specified by the developer) * * When parsing flags and options, the identifiers (and values) are removed from - * the vector format_parse::argv. That way, options that are specified multiple times, + * the vector format_parse::arguments. That way, options that are specified multiple times, * but are no container type, can be identified and an error is reported. * * \remark For a complete overview, take a look at \ref parser @@ -57,9 +57,9 @@ class format_parse : public format_base ~format_parse() = default; //!< Defaulted. /*!\brief The constructor of the parse format. - * \param[in] argv_ The command line arguments to parse. + * \param[in] cmd_arguments The command line arguments to parse. */ - format_parse(std::vector argv_) : argv{std::move(argv_)} + format_parse(std::vector cmd_arguments) : arguments{std::move(cmd_arguments)} {} //!\} @@ -105,7 +105,7 @@ class format_parse : public format_base //!\brief Initiates the actual command line parsing. void parse(parser_meta_data const & /*meta*/) { - end_of_options_it = std::find(argv.begin(), argv.end(), "--"); + end_of_options_it = std::find(arguments.begin(), arguments.end(), "--"); // parse options first, because we need to rule out -keyValue pairs // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags @@ -117,7 +117,7 @@ class format_parse : public format_base check_for_unknown_ids(); - if (end_of_options_it != argv.end()) + if (end_of_options_it != arguments.end()) *end_of_options_it = ""; // remove -- before parsing positional arguments for (auto && f : positional_option_calls) @@ -148,7 +148,7 @@ class format_parse : public format_base return id == '\0'; } - /*!\brief Finds the position of a short/long identifier in format_parse::argv. + /*!\brief Finds the position of a short/long identifier in format_parse::arguments. * \tparam iterator_type The type of iterator that defines the range to search in. * \tparam id_type The identifier type; must be either of type `char` if it denotes a short identifier or * std::string if it denotes a long identifier. @@ -162,11 +162,11 @@ class format_parse : public format_base * * **Valid short-id value pairs are: `-iValue`, `-i=Value`, or `-i Value`** * If the `id` passed to this function is of type `char`, it is assumed to be a short identifier. - * The `id` is found by comparing the prefix of every argument in argv to the `id` prepended with a single `-`. + * The `id` is found by comparing the prefix of every argument in arguments to the `id` prepended with a single `-`. * * **Valid long id value pairs are: `--id=Value`, `--id Value`**. * If the `id` passed to this function is of type `std::string`, it is assumed to be a long identifier. - * The `id` is found by comparing every argument in argv to `id` prepended with two dashes (`--`) + * The `id` is found by comparing every argument in arguments to `id` prepended with two dashes (`--`) * or a prefix of such followed by the equal sign `=`. */ template @@ -239,12 +239,12 @@ class format_parse : public format_base return prepend_dash(short_id) + "/" + prepend_dash(long_id); } - /*!\brief Returns true and removes the long identifier if it is in format_parse::argv. + /*!\brief Returns true and removes the long identifier if it is in format_parse::arguments. * \param[in] long_id The long identifier of the flag to check. */ bool flag_is_set(std::string const & long_id) { - auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id)); + auto it = std::find(arguments.begin(), end_of_options_it, prepend_dash(long_id)); if (it != end_of_options_it) *it = ""; // remove seen flag @@ -252,13 +252,13 @@ class format_parse : public format_base return (it != end_of_options_it); } - /*!\brief Returns true and removes the short identifier if it is in format_parse::argv. + /*!\brief Returns true and removes the short identifier if it is in format_parse::arguments. * \param[in] short_id The short identifier of the flag to check. */ bool flag_is_set(char const short_id) { // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v) - for (std::string & arg : argv) + for (std::string & arg : arguments) { if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option { @@ -473,7 +473,7 @@ class format_parse : public format_base /*!\brief Handles value retrieval for options based on different key-value pairs. * - * \param[out] value Stores the value found in argv, parsed by parse_option_value. + * \param[out] value Stores the value found in arguments, parsed by parse_option_value. * \param[in] option_it The iterator where the option identifier was found. * \param[in] id The option identifier supplied on the command line. * @@ -533,16 +533,16 @@ class format_parse : public format_base /*!\brief Handles value retrieval (non container type) options. * - * \param[out] value Stores the value found in argv, parsed by parse_option_value. + * \param[out] value Stores the value found in arguments, parsed by parse_option_value. * \param[in] id The option identifier supplied on the command line. * * \throws sharg::option_declared_multiple_times * * \details * - * If the option identifier is found in format_parse::argv, the value of - * the following position in argv is tried to be parsed given the respective option value type - * and the identifier and value argument are removed from argv. + * If the option identifier is found in format_parse::arguments, the value of + * the following position in arguments is tried to be parsed given the respective option value type + * and the identifier and value argument are removed from arguments. * * Returns true on success and false otherwise. This is needed to catch * the user error of supplying multiple arguments for the same @@ -551,7 +551,7 @@ class format_parse : public format_base template bool get_option_by_id(option_type & value, id_type const & id) { - auto it = find_option_id(argv.begin(), end_of_options_it, id); + auto it = find_option_id(arguments.begin(), end_of_options_it, id); if (it != end_of_options_it) identify_and_retrieve_option_value(value, it, id); @@ -565,7 +565,7 @@ class format_parse : public format_base /*!\brief Handles value retrieval (container type) options. * - * \param[out] value Stores all values found in argv, parsed by parse_option_value. + * \param[out] value Stores all values found in arguments, parsed by parse_option_value. * \param[in] id The option identifier supplied on the command line. * * \details @@ -577,7 +577,7 @@ class format_parse : public format_base template bool get_option_by_id(option_type & value, id_type const & id) { - auto it = find_option_id(argv.begin(), end_of_options_it, id); + auto it = find_option_id(arguments.begin(), end_of_options_it, id); bool seen_at_least_once{it != end_of_options_it}; if (seen_at_least_once) @@ -592,22 +592,22 @@ class format_parse : public format_base return seen_at_least_once; } - /*!\brief Checks format_parse::argv for unknown options/flags. + /*!\brief Checks format_parse::arguments for unknown options/flags. * * \throws sharg::unknown_option * * \details * * This function is used by format_parse::parse() AFTER all flags and options - * specified by the developer were parsed and therefore removed from argv. + * specified by the developer were parsed and therefore removed from arguments. * Thus, all remaining flags/options are unknown. * - * In addition this function removes "--" (if specified) from argv to - * clean argv for positional option retrieval. + * In addition this function removes "--" (if specified) from arguments to + * clean arguments for positional option retrieval. */ void check_for_unknown_ids() { - for (auto it = argv.begin(); it != end_of_options_it; ++it) + for (auto it = arguments.begin(); it != end_of_options_it; ++it) { std::string arg{*it}; if (!arg.empty() && arg[0] == '-') // may be an identifier @@ -634,7 +634,7 @@ class format_parse : public format_base } } - /*!\brief Checks format_parse::argv for unknown options/flags. + /*!\brief Checks format_parse::arguments for unknown options/flags. * * \throws sharg::too_many_arguments * @@ -642,18 +642,18 @@ class format_parse : public format_base * * This function is used by format_parse::parse() AFTER all flags, options * and positional options specified by the developer were parsed and - * therefore removed from argv. + * therefore removed from arguments. * Thus, all remaining non-empty arguments are too much. */ void check_for_left_over_args() { - if (std::find_if(argv.begin(), - argv.end(), + if (std::find_if(arguments.begin(), + arguments.end(), [](std::string const & s) { return (s != ""); }) - != argv.end()) + != arguments.end()) throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information."); } @@ -731,27 +731,27 @@ class format_parse : public format_base * \details * * This function assumes that - * -#) argv has been stripped from all known options and flags - * -#) argv has been checked for unknown options - * -#) argv does not contain "--" anymore - * Thus we can simply iterate over non empty entries of argv. + * -#) arguments has been stripped from all known options and flags + * -#) arguments has been checked for unknown options + * -#) arguments does not contain "--" anymore + * Thus we can simply iterate over non empty entries of arguments. * * This function * - checks if the user did not provide enough arguments, - * - retrieves the next (no container type) or all (container type) remaining non empty value/s in argv + * - retrieves the next (no container type) or all (container type) remaining non empty value/s in arguments */ template void get_positional_option(option_type & value, validator_type && validator) { ++positional_option_count; - auto it = std::find_if(argv.begin(), - argv.end(), + auto it = std::find_if(arguments.begin(), + arguments.end(), [](std::string const & s) { return (s != ""); }); - if (it == argv.end()) + if (it == arguments.end()) throw too_few_arguments("Not enough positional arguments provided (Need at least " + std::to_string(positional_option_calls.size()) + "). See -h/--help for more information."); @@ -763,15 +763,15 @@ class format_parse : public format_base value.clear(); - while (it != argv.end()) + while (it != arguments.end()) { auto res = parse_option_value(value, *it); std::string id = "positional option" + std::to_string(positional_option_count); throw_on_input_error(res, id, *it); - *it = ""; // remove arg from argv + *it = ""; // remove arg from arguments it = std::find_if(it, - argv.end(), + arguments.end(), [](std::string const & s) { return (s != ""); @@ -785,7 +785,7 @@ class format_parse : public format_base std::string id = "positional option" + std::to_string(positional_option_count); throw_on_input_error(res, id, *it); - *it = ""; // remove arg from argv + *it = ""; // remove arg from arguments } try @@ -808,8 +808,8 @@ class format_parse : public format_base //!\brief Keeps track of the number of specified positional options. unsigned positional_option_count{0}; //!\brief Vector of command line arguments. - std::vector argv; - //!\brief Artificial end of argv if \-- was seen. + std::vector arguments; + //!\brief Artificial end of arguments if \-- was seen. std::vector::iterator end_of_options_it; };