Skip to content

Commit fc26a68

Browse files
committed
[MISC] Allow recursive subcommands
1 parent d48c51f commit fc26a68

1 file changed

Lines changed: 94 additions & 19 deletions

File tree

include/sharg/parser.hpp

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -186,21 +186,13 @@ class parser
186186
char const * const * const argv,
187187
update_notifications version_updates = update_notifications::on,
188188
std::vector<std::string> subcommands = {}) :
189-
version_check_dev_decision{version_updates},
190-
subcommands{std::move(subcommands)}
189+
argc{argc},
190+
argv{argv},
191+
version_check_dev_decision{version_updates}
191192
{
192-
for (auto & sub : this->subcommands)
193-
{
194-
if (!std::regex_match(sub, app_name_regex))
195-
{
196-
throw design_error{"The subcommand name must only contain alpha-numeric characters or '_' and '-' "
197-
"(regex: \"^[a-zA-Z0-9_-]+$\")."};
198-
}
199-
}
200-
201193
info.app_name = app_name;
202194

203-
init(argc, argv);
195+
add_subcommands(std::move(subcommands));
204196
}
205197

206198
//!\brief The destructor.
@@ -666,13 +658,92 @@ class parser
666658
*/
667659
parser_meta_data info;
668660

661+
/*!\brief Adds subcommands to the parser.
662+
* \param[in] subcommands A list of subcommands.
663+
* \throws sharg::design_error if the subcommand name contains illegal characters.
664+
*/
665+
void add_subcommands(std::vector<std::string> const & subcommands)
666+
{
667+
for (auto const & sub : subcommands)
668+
{
669+
if (!std::regex_match(sub, app_name_regex))
670+
{
671+
std::string const error_message =
672+
detail::to_string(std::quoted(info.app_name),
673+
" contains an invalid subcommand name: ",
674+
std::quoted(sub),
675+
". The subcommand name must only contain alpha-numeric characters ",
676+
"or '_' and '-' (regex: \"^[a-zA-Z0-9_-]+$\").");
677+
throw design_error{error_message};
678+
};
679+
}
680+
681+
auto & parser_subcommands = this->subcommands;
682+
683+
#if 0
684+
auto sorted = subcommands;
685+
std::ranges::sort(sorted);
686+
std::vector<std::string> difference{};
687+
std::ranges::set_intersection(sorted, std::ranges::unique(sorted), std::back_inserter(difference));
688+
if (!difference.empty())
689+
{
690+
auto view = std::views::transform(difference,
691+
[](auto const & val)
692+
{
693+
return std::quoted(val);
694+
});
695+
std::string const error_message =
696+
detail::to_string("add_subcommands()'s arguments list contains duplicate elements for ",
697+
std::quoted(info.app_name),
698+
" : ",
699+
view,
700+
".");
701+
throw design_error{error_message};
702+
}
703+
704+
auto duplicate_subcommands = std::ranges::search(parser_subcommands, subcommands);
705+
if (duplicate_subcommands)
706+
{
707+
auto view = std::views::transform(duplicate_subcommands,
708+
[](auto const & val)
709+
{
710+
return std::quoted(val);
711+
});
712+
std::string const error_message =
713+
detail::to_string(std::quoted(info.app_name), " already contains subcommands: ", view, ".");
714+
throw design_error{error_message};
715+
}
716+
#endif
717+
718+
#ifdef __cpp_lib_containers_ranges
719+
parser_subcommands.append_range(subcommands);
720+
#else
721+
parser_subcommands.insert(parser_subcommands.end(), subcommands.cbegin(), subcommands.cend());
722+
#endif
723+
724+
std::ranges::sort(parser_subcommands);
725+
auto const [first, last] = std::ranges::unique(parser_subcommands);
726+
parser_subcommands.erase(first, last);
727+
728+
init();
729+
}
730+
669731
private:
670732
//!\brief Keeps track of whether the parse function has been called already.
671733
bool parse_was_called{false};
672734

735+
//!\brief Keeps track of whether the init function has been called already.
736+
bool init_was_called{false};
737+
673738
//!\brief Keeps track of whether the user has added a positional list option to check if this was the very last.
674739
bool has_positional_list_option{false};
675740

741+
//!\brief The number of command line arguments.
742+
int argc{};
743+
744+
//!\brief The command line arguments.
745+
char const * const * argv{nullptr};
746+
676747
//!\brief Set on construction and indicates whether the developer deactivates the version check calls completely.
677748
update_notifications version_check_dev_decision{};
678749

@@ -724,16 +795,11 @@ class parser
724795
std::vector<std::string> executable_name{};
725796

726797
/*!\brief Initializes the sharg::parser class on construction.
727-
*
728-
* \param[in] argc The number of command line arguments.
729-
* \param[in] argv The command line arguments.
730-
*
731798
* \throws sharg::too_few_arguments if option --export-help was specified without a value
732799
* \throws sharg::too_few_arguments if option --version-check was specified without a value
733800
* \throws sharg::validation_error if the value passed to option --export-help was invalid.
734801
* \throws sharg::validation_error if the value passed to option --version-check was invalid.
735802
* \throws sharg::too_few_arguments if a sub parser was configured at construction but a subcommand is missing.
736-
*
737803
* \details
738804
*
739805
* This function adds all command line parameters to the cmd_arguments member variable
@@ -755,10 +821,19 @@ class parser
755821
*
756822
* If `--export-help` is specified with a value other than html, man, cwl or ctd, an sharg::parser_error is thrown.
757823
*/
758-
void init(int argc, char const * const * const argv)
824+
void init()
759825
{
760826
assert(argc > 0);
761-
executable_name.emplace_back(argv[0]);
827+
828+
if (init_was_called)
829+
{
830+
cmd_arguments.clear();
831+
}
832+
else
833+
{
834+
executable_name.emplace_back(argv[0]);
835+
init_was_called = true;
836+
}
762837

763838
bool special_format_was_set{false};
764839

0 commit comments

Comments
 (0)