diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index e55dee79..46fb6504 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -121,6 +121,9 @@ class App { /// -1 for 1 or more, 0 for not required, # for exact number required int require_subcommand_ = 0; + /// The group membership + std::string group_{"Subcommands"}; + ///@} /// @name Config ///@{ @@ -212,6 +215,15 @@ class App { return this; } + /// Changes the group membership + App *group(std::string name) { + group_ = name; + return this; + } + + /// Get the group of this subcommand + const std::string &get_group() const { return group_; } + ///@} /// @name Adding options ///@{ @@ -845,9 +857,18 @@ class App { // Subcommands if(!subcommands_.empty()) { - out << std::endl << "Subcommands:" << std::endl; - for(const App_p &com : subcommands_) - detail::format_help(out, com->get_name(), com->description_, wid); + std::set subcmd_groups_seen; + for(const App_p &com : subcommands_) { + const std::string &group_key = detail::to_lower(com->get_group()); + if(group_key == "hidden" || subcmd_groups_seen.count(group_key) != 0) + continue; + + subcmd_groups_seen.insert(group_key); + out << std::endl << com->get_group() << ":" << std::endl; + for(const App_p &com : subcommands_) + if(detail::to_lower(com->get_group()) == group_key) + detail::format_help(out, com->get_name(), com->description_, wid); + } } if(!footer_.empty()) { diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index 920b5b8b..1fb7f924 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -1,5 +1,11 @@ #include "app_helper.hpp" +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +using ::testing::HasSubstr; +using ::testing::Not; + TEST_F(TApp, BasicSubcommands) { auto sub1 = app.add_subcommand("sub1"); auto sub2 = app.add_subcommand("sub2"); @@ -482,3 +488,21 @@ TEST_F(SubcommandProgram, Callbacks) { EXPECT_THROW(run(), CLI::Success); } + +TEST_F(SubcommandProgram, Groups) { + + std::string help = app.help(); + EXPECT_THAT(help, Not(HasSubstr("More Commands:"))); + EXPECT_THAT(help, HasSubstr("Subcommands:")); + + start->group("More Commands"); + help = app.help(); + EXPECT_THAT(help, HasSubstr("More Commands:")); + EXPECT_THAT(help, HasSubstr("Subcommands:")); + + // Case is ignored but for the first subcommand in a group. + stop->group("more commands"); + help = app.help(); + EXPECT_THAT(help, HasSubstr("More Commands:")); + EXPECT_THAT(help, Not(HasSubstr("Subcommands:"))); +}