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,6 +1062,10 @@ option_groups. These are:
1062 - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first 1062 - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first
1063 unrecognized item. It is ideal for allowing your app or subcommand to be a 1063 unrecognized item. It is ideal for allowing your app or subcommand to be a
1064 "prefix" to calling another app. 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 - `.footer(message)`: Set text to appear at the bottom of the help string. 1069 - `.footer(message)`: Set text to appear at the bottom of the help string.
1066 - `.footer(std::string())`: Set a callback to generate a string that will appear 1070 - `.footer(std::string())`: Set a callback to generate a string that will appear
1067 at the end of the help string. 1071 at the end of the help string.
@@ -1356,8 +1360,9 @@ multiple calls or using `|` operations with the transform. @@ -1356,8 +1360,9 @@ multiple calls or using `|` operations with the transform.
1356 Many of the defaults for subcommands and even options are inherited from their 1360 Many of the defaults for subcommands and even options are inherited from their
1357 creators. The inherited default values for subcommands are `allow_extras`, 1361 creators. The inherited default values for subcommands are `allow_extras`,
1358 `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, 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 Options have defaults for `group`, `required`, `multi_option_policy`, 1367 Options have defaults for `group`, `required`, `multi_option_policy`,
1363 `ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To 1368 `ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To
include/CLI/App.hpp
@@ -150,6 +150,12 @@ class App { @@ -150,6 +150,12 @@ class App {
150 /// @name Help 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 /// Footer to put after all options in the help output INHERITABLE 159 /// Footer to put after all options in the help output INHERITABLE
154 std::string footer_{}; 160 std::string footer_{};
155 161
@@ -947,6 +953,16 @@ class App { @@ -947,6 +953,16 @@ class App {
947 /// @name Help 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 /// Set footer. 966 /// Set footer.
951 App *footer(std::string footer_string) { 967 App *footer(std::string footer_string) {
952 footer_ = std::move(footer_string); 968 footer_ = std::move(footer_string);
@@ -1055,6 +1071,11 @@ class App { @@ -1055,6 +1071,11 @@ class App {
1055 /// Get the group of this subcommand 1071 /// Get the group of this subcommand
1056 CLI11_NODISCARD const std::string &get_group() const { return group_; } 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 /// Generate and return the footer. 1079 /// Generate and return the footer.
1059 CLI11_NODISCARD std::string get_footer() const { 1080 CLI11_NODISCARD std::string get_footer() const {
1060 return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; 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,6 +46,7 @@ CLI11_INLINE App::App(std::string app_description, std::string app_name, App *pa
46 configurable_ = parent_->configurable_; 46 configurable_ = parent_->configurable_;
47 allow_windows_style_options_ = parent_->allow_windows_style_options_; 47 allow_windows_style_options_ = parent_->allow_windows_style_options_;
48 group_ = parent_->group_; 48 group_ = parent_->group_;
  49 + usage_ = parent_->usage_;
49 footer_ = parent_->footer_; 50 footer_ = parent_->footer_;
50 formatter_ = parent_->formatter_; 51 formatter_ = parent_->formatter_;
51 config_formatter_ = parent_->config_formatter_; 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,6 +91,11 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const {
91 } 91 }
92 92
93 CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const { 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 std::stringstream out; 99 std::stringstream out;
95 100
96 out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; 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,6 +455,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) {
455 CHECK(!app.get_configurable()); 455 CHECK(!app.get_configurable());
456 CHECK(!app.get_validate_positionals()); 456 CHECK(!app.get_validate_positionals());
457 457
  458 + CHECK(app.get_usage().empty());
458 CHECK(app.get_footer().empty()); 459 CHECK(app.get_footer().empty());
459 CHECK("Subcommands" == app.get_group()); 460 CHECK("Subcommands" == app.get_group());
460 CHECK(0u == app.get_require_subcommand_min()); 461 CHECK(0u == app.get_require_subcommand_min());
@@ -474,6 +475,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) { @@ -474,6 +475,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) {
474 475
475 app.fallthrough(); 476 app.fallthrough();
476 app.validate_positionals(); 477 app.validate_positionals();
  478 + app.usage("ussy");
477 app.footer("footy"); 479 app.footer("footy");
478 app.group("Stuff"); 480 app.group("Stuff");
479 app.require_subcommand(2, 3); 481 app.require_subcommand(2, 3);
@@ -494,6 +496,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) { @@ -494,6 +496,7 @@ TEST_CASE_METHOD(TApp, &quot;SubcommandDefaults&quot;, &quot;[creation]&quot;) {
494 CHECK(app2->get_fallthrough()); 496 CHECK(app2->get_fallthrough());
495 CHECK(app2->get_validate_positionals()); 497 CHECK(app2->get_validate_positionals());
496 CHECK(app2->get_configurable()); 498 CHECK(app2->get_configurable());
  499 + CHECK("ussy" == app2->get_usage());
497 CHECK("footy" == app2->get_footer()); 500 CHECK("footy" == app2->get_footer());
498 CHECK("Stuff" == app2->get_group()); 501 CHECK("Stuff" == app2->get_group());
499 CHECK(0u == app2->get_require_subcommand_min()); 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,6 +26,43 @@ TEST_CASE(&quot;THelp: Basic&quot;, &quot;[help]&quot;) {
26 CHECK_THAT(help, Contains("Usage:")); 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 TEST_CASE("THelp: Footer", "[help]") { 66 TEST_CASE("THelp: Footer", "[help]") {
30 CLI::App app{"My prog"}; 67 CLI::App app{"My prog"};
31 app.footer("Report bugs to bugs@example.com"); 68 app.footer("Report bugs to bugs@example.com");