Skip to content

Commit

Permalink
[MISC] Allow recursive subcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
eseiler committed Feb 8, 2024
1 parent d48c51f commit fc26a68
Showing 1 changed file with 94 additions and 19 deletions.
113 changes: 94 additions & 19 deletions include/sharg/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,21 +186,13 @@ class parser
char const * const * const argv,
update_notifications version_updates = update_notifications::on,
std::vector<std::string> 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.
Expand Down Expand Up @@ -666,13 +658,92 @@ class parser
*/
parser_meta_data info;

/*!\brief Adds subcommands to the parser.
* \param[in] subcommands A list of subcommands.
* \throws sharg::design_error if the subcommand name contains illegal characters.
*/
void add_subcommands(std::vector<std::string> 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<std::string> 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{};

Expand Down Expand Up @@ -724,16 +795,11 @@ class parser
std::vector<std::string> 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
Expand All @@ -755,10 +821,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();

Check warning on line 830 in include/sharg/parser.hpp

View check run for this annotation

Codecov / codecov/patch

include/sharg/parser.hpp#L830

Added line #L830 was not covered by tests
}
else
{
executable_name.emplace_back(argv[0]);
init_was_called = true;
}

bool special_format_was_set{false};

Expand Down

0 comments on commit fc26a68

Please sign in to comment.