From 0aed6de8b294ab5800d26065d2ffbb4754e3fc34 Mon Sep 17 00:00:00 2001 From: Enrico Seiler Date: Thu, 8 Feb 2024 17:59:58 +0100 Subject: [PATCH] [MISC] Allow recursive subcommands --- include/sharg/parser.hpp | 104 +++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 14 deletions(-) diff --git a/include/sharg/parser.hpp b/include/sharg/parser.hpp index 26169797..7361a937 100644 --- a/include/sharg/parser.hpp +++ b/include/sharg/parser.hpp @@ -186,21 +186,13 @@ class parser char const * const * const argv, update_notifications version_updates = update_notifications::on, std::vector subcommands = {}) : - version_check_dev_decision{version_updates}, - subcommands{std::move(subcommands)} + argc{argc}, + argv{argv}, + version_check_dev_decision{version_updates} { - for (auto & sub : this->subcommands) - { - if (!std::regex_match(sub, app_name_regex)) - { - throw design_error{"The subcommand name must only contain alpha-numeric characters or '_' and '-' " - "(regex: \"^[a-zA-Z0-9_-]+$\")."}; - } - } - info.app_name = app_name; - init(argc, argv); + add_subcommands(std::move(subcommands)); } //!\brief The destructor. @@ -666,13 +658,88 @@ class parser */ parser_meta_data info; + void add_subcommands(std::vector const & subcommands) + { + for (auto const & sub : subcommands) + { + if (!std::regex_match(sub, app_name_regex)) + { + std::string const error_message = + detail::to_string(std::quoted(info.app_name), + " contains an invalid subcommand name: ", + std::quoted(sub), + ". The subcommand name must only contain alpha-numeric characters ", + "or '_' and '-' (regex: \"^[a-zA-Z0-9_-]+$\")."); + throw design_error{error_message}; + }; + } + + auto & parser_subcommands = this->subcommands; + +#if 0 + auto sorted = subcommands; + std::ranges::sort(sorted); + std::vector difference{}; + std::ranges::set_intersection(sorted, std::ranges::unique(sorted), std::back_inserter(difference)); + if (!difference.empty()) + { + auto view = std::views::transform(difference, + [](auto const & val) + { + return std::quoted(val); + }); + std::string const error_message = + detail::to_string("add_subcommands()'s arguments list contains duplicate elements for ", + std::quoted(info.app_name), + " : ", + view, + "."); + throw design_error{error_message}; + } + + auto duplicate_subcommands = std::ranges::search(parser_subcommands, subcommands); + if (duplicate_subcommands) + { + auto view = std::views::transform(duplicate_subcommands, + [](auto const & val) + { + return std::quoted(val); + }); + std::string const error_message = + detail::to_string(std::quoted(info.app_name), " already contains subcommands: ", view, "."); + throw design_error{error_message}; + } +#endif + +#ifdef __cpp_lib_containers_ranges + parser_subcommands.append_range(subcommands); +#else + parser_subcommands.insert(parser_subcommands.end(), subcommands.cbegin(), subcommands.cend()); +#endif + + std::ranges::sort(parser_subcommands); + auto const [first, last] = std::ranges::unique(parser_subcommands); + parser_subcommands.erase(first, last); + + init(); + } + private: //!\brief Keeps track of whether the parse function has been called already. bool parse_was_called{false}; + //!\brief Keeps track of whether the init function has been called already. + bool init_was_called{false}; + //!\brief Keeps track of whether the user has added a positional list option to check if this was the very last. bool has_positional_list_option{false}; + //!\brief The number of command line arguments. + int argc{}; + + //!\brief The command line arguments. + char const * const * argv{nullptr}; + //!\brief Set on construction and indicates whether the developer deactivates the version check calls completely. update_notifications version_check_dev_decision{}; @@ -755,10 +822,19 @@ 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]); + + if (init_was_called) + { + cmd_arguments.clear(); + } + else + { + executable_name.emplace_back(argv[0]); + init_was_called = true; + } bool special_format_was_set{false};