Commit d3505540e7d7b5384ce1d20b46b49ca7910e3708

Authored by polistern
Committed by GitHub
1 parent 291c5878

feat: added usage message replacement feature (#786)

* fix: show newline before footer only if footer is set and not empty

* feat: added usage message replacement feature

* fix: tests corrected for new help message formatting
README.md
... ... @@ -1062,6 +1062,10 @@ option_groups. These are:
1062 1062 - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first
1063 1063 unrecognized item. It is ideal for allowing your app or subcommand to be a
1064 1064 "prefix" to calling another app.
  1065 +- `.usage(message)`: Replace text to appear at the start of the help string
  1066 + after description.
  1067 +- `.usage(std::string())`: Set a callback to generate a string that will appear
  1068 + at the start of the help string after description.
1065 1069 - `.footer(message)`: Set text to appear at the bottom of the help string.
1066 1070 - `.footer(std::string())`: Set a callback to generate a string that will appear
1067 1071 at the end of the help string.
... ... @@ -1356,8 +1360,9 @@ multiple calls or using `|` operations with the transform.
1356 1360 Many of the defaults for subcommands and even options are inherited from their
1357 1361 creators. The inherited default values for subcommands are `allow_extras`,
1358 1362 `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`,
1359   -`footer`,`immediate_callback` and maximum number of required subcommands. The
1360   -help flag existence, name, and description are inherited, as well.
  1363 +`usage`, `footer`, `immediate_callback` and maximum number of required
  1364 +subcommands. The help flag existence, name, and description are inherited, as
  1365 +well.
1361 1366  
1362 1367 Options have defaults for `group`, `required`, `multi_option_policy`,
1363 1368 `ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To
... ...
include/CLI/App.hpp
... ... @@ -150,6 +150,12 @@ class App {
150 150 /// @name Help
151 151 ///@{
152 152  
  153 + /// Usage to put after program/subcommand description in the help output INHERITABLE
  154 + std::string usage_{};
  155 +
  156 + /// This is a function that generates a usage to put after program/subcommand description in help output
  157 + std::function<std::string()> usage_callback_{};
  158 +
153 159 /// Footer to put after all options in the help output INHERITABLE
154 160 std::string footer_{};
155 161  
... ... @@ -947,6 +953,16 @@ class App {
947 953 /// @name Help
948 954 ///@{
949 955  
  956 + /// Set usage.
  957 + App *usage(std::string usage_string) {
  958 + usage_ = std::move(usage_string);
  959 + return this;
  960 + }
  961 + /// Set usage.
  962 + App *usage(std::function<std::string()> usage_function) {
  963 + usage_callback_ = std::move(usage_function);
  964 + return this;
  965 + }
950 966 /// Set footer.
951 967 App *footer(std::string footer_string) {
952 968 footer_ = std::move(footer_string);
... ... @@ -1055,6 +1071,11 @@ class App {
1055 1071 /// Get the group of this subcommand
1056 1072 CLI11_NODISCARD const std::string &get_group() const { return group_; }
1057 1073  
  1074 + /// Generate and return the usage.
  1075 + CLI11_NODISCARD std::string get_usage() const {
  1076 + return (usage_callback_) ? usage_callback_() + '\n' + usage_ : usage_;
  1077 + }
  1078 +
1058 1079 /// Generate and return the footer.
1059 1080 CLI11_NODISCARD std::string get_footer() const {
1060 1081 return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_;
... ...
include/CLI/impl/App_inl.hpp
... ... @@ -46,6 +46,7 @@ CLI11_INLINE App::App(std::string app_description, std::string app_name, App *pa
46 46 configurable_ = parent_->configurable_;
47 47 allow_windows_style_options_ = parent_->allow_windows_style_options_;
48 48 group_ = parent_->group_;
  49 + usage_ = parent_->usage_;
49 50 footer_ = parent_->footer_;
50 51 formatter_ = parent_->formatter_;
51 52 config_formatter_ = parent_->config_formatter_;
... ...
include/CLI/impl/Formatter_inl.hpp
... ... @@ -91,6 +91,11 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const {
91 91 }
92 92  
93 93 CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const {
  94 + std::string usage = app->get_usage();
  95 + if(!usage.empty()) {
  96 + return usage + "\n";
  97 + }
  98 +
94 99 std::stringstream out;
95 100  
96 101 out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
... ...
tests/CreationTest.cpp
... ... @@ -455,6 +455,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) {
455 455 CHECK(!app.get_configurable());
456 456 CHECK(!app.get_validate_positionals());
457 457  
  458 + CHECK(app.get_usage().empty());
458 459 CHECK(app.get_footer().empty());
459 460 CHECK("Subcommands" == app.get_group());
460 461 CHECK(0u == app.get_require_subcommand_min());
... ... @@ -474,6 +475,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) {
474 475  
475 476 app.fallthrough();
476 477 app.validate_positionals();
  478 + app.usage("ussy");
477 479 app.footer("footy");
478 480 app.group("Stuff");
479 481 app.require_subcommand(2, 3);
... ... @@ -494,6 +496,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) {
494 496 CHECK(app2->get_fallthrough());
495 497 CHECK(app2->get_validate_positionals());
496 498 CHECK(app2->get_configurable());
  499 + CHECK("ussy" == app2->get_usage());
497 500 CHECK("footy" == app2->get_footer());
498 501 CHECK("Stuff" == app2->get_group());
499 502 CHECK(0u == app2->get_require_subcommand_min());
... ...
tests/HelpTest.cpp
... ... @@ -26,6 +26,43 @@ TEST_CASE(&quot;THelp: Basic&quot;, &quot;[help]&quot;) {
26 26 CHECK_THAT(help, Contains("Usage:"));
27 27 }
28 28  
  29 +TEST_CASE("THelp: Usage", "[help]") {
  30 + CLI::App app{"My prog"};
  31 + app.usage("use: just use it");
  32 +
  33 + std::string help = app.help();
  34 +
  35 + CHECK_THAT(help, Contains("My prog"));
  36 + CHECK_THAT(help, Contains("-h,--help"));
  37 + CHECK_THAT(help, Contains("Options:"));
  38 + CHECK_THAT(help, Contains("use: just use it"));
  39 +}
  40 +
  41 +TEST_CASE("THelp: UsageCallback", "[help]") {
  42 + CLI::App app{"My prog"};
  43 + app.usage([]() { return "use: just use it"; });
  44 +
  45 + std::string help = app.help();
  46 +
  47 + CHECK_THAT(help, Contains("My prog"));
  48 + CHECK_THAT(help, Contains("-h,--help"));
  49 + CHECK_THAT(help, Contains("Options:"));
  50 + CHECK_THAT(help, Contains("use: just use it"));
  51 +}
  52 +
  53 +TEST_CASE("THelp: UsageCallbackBoth", "[help]") {
  54 + CLI::App app{"My prog"};
  55 + app.usage([]() { return "use: just use it"; });
  56 + app.usage("like 1, 2, and 3");
  57 + std::string help = app.help();
  58 +
  59 + CHECK_THAT(help, Contains("My prog"));
  60 + CHECK_THAT(help, Contains("-h,--help"));
  61 + CHECK_THAT(help, Contains("Options:"));
  62 + CHECK_THAT(help, Contains("use: just use it"));
  63 + CHECK_THAT(help, Contains("like 1, 2, and 3"));
  64 +}
  65 +
29 66 TEST_CASE("THelp: Footer", "[help]") {
30 67 CLI::App app{"My prog"};
31 68 app.footer("Report bugs to bugs@example.com");
... ...