Commit d3505540e7d7b5384ce1d20b46b49ca7910e3708
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
Showing
6 changed files
with
74 additions
and
2 deletions
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, "SubcommandDefaults", "[creation]") { | @@ -455,6 +455,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { | ||
| 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, "SubcommandDefaults", "[creation]") { | @@ -474,6 +475,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { | ||
| 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, "SubcommandDefaults", "[creation]") { | @@ -494,6 +496,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { | ||
| 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("THelp: Basic", "[help]") { | @@ -26,6 +26,43 @@ TEST_CASE("THelp: Basic", "[help]") { | ||
| 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"); |