Commit 952f2913e3d9a0c2b93001153d13d5133c0874c0

Authored by Henry Fredrick Schreiner
Committed by Henry Schreiner
1 parent a061275e

First attempt at formatter (app and option)

in progress: formatters

Getting closer

Working on apps

One test actually runs

All builds, added filter functions

Reverting a few behavours as needed

Repairs

All tests pass

Fixing error with adding help flag

Labels are simpler mappings, normalized setters

Adding help_all

Adding a few more tests

One more line tested

Adding one more check

Adding to readme

Simplify naming

Adding default constructors

Fixing spacing issues with subcommand all printout

Adding a couple of tests
CHANGELOG.md
  1 +### Version 1.6: Formatters
  2 +
  3 +Added a new formatting system. You can now set the formatter on Apps and Options.
  4 +
  5 +* Added `AppFormatter` and `OptionFormatter`, and `formatter` slot
  6 +* Added `help_all` support (not added by default)
  7 +* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering
  8 +* Added `get_groups()` to get groups
  9 +* Added getters for the missing parts of options (help no longer uses any private parts)
  10 +
  11 +Changes to the help system (most normal users will not notice this):
  12 +
  13 +* Renamed `single_name` to `get_name(false, false)` (the default)
  14 +* The old `get_name()` is now `get_name(false, true)`
  15 +* The old `get_pname()` is now `get_name(true, false)`
  16 +* Removed `help_*` functions
  17 +* Protected function `_has_help_positional` removed
  18 +* `format_help` can now be chained
  19 +
  20 +
  21 +Other small changes:
  22 +
  23 +* Testing (only) now uses submodules.
  24 +* Removed `requires` in favor of `needs` (deprecated in last version)
  25 +* Better CMake policy handling
  26 +
1 ### Version 1.5.3: Compiler compatibility 27 ### Version 1.5.3: Compiler compatibility
2 This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported. 28 This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported.
3 29
README.md
@@ -193,6 +193,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t @@ -193,6 +193,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
193 * `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. 193 * `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
194 * `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options. 194 * `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
195 * `->configurable(false)`: Disable this option from being in an ini configuration file. 195 * `->configurable(false)`: Disable this option from being in an ini configuration file.
  196 +* `->formatter(fmt)`: Set the formatter, with signature `std::string(const Option*, OptionFormatMode)`. See Formatting for more details.
196 197
197 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. 198 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
198 199
@@ -247,6 +248,7 @@ There are several options that are supported on the main app and subcommands. Th @@ -247,6 +248,7 @@ There are several options that are supported on the main app and subcommands. Th
247 * `.get_parent()`: Get the parent App or nullptr if called on master App. 248 * `.get_parent()`: Get the parent App or nullptr if called on master App.
248 * `.get_options()`: Get the list of all defined option pointers (useful for processing the app for custom output formats). 249 * `.get_options()`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
249 * `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). 250 * `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates).
  251 +* `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details.
250 * `.get_description()`: Access the description. 252 * `.get_description()`: Access the description.
251 * `.parsed()`: True if this subcommand was given on the command line. 253 * `.parsed()`: True if this subcommand was given on the command line.
252 * `.set_name(name)`: Add or change the name. 254 * `.set_name(name)`: Add or change the name.
@@ -254,6 +256,8 @@ There are several options that are supported on the main app and subcommands. Th @@ -254,6 +256,8 @@ There are several options that are supported on the main app and subcommands. Th
254 * `.allow_extras()`: Do not throw an error if extra arguments are left over. 256 * `.allow_extras()`: Do not throw an error if extra arguments are left over.
255 * `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognised item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. 257 * `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognised item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
256 * `.set_footer(message)`: Set text to appear at the bottom of the help string. 258 * `.set_footer(message)`: Set text to appear at the bottom of the help string.
  259 +* `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option.
  260 +* `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands.
257 * `.set_failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). 261 * `.set_failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
258 * `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand. 262 * `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
259 263
@@ -300,11 +304,22 @@ app.option_defaults()-&gt;required(); @@ -300,11 +304,22 @@ app.option_defaults()-&gt;required();
300 304
301 The default settings for options are inherited to subcommands, as well. 305 The default settings for options are inherited to subcommands, as well.
302 306
  307 +## Formatting
  308 +
  309 +The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App` or `Option`.
  310 +CLI11 comes with a default App formatter functional, `AppFormatter`, and a default `OptionFormatter` as well. They are both customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
  311 +that too; you just need to implement the correct signature. The first argument is a const pointer to the App or Option in question. The App formatter will get a `std::string` usage name as the second option, and both end with with a mode. Both formatters return a `std::string`.
  312 +
  313 +The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the situation the help was called in. `Sub` is optional, but the default formatter uses it to make sure expanded subcommands are called with
  314 +their own formatter since you can't access anything but the call operator once a formatter has been set.
  315 +
  316 +The `OptionFormatMode` also has three values, depending on the calling situation: `Positional`, `Optional`, and `Usage`. The default formatter uses these to print out the option in each of these situations.
  317 +
303 ## Subclassing 318 ## Subclassing
304 319
305 -The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse 320 +The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can also set a help-all flag with `.set_help_all_flag(name, help_string)`; this will expand the subcommands (one level only). You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse
306 but before run behavior, while 321 but before run behavior, while
307 -still giving the user freedom to `set_callback` on the main app. 322 +still giving the user freedom to `set_callback` on the main app.
308 323
309 The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. 324 The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector.
310 325
examples/CMakeLists.txt
@@ -71,7 +71,7 @@ add_cli_exe(enum enum.cpp) @@ -71,7 +71,7 @@ add_cli_exe(enum enum.cpp)
71 add_test(NAME enum_pass COMMAND enum -l 1) 71 add_test(NAME enum_pass COMMAND enum -l 1)
72 add_test(NAME enum_fail COMMAND enum -l 4) 72 add_test(NAME enum_fail COMMAND enum -l 4)
73 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION 73 set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
74 - "Could not convert: -l,--level = 4") 74 + "Could not convert: --level = 4")
75 75
76 add_cli_exe(modhelp modhelp.cpp) 76 add_cli_exe(modhelp modhelp.cpp)
77 add_test(NAME modhelp COMMAND modhelp -a test -h) 77 add_test(NAME modhelp COMMAND modhelp -a test -h)
@@ -83,3 +83,5 @@ add_test(NAME subcom_in_files COMMAND subcommand_main subcommand_a -f this.txt - @@ -83,3 +83,5 @@ add_test(NAME subcom_in_files COMMAND subcommand_main subcommand_a -f this.txt -
83 set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION 83 set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION
84 "Working on file: this\.txt" 84 "Working on file: this\.txt"
85 "Using foo!") 85 "Using foo!")
  86 +
  87 +add_cli_exe(formatter formatter.cpp)
examples/formatter.cpp 0 โ†’ 100644
  1 +#include <CLI/CLI.hpp>
  2 +
  3 +class MyFormatter : public CLI::OptionFormatter {
  4 + public:
  5 + std::string make_opts(const CLI::Option *) const override { return " OPTION"; }
  6 +};
  7 +
  8 +int main(int argc, char **argv) {
  9 + CLI::App app;
  10 + app.set_help_all_flag("--help-all", "Show all help");
  11 +
  12 + app.option_defaults()->formatter(MyFormatter());
  13 +
  14 + CLI::AppFormatter fmt;
  15 + fmt.column_width(15);
  16 + app.formatter(fmt);
  17 +
  18 + app.add_flag("--flag", "This is a flag");
  19 +
  20 + auto sub1 = app.add_subcommand("one", "Description One");
  21 + sub1->add_flag("--oneflag", "Some flag");
  22 + auto sub2 = app.add_subcommand("two", "Description Two");
  23 + sub2->add_flag("--twoflag", "Some other flag");
  24 +
  25 + CLI11_PARSE(app, argc, argv);
  26 +
  27 + std::cout << "This app was meant to show off the formatter, run with -h" << std::endl;
  28 +
  29 + return 0;
  30 +}
examples/subcommands.cpp
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 int main(int argc, char **argv) { 3 int main(int argc, char **argv) {
4 4
5 CLI::App app("K3Pi goofit fitter"); 5 CLI::App app("K3Pi goofit fitter");
  6 + app.set_help_all_flag("--help-all", "Expand all help");
6 app.add_flag("--random", "Some random flag"); 7 app.add_flag("--random", "Some random flag");
7 CLI::App *start = app.add_subcommand("start", "A great subcommand"); 8 CLI::App *start = app.add_subcommand("start", "A great subcommand");
8 CLI::App *stop = app.add_subcommand("stop", "Do you really want to stop?"); 9 CLI::App *stop = app.add_subcommand("stop", "Do you really want to stop?");
include/CLI/App.hpp
@@ -24,6 +24,7 @@ @@ -24,6 +24,7 @@
24 #include "CLI/Split.hpp" 24 #include "CLI/Split.hpp"
25 #include "CLI/StringTools.hpp" 25 #include "CLI/StringTools.hpp"
26 #include "CLI/TypeTools.hpp" 26 #include "CLI/TypeTools.hpp"
  27 +#include "CLI/Formatter.hpp"
27 28
28 namespace CLI { 29 namespace CLI {
29 30
@@ -102,6 +103,12 @@ class App { @@ -102,6 +103,12 @@ class App {
102 /// A pointer to the help flag if there is one INHERITABLE 103 /// A pointer to the help flag if there is one INHERITABLE
103 Option *help_ptr_{nullptr}; 104 Option *help_ptr_{nullptr};
104 105
  106 + /// A pointer to the help all flag if there is one INHERITABLE
  107 + Option *help_all_ptr_{nullptr};
  108 +
  109 + /// This is the formatter for help printing. Default provided. INHERITABLE
  110 + std::function<std::string(const App *, std::string, AppFormatMode)> formatter_{AppFormatter()};
  111 +
105 /// The error message printing function INHERITABLE 112 /// The error message printing function INHERITABLE
106 std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple; 113 std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple;
107 114
@@ -141,7 +148,7 @@ class App { @@ -141,7 +148,7 @@ class App {
141 /// True if this command/subcommand was parsed 148 /// True if this command/subcommand was parsed
142 bool parsed_{false}; 149 bool parsed_{false};
143 150
144 - /// Minimum required subcommands 151 + /// Minimum required subcommands (not inheritable!)
145 size_t require_subcommand_min_ = 0; 152 size_t require_subcommand_min_ = 0;
146 153
147 /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE 154 /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE
@@ -171,7 +178,10 @@ class App { @@ -171,7 +178,10 @@ class App {
171 // Inherit if not from a nullptr 178 // Inherit if not from a nullptr
172 if(parent_ != nullptr) { 179 if(parent_ != nullptr) {
173 if(parent_->help_ptr_ != nullptr) 180 if(parent_->help_ptr_ != nullptr)
174 - set_help_flag(parent_->help_ptr_->get_name(), parent_->help_ptr_->get_description()); 181 + set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description());
  182 + if(parent_->help_all_ptr_ != nullptr)
  183 + set_help_all_flag(parent_->help_all_ptr_->get_name(false, true),
  184 + parent_->help_all_ptr_->get_description());
175 185
176 /// OptionDefaults 186 /// OptionDefaults
177 option_defaults_ = parent_->option_defaults_; 187 option_defaults_ = parent_->option_defaults_;
@@ -185,6 +195,7 @@ class App { @@ -185,6 +195,7 @@ class App {
185 fallthrough_ = parent_->fallthrough_; 195 fallthrough_ = parent_->fallthrough_;
186 group_ = parent_->group_; 196 group_ = parent_->group_;
187 footer_ = parent_->footer_; 197 footer_ = parent_->footer_;
  198 + formatter_ = parent_->formatter_;
188 require_subcommand_max_ = parent_->require_subcommand_max_; 199 require_subcommand_max_ = parent_->require_subcommand_max_;
189 } 200 }
190 } 201 }
@@ -250,6 +261,12 @@ class App { @@ -250,6 +261,12 @@ class App {
250 return this; 261 return this;
251 } 262 }
252 263
  264 + /// Set the help formatter
  265 + App *formatter(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) {
  266 + formatter_ = fmt;
  267 + return this;
  268 + }
  269 +
253 /// Check to see if this subcommand was parsed, true only if received on command line. 270 /// Check to see if this subcommand was parsed, true only if received on command line.
254 bool parsed() const { return parsed_; } 271 bool parsed() const { return parsed_; }
255 272
@@ -388,6 +405,22 @@ class App { @@ -388,6 +405,22 @@ class App {
388 return help_ptr_; 405 return help_ptr_;
389 } 406 }
390 407
  408 + /// Set a help all flag, replaced the existing one if present
  409 + Option *set_help_all_flag(std::string name = "", std::string description = "") {
  410 + if(help_all_ptr_ != nullptr) {
  411 + remove_option(help_all_ptr_);
  412 + help_all_ptr_ = nullptr;
  413 + }
  414 +
  415 + // Empty name will simply remove the help flag
  416 + if(!name.empty()) {
  417 + help_all_ptr_ = add_flag(name, description);
  418 + help_all_ptr_->configurable(false);
  419 + }
  420 +
  421 + return help_all_ptr_;
  422 + }
  423 +
391 /// Add option for flag 424 /// Add option for flag
392 Option *add_flag(std::string name, std::string description = "") { 425 Option *add_flag(std::string name, std::string description = "") {
393 CLI::callback_t fun = [](CLI::results_t) { return true; }; 426 CLI::callback_t fun = [](CLI::results_t) { return true; };
@@ -764,6 +797,11 @@ class App { @@ -764,6 +797,11 @@ class App {
764 return e.get_exit_code(); 797 return e.get_exit_code();
765 } 798 }
766 799
  800 + if(dynamic_cast<const CLI::CallForAllHelp *>(&e) != nullptr) {
  801 + out << help("", AppFormatMode::All);
  802 + return e.get_exit_code();
  803 + }
  804 +
767 if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) { 805 if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
768 if(failure_message_) 806 if(failure_message_)
769 err << failure_message_(this, e) << std::flush; 807 err << failure_message_(this, e) << std::flush;
@@ -801,18 +839,43 @@ class App { @@ -801,18 +839,43 @@ class App {
801 throw OptionNotFound(name); 839 throw OptionNotFound(name);
802 } 840 }
803 841
804 - /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command line  
805 - /// order)  
806 - std::vector<App *> get_subcommands(bool parsed = true) const {  
807 - if(parsed) {  
808 - return parsed_subcommands_;  
809 - } else {  
810 - std::vector<App *> subcomms(subcommands_.size());  
811 - std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {  
812 - return v.get();  
813 - });  
814 - return subcomms; 842 + /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command
  843 + /// line order; use parsed = false to get the original definition list.)
  844 + std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
  845 +
  846 + /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
  847 + /// subcommands (const)
  848 + std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const {
  849 + std::vector<const App *> subcomms(subcommands_.size());
  850 + std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
  851 + return v.get();
  852 + });
  853 +
  854 + if(filter) {
  855 + subcomms.erase(std::remove_if(std::begin(subcomms),
  856 + std::end(subcomms),
  857 + [&filter](const App *app) { return !filter(app); }),
  858 + std::end(subcomms));
  859 + }
  860 +
  861 + return subcomms;
  862 + }
  863 +
  864 + /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
  865 + /// subcommands
  866 + std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter) {
  867 + std::vector<App *> subcomms(subcommands_.size());
  868 + std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
  869 + return v.get();
  870 + });
  871 +
  872 + if(filter) {
  873 + subcomms.erase(
  874 + std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }),
  875 + std::end(subcomms));
815 } 876 }
  877 +
  878 + return subcomms;
816 } 879 }
817 880
818 /// Check to see if given subcommand was selected 881 /// Check to see if given subcommand was selected
@@ -885,105 +948,20 @@ class App { @@ -885,105 +948,20 @@ class App {
885 return out.str(); 948 return out.str();
886 } 949 }
887 950
888 - /// Makes a help message, with a column wid for column 1  
889 - std::string help(size_t wid = 30, std::string prev = "") const {  
890 - // Delegate to subcommand if needed 951 + /// Makes a help message, using the currently configured formatter
  952 + /// Will only do one subcommand at a time
  953 + std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const {
891 if(prev.empty()) 954 if(prev.empty())
892 - prev = name_; 955 + prev = get_name();
893 else 956 else
894 - prev += " " + name_; 957 + prev += " " + get_name();
895 958
  959 + // Delegate to subcommand if needed
896 auto selected_subcommands = get_subcommands(); 960 auto selected_subcommands = get_subcommands();
897 if(!selected_subcommands.empty()) 961 if(!selected_subcommands.empty())
898 - return selected_subcommands.at(0)->help(wid, prev);  
899 -  
900 - std::stringstream out;  
901 - out << description_ << std::endl;  
902 - out << "Usage:" << (prev.empty() ? "" : " ") << prev;  
903 -  
904 - // Check for options_  
905 - bool npos = false;  
906 - std::vector<std::string> groups;  
907 - for(const Option_p &opt : options_) {  
908 - if(opt->nonpositional()) {  
909 - npos = true;  
910 -  
911 - // Add group if it is not already in there  
912 - if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {  
913 - groups.push_back(opt->get_group());  
914 - }  
915 - }  
916 - }  
917 -  
918 - if(npos)  
919 - out << " [OPTIONS]";  
920 -  
921 - // Positionals  
922 - bool pos = false;  
923 - for(const Option_p &opt : options_)  
924 - if(opt->get_positional()) {  
925 - // A hidden positional should still show up in the usage statement  
926 - // if(detail::to_lower(opt->get_group()).empty())  
927 - // continue;  
928 - out << " " << opt->help_positional();  
929 - if(opt->_has_help_positional())  
930 - pos = true;  
931 - }  
932 -  
933 - if(!subcommands_.empty()) {  
934 - if(require_subcommand_min_ > 0)  
935 - out << " SUBCOMMAND";  
936 - else  
937 - out << " [SUBCOMMAND]";  
938 - }  
939 -  
940 - out << std::endl;  
941 -  
942 - // Positional descriptions  
943 - if(pos) {  
944 - out << std::endl << "Positionals:" << std::endl;  
945 - for(const Option_p &opt : options_) {  
946 - if(detail::to_lower(opt->get_group()).empty())  
947 - continue; // Hidden  
948 - if(opt->_has_help_positional())  
949 - detail::format_help(out, opt->help_pname(), opt->get_description(), wid);  
950 - }  
951 - }  
952 -  
953 - // Options  
954 - if(npos) {  
955 - for(const std::string &group : groups) {  
956 - if(detail::to_lower(group).empty())  
957 - continue; // Hidden  
958 - out << std::endl << group << ":" << std::endl;  
959 - for(const Option_p &opt : options_) {  
960 - if(opt->nonpositional() && opt->get_group() == group)  
961 - detail::format_help(out, opt->help_name(true), opt->get_description(), wid);  
962 - }  
963 - }  
964 - }  
965 -  
966 - // Subcommands  
967 - if(!subcommands_.empty()) {  
968 - std::set<std::string> subcmd_groups_seen;  
969 - for(const App_p &com : subcommands_) {  
970 - const std::string &group_key = detail::to_lower(com->get_group());  
971 - if(group_key.empty() || subcmd_groups_seen.count(group_key) != 0)  
972 - continue; // Hidden or not in a group  
973 -  
974 - subcmd_groups_seen.insert(group_key);  
975 - out << std::endl << com->get_group() << ":" << std::endl;  
976 - for(const App_p &new_com : subcommands_)  
977 - if(detail::to_lower(new_com->get_group()) == group_key)  
978 - detail::format_help(out, new_com->get_name(), new_com->description_, wid);  
979 - }  
980 - }  
981 -  
982 - if(!footer_.empty()) {  
983 - out << std::endl << footer_ << std::endl;  
984 - }  
985 -  
986 - return out.str(); 962 + return selected_subcommands.at(0)->help(prev);
  963 + else
  964 + return formatter_(this, prev, mode);
987 } 965 }
988 966
989 ///@} 967 ///@}
@@ -993,12 +971,20 @@ class App { @@ -993,12 +971,20 @@ class App {
993 /// Get the app or subcommand description 971 /// Get the app or subcommand description
994 std::string get_description() const { return description_; } 972 std::string get_description() const { return description_; }
995 973
996 - /// Get the list of options (user facing function, so returns raw pointers)  
997 - std::vector<Option *> get_options() const {  
998 - std::vector<Option *> options(options_.size()); 974 + /// Get the list of options (user facing function, so returns raw pointers), has optional filter function
  975 + std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const {
  976 + std::vector<const Option *> options(options_.size());
999 std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { 977 std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
1000 return val.get(); 978 return val.get();
1001 }); 979 });
  980 +
  981 + if(filter) {
  982 + options.erase(std::remove_if(std::begin(options),
  983 + std::end(options),
  984 + [&filter](const Option *opt) { return !filter(opt); }),
  985 + std::end(options));
  986 + }
  987 +
1002 return options; 988 return options;
1003 } 989 }
1004 990
@@ -1035,6 +1021,9 @@ class App { @@ -1035,6 +1021,9 @@ class App {
1035 /// Get a pointer to the help flag. (const) 1021 /// Get a pointer to the help flag. (const)
1036 const Option *get_help_ptr() const { return help_ptr_; } 1022 const Option *get_help_ptr() const { return help_ptr_; }
1037 1023
  1024 + /// Get a pointer to the help all flag. (const)
  1025 + const Option *get_help_all_ptr() const { return help_all_ptr_; }
  1026 +
1038 /// Get a pointer to the config option. 1027 /// Get a pointer to the config option.
1039 Option *get_config_ptr() { return config_ptr_; } 1028 Option *get_config_ptr() { return config_ptr_; }
1040 1029
@@ -1058,6 +1047,20 @@ class App { @@ -1058,6 +1047,20 @@ class App {
1058 return local_name == name_to_check; 1047 return local_name == name_to_check;
1059 } 1048 }
1060 1049
  1050 + /// Get the groups available directly from this option (in order)
  1051 + std::vector<std::string> get_groups() const {
  1052 + std::vector<std::string> groups;
  1053 +
  1054 + for(const Option_p &opt : options_) {
  1055 + // Add group if it is not already in there
  1056 + if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {
  1057 + groups.push_back(opt->get_group());
  1058 + }
  1059 + }
  1060 +
  1061 + return groups;
  1062 + }
  1063 +
1061 /// This gets a vector of pointers with the original parse order 1064 /// This gets a vector of pointers with the original parse order
1062 const std::vector<Option *> &parse_order() const { return parse_order_; } 1065 const std::vector<Option *> &parse_order() const { return parse_order_; }
1063 1066
@@ -1161,6 +1164,10 @@ class App { @@ -1161,6 +1164,10 @@ class App {
1161 throw CallForHelp(); 1164 throw CallForHelp();
1162 } 1165 }
1163 1166
  1167 + if(help_all_ptr_ != nullptr && help_all_ptr_->count() > 0) {
  1168 + throw CallForAllHelp();
  1169 + }
  1170 +
1164 // Process an INI file 1171 // Process an INI file
1165 if(config_ptr_ != nullptr) { 1172 if(config_ptr_ != nullptr) {
1166 if(*config_ptr_) { 1173 if(*config_ptr_) {
@@ -1221,20 +1228,20 @@ class App { @@ -1221,20 +1228,20 @@ class App {
1221 if(opt->get_required() || opt->count() != 0) { 1228 if(opt->get_required() || opt->count() != 0) {
1222 // Make sure enough -N arguments parsed (+N is already handled in parsing function) 1229 // Make sure enough -N arguments parsed (+N is already handled in parsing function)
1223 if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected())) 1230 if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected()))
1224 - throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_items_expected()); 1231 + throw ArgumentMismatch::AtLeast(opt->get_name(), -opt->get_items_expected());
1225 1232
1226 // Required but empty 1233 // Required but empty
1227 if(opt->get_required() && opt->count() == 0) 1234 if(opt->get_required() && opt->count() == 0)
1228 - throw RequiredError(opt->single_name()); 1235 + throw RequiredError(opt->get_name());
1229 } 1236 }
1230 // Requires 1237 // Requires
1231 for(const Option *opt_req : opt->requires_) 1238 for(const Option *opt_req : opt->requires_)
1232 if(opt->count() > 0 && opt_req->count() == 0) 1239 if(opt->count() > 0 && opt_req->count() == 0)
1233 - throw RequiresError(opt->single_name(), opt_req->single_name()); 1240 + throw RequiresError(opt->get_name(), opt_req->get_name());
1234 // Excludes 1241 // Excludes
1235 for(const Option *opt_ex : opt->excludes_) 1242 for(const Option *opt_ex : opt->excludes_)
1236 if(opt->count() > 0 && opt_ex->count() != 0) 1243 if(opt->count() > 0 && opt_ex->count() != 0)
1237 - throw ExcludesError(opt->single_name(), opt_ex->single_name()); 1244 + throw ExcludesError(opt->get_name(), opt_ex->get_name());
1238 } 1245 }
1239 1246
1240 auto selected_subcommands = get_subcommands(); 1247 auto selected_subcommands = get_subcommands();
@@ -1508,7 +1515,7 @@ class App { @@ -1508,7 +1515,7 @@ class App {
1508 } 1515 }
1509 1516
1510 if(num > 0) { 1517 if(num > 0) {
1511 - throw ArgumentMismatch::TypedAtLeast(op->single_name(), num, op->get_type_name()); 1518 + throw ArgumentMismatch::TypedAtLeast(op->get_name(), num, op->get_type_name());
1512 } 1519 }
1513 } 1520 }
1514 1521
@@ -1524,7 +1531,7 @@ namespace FailureMessage { @@ -1524,7 +1531,7 @@ namespace FailureMessage {
1524 inline std::string simple(const App *app, const Error &e) { 1531 inline std::string simple(const App *app, const Error &e) {
1525 std::string header = std::string(e.what()) + "\n"; 1532 std::string header = std::string(e.what()) + "\n";
1526 if(app->get_help_ptr() != nullptr) 1533 if(app->get_help_ptr() != nullptr)
1527 - header += "Run with " + app->get_help_ptr()->single_name() + " for more information.\n"; 1534 + header += "Run with " + app->get_help_ptr()->get_name() + " for more information.\n";
1528 return header; 1535 return header;
1529 } 1536 }
1530 1537
include/CLI/AppFormatter.hpp 0 โ†’ 100644
  1 +#pragma once
  2 +
  3 +// Distributed under the 3-Clause BSD License. See accompanying
  4 +// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
  5 +
  6 +#include <string>
  7 +
  8 +#include "CLI/App.hpp"
  9 +#include "CLI/Formatter.hpp"
  10 +
  11 +namespace CLI {
  12 +
  13 +inline std::string
  14 +AppFormatter::make_group(std::string group, std::vector<const Option *> opts, OptionFormatMode mode) const {
  15 + std::stringstream out;
  16 + out << "\n" << group << ":\n";
  17 + for(const Option *opt : opts) {
  18 + out << opt->help(mode);
  19 + }
  20 +
  21 + return out.str();
  22 +}
  23 +
  24 +inline std::string AppFormatter::make_groups(const App *app, AppFormatMode mode) const {
  25 + std::stringstream out;
  26 + std::vector<std::string> groups = app->get_groups();
  27 + std::vector<const Option *> positionals =
  28 + app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
  29 +
  30 + if(!positionals.empty())
  31 + out << make_group(get_label("Positionals"), positionals, OptionFormatMode::Positional);
  32 +
  33 + // Options
  34 + for(const std::string &group : groups) {
  35 + std::vector<const Option *> grouped_items =
  36 + app->get_options([&group](const Option *opt) { return opt->nonpositional() && opt->get_group() == group; });
  37 +
  38 + if(mode == AppFormatMode::Sub) {
  39 + grouped_items.erase(std::remove_if(grouped_items.begin(),
  40 + grouped_items.end(),
  41 + [app](const Option *opt) {
  42 + return app->get_help_ptr() == opt || app->get_help_all_ptr() == opt;
  43 + }),
  44 + grouped_items.end());
  45 + }
  46 +
  47 + if(!group.empty() && !grouped_items.empty()) {
  48 + out << make_group(group, grouped_items, OptionFormatMode::Optional);
  49 + if(group != groups.back())
  50 + out << "\n";
  51 + }
  52 + }
  53 +
  54 + return out.str();
  55 +}
  56 +
  57 +inline std::string AppFormatter::make_description(const App *app) const {
  58 + std::string desc = app->get_description();
  59 +
  60 + if(!desc.empty())
  61 + return desc + "\n";
  62 + else
  63 + return "";
  64 +}
  65 +
  66 +inline std::string AppFormatter::make_usage(const App *app, std::string name) const {
  67 + std::stringstream out;
  68 +
  69 + out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
  70 +
  71 + std::vector<std::string> groups = app->get_groups();
  72 +
  73 + // Print an Options badge if any options exist
  74 + std::vector<const Option *> non_pos_options =
  75 + app->get_options([](const Option *opt) { return opt->nonpositional(); });
  76 + if(!non_pos_options.empty())
  77 + out << " [" << get_label("OPTIONS") << "]";
  78 +
  79 + // Positionals need to be listed here
  80 + std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
  81 +
  82 + // Print out positionals if any are left
  83 + if(!positionals.empty()) {
  84 + // Convert to help names
  85 + std::vector<std::string> positional_names(positionals.size());
  86 + std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [](const Option *opt) {
  87 + return opt->help(OptionFormatMode::Usage);
  88 + });
  89 +
  90 + out << " " << detail::join(positional_names, " ");
  91 + }
  92 +
  93 + // Add a marker if subcommands are expected or optional
  94 + if(!app->get_subcommands({}).empty()) {
  95 + out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
  96 + << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
  97 + : "SUBCOMMANDS")
  98 + << (app->get_require_subcommand_min() == 0 ? "]" : "");
  99 + }
  100 +
  101 + out << std::endl;
  102 +
  103 + return out.str();
  104 +}
  105 +
  106 +inline std::string AppFormatter::make_footer(const App *app) const {
  107 + std::string footer = app->get_footer();
  108 + if(!footer.empty())
  109 + return footer + "\n";
  110 + else
  111 + return "";
  112 +}
  113 +
  114 +inline std::string AppFormatter::operator()(const App *app, std::string name, AppFormatMode mode) const {
  115 +
  116 + std::stringstream out;
  117 + if(mode == AppFormatMode::Normal) {
  118 + out << make_description(app);
  119 + out << make_usage(app, name);
  120 + out << make_groups(app, mode);
  121 + out << make_subcommands(app, mode);
  122 + out << make_footer(app);
  123 + } else if(mode == AppFormatMode::Sub) {
  124 + out << make_expanded(app);
  125 + } else if(mode == AppFormatMode::All) {
  126 + out << make_description(app);
  127 + out << make_usage(app, name);
  128 + out << make_groups(app, mode);
  129 + out << make_subcommands(app, mode);
  130 + }
  131 +
  132 + return out.str();
  133 +}
  134 +
  135 +inline std::string AppFormatter::make_subcommands(const App *app, AppFormatMode mode) const {
  136 + std::stringstream out;
  137 +
  138 + std::vector<const App *> subcommands = app->get_subcommands({});
  139 +
  140 + // Make a list in definition order of the groups seen
  141 + std::vector<std::string> subcmd_groups_seen;
  142 + for(const App *com : subcommands) {
  143 + std::string group_key = com->get_group();
  144 + if(!group_key.empty() &&
  145 + std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
  146 + return detail::to_lower(a) == detail::to_lower(group_key);
  147 + }) == subcmd_groups_seen.end())
  148 + subcmd_groups_seen.push_back(group_key);
  149 + }
  150 +
  151 + // For each group, filter out and print subcommands
  152 + for(const std::string &group : subcmd_groups_seen) {
  153 + out << "\n" << group << ":\n";
  154 + if(mode == AppFormatMode::All)
  155 + out << "\n";
  156 + std::vector<const App *> subcommands_group = app->get_subcommands(
  157 + [&group](const App *app) { return detail::to_lower(app->get_group()) == detail::to_lower(group); });
  158 + for(const App *new_com : subcommands_group) {
  159 + if(mode != AppFormatMode::All) {
  160 + out << make_subcommand(new_com);
  161 + } else {
  162 + out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
  163 + if(new_com != subcommands_group.back())
  164 + out << "\n";
  165 + }
  166 + }
  167 + }
  168 +
  169 + return out.str();
  170 +}
  171 +
  172 +inline std::string AppFormatter::make_subcommand(const App *sub) const {
  173 + std::stringstream out;
  174 + detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
  175 + return out.str();
  176 +}
  177 +
  178 +inline std::string AppFormatter::make_expanded(const App *sub) const {
  179 + std::stringstream out;
  180 + out << sub->get_name() << "\n " << sub->get_description();
  181 + out << make_groups(sub, AppFormatMode::Sub);
  182 + return out.str();
  183 +}
  184 +
  185 +} // namespace CLI
include/CLI/CLI.hpp
@@ -24,6 +24,12 @@ @@ -24,6 +24,12 @@
24 24
25 #include "CLI/Validators.hpp" 25 #include "CLI/Validators.hpp"
26 26
  27 +#include "CLI/Formatter.hpp"
  28 +
27 #include "CLI/Option.hpp" 29 #include "CLI/Option.hpp"
28 30
  31 +#include "CLI/OptionFormatter.hpp"
  32 +
29 #include "CLI/App.hpp" 33 #include "CLI/App.hpp"
  34 +
  35 +#include "CLI/AppFormatter.hpp"
include/CLI/Error.hpp
@@ -157,6 +157,13 @@ class CallForHelp : public ParseError { @@ -157,6 +157,13 @@ class CallForHelp : public ParseError {
157 CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} 157 CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
158 }; 158 };
159 159
  160 +/// Usually somethign like --help-all on command line
  161 +class CallForAllHelp : public ParseError {
  162 + CLI11_ERROR_DEF(ParseError, CallForAllHelp)
  163 + CallForAllHelp()
  164 + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
  165 +};
  166 +
160 /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. 167 /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
161 class RuntimeError : public ParseError { 168 class RuntimeError : public ParseError {
162 CLI11_ERROR_DEF(ParseError, RuntimeError) 169 CLI11_ERROR_DEF(ParseError, RuntimeError)
include/CLI/Formatter.hpp 0 โ†’ 100644
  1 +#pragma once
  2 +
  3 +// Distributed under the 3-Clause BSD License. See accompanying
  4 +// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
  5 +
  6 +#include <string>
  7 +#include <map>
  8 +
  9 +#include "CLI/StringTools.hpp"
  10 +
  11 +namespace CLI {
  12 +
  13 +class Option;
  14 +class App;
  15 +
  16 +/// This enum signifies what situation the option is beig printed in.
  17 +///
  18 +/// This is passed in as part of the built in formatter in App; it is
  19 +/// possible that a custom App formatter could avoid using it, however.
  20 +enum class OptionFormatMode {
  21 + Usage, //< In the program usage line
  22 + Positional, //< In the positionals
  23 + Optional //< In the normal optionals
  24 +};
  25 +
  26 +/// This enum signifies the type of help requested
  27 +///
  28 +/// This is passed in by App; all user classes must accept this as
  29 +/// the second argument.
  30 +
  31 +enum class AppFormatMode {
  32 + Normal, //< The normal, detailed help
  33 + All, //< A fully expanded help
  34 + Sub, //< Used when printed as part of expanded subcommand
  35 +};
  36 +
  37 +/// This is an example formatter (and also the default formatter)
  38 +/// For option help.
  39 +class OptionFormatter {
  40 + protected:
  41 + /// @name Options
  42 + ///@{
  43 +
  44 + /// @brief The required help printout labels (user changeable)
  45 + /// Values are REQUIRED, NEEDS, EXCLUDES
  46 + std::map<std::string, std::string> labels_{{"REQUIRED", "(REQUIRED)"}};
  47 +
  48 + /// The width of the first column
  49 + size_t column_width_{30};
  50 +
  51 + ///@}
  52 + /// @name Basic
  53 + ///@{
  54 +
  55 + public:
  56 + OptionFormatter() = default;
  57 + OptionFormatter(const OptionFormatter &) = default;
  58 + OptionFormatter(OptionFormatter &&) = default;
  59 +
  60 + ///@}
  61 + /// @name Setters
  62 + ///@{
  63 +
  64 + /// Set the "REQUIRED" label
  65 + void label(std::string key, std::string val) { labels_[key] = val; }
  66 +
  67 + /// Set the column width
  68 + void column_width(size_t val) { column_width_ = val; }
  69 +
  70 + ///@}
  71 + /// @name Getters
  72 + ///@{
  73 +
  74 + /// Get the current value of a name (REQUIRED, etc.)
  75 + std::string get_label(std::string key) const {
  76 + if(labels_.find(key) == labels_.end())
  77 + return key;
  78 + else
  79 + return labels_.at(key);
  80 + }
  81 +
  82 + /// Get the current column width
  83 + size_t get_column_width() const { return column_width_; }
  84 +
  85 + ///@}
  86 + /// @name Overridables
  87 + ///@{
  88 +
  89 + /// @brief This is the name part of an option, Default: left column
  90 + virtual std::string make_name(const Option *, OptionFormatMode) const;
  91 +
  92 + /// @brief This is the options part of the name, Default: combined into left column
  93 + virtual std::string make_opts(const Option *) const;
  94 +
  95 + /// @brief This is the description. Default: Right column, on new line if left column too large
  96 + virtual std::string make_desc(const Option *) const;
  97 +
  98 + /// @brief This is used to print the name on the USAGE line (by App formatter)
  99 + virtual std::string make_usage(const Option *opt) const;
  100 +
  101 + /// @brief This is the standard help combiner that does the "default" thing.
  102 + virtual std::string operator()(const Option *opt, OptionFormatMode mode) const {
  103 + std::stringstream out;
  104 + if(mode == OptionFormatMode::Usage)
  105 + out << make_usage(opt);
  106 + else
  107 + detail::format_help(out, make_name(opt, mode) + make_opts(opt), make_desc(opt), column_width_);
  108 + return out.str();
  109 + }
  110 +
  111 + ///@}
  112 +};
  113 +
  114 +class AppFormatter {
  115 + /// @name Options
  116 + ///@{
  117 +
  118 + /// The width of the first column
  119 + size_t column_width_{30};
  120 +
  121 + /// @brief The required help printout labels (user changeable)
  122 + /// Values are Needs, Excludes, etc.
  123 + std::map<std::string, std::string> labels_;
  124 +
  125 + ///@}
  126 + /// @name Basic
  127 + ///@{
  128 +
  129 + public:
  130 + AppFormatter() = default;
  131 + AppFormatter(const AppFormatter &) = default;
  132 + AppFormatter(AppFormatter &&) = default;
  133 + ///@}
  134 + /// @name Setters
  135 + ///@{
  136 +
  137 + /// Set the "REQUIRED" label
  138 + void label(std::string key, std::string val) { labels_[key] = val; }
  139 +
  140 + /// Set the column width
  141 + void column_width(size_t val) { column_width_ = val; }
  142 +
  143 + ///@}
  144 + /// @name Getters
  145 + ///@{
  146 +
  147 + /// Get the current value of a name (REQUIRED, etc.)
  148 + std::string get_label(std::string key) const {
  149 + if(labels_.find(key) == labels_.end())
  150 + return key;
  151 + else
  152 + return labels_.at(key);
  153 + }
  154 +
  155 + /// Get the current column width
  156 + size_t get_column_width() const { return column_width_; }
  157 +
  158 + /// @name Overridables
  159 + ///@{
  160 +
  161 + /// This prints out a group of options
  162 + virtual std::string make_group(std::string group, std::vector<const Option *> opts, OptionFormatMode mode) const;
  163 +
  164 + /// This prints out all the groups of options
  165 + virtual std::string make_groups(const App *app, AppFormatMode mode) const;
  166 +
  167 + /// This prints out all the subcommands
  168 + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
  169 +
  170 + /// This prints out a subcommand
  171 + virtual std::string make_subcommand(const App *sub) const;
  172 +
  173 + /// This prints out a subcommand in help-all
  174 + virtual std::string make_expanded(const App *sub) const;
  175 +
  176 + /// This prints out all the groups of options
  177 + virtual std::string make_footer(const App *app) const;
  178 +
  179 + /// This displays the description line
  180 + virtual std::string make_description(const App *app) const;
  181 +
  182 + /// This displays the usage line
  183 + virtual std::string make_usage(const App *app, std::string name) const;
  184 +
  185 + /// This puts everything together
  186 + virtual std::string operator()(const App *, std::string, AppFormatMode) const;
  187 + ///@}
  188 +};
  189 +
  190 +} // namespace CLI
include/CLI/Option.hpp
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 #include "CLI/Macros.hpp" 16 #include "CLI/Macros.hpp"
17 #include "CLI/Split.hpp" 17 #include "CLI/Split.hpp"
18 #include "CLI/StringTools.hpp" 18 #include "CLI/StringTools.hpp"
  19 +#include "CLI/Formatter.hpp"
19 20
20 namespace CLI { 21 namespace CLI {
21 22
@@ -48,12 +49,17 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -48,12 +49,17 @@ template &lt;typename CRTP&gt; class OptionBase {
48 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) 49 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
49 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; 50 MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
50 51
  52 + /// @brief A formatter to print out help
  53 + /// Used by the default App help formatter.
  54 + std::function<std::string(const Option *, OptionFormatMode)> formatter_{OptionFormatter()};
  55 +
51 template <typename T> void copy_to(T *other) const { 56 template <typename T> void copy_to(T *other) const {
52 other->group(group_); 57 other->group(group_);
53 other->required(required_); 58 other->required(required_);
54 other->ignore_case(ignore_case_); 59 other->ignore_case(ignore_case_);
55 other->configurable(configurable_); 60 other->configurable(configurable_);
56 other->multi_option_policy(multi_option_policy_); 61 other->multi_option_policy(multi_option_policy_);
  62 + other->formatter(formatter_);
57 } 63 }
58 64
59 public: 65 public:
@@ -75,6 +81,12 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -75,6 +81,12 @@ template &lt;typename CRTP&gt; class OptionBase {
75 /// Support Plumbum term 81 /// Support Plumbum term
76 CRTP *mandatory(bool value = true) { return required(value); } 82 CRTP *mandatory(bool value = true) { return required(value); }
77 83
  84 + /// Set a formatter for this option
  85 + CRTP *formatter(std::function<std::string(const Option *, OptionFormatMode)> value) {
  86 + formatter_ = value;
  87 + return static_cast<CRTP *>(this);
  88 + }
  89 +
78 // Getters 90 // Getters
79 91
80 /// Get the group of this option 92 /// Get the group of this option
@@ -250,11 +262,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -250,11 +262,11 @@ class Option : public OptionBase&lt;Option&gt; {
250 Option *expected(int value) { 262 Option *expected(int value) {
251 // Break if this is a flag 263 // Break if this is a flag
252 if(type_size_ == 0) 264 if(type_size_ == 0)
253 - throw IncorrectConstruction::SetFlag(single_name()); 265 + throw IncorrectConstruction::SetFlag(get_name(true, true));
254 266
255 // Setting 0 is not allowed 267 // Setting 0 is not allowed
256 else if(value == 0) 268 else if(value == 0)
257 - throw IncorrectConstruction::Set0Opt(single_name()); 269 + throw IncorrectConstruction::Set0Opt(get_name());
258 270
259 // No change is okay, quit now 271 // No change is okay, quit now
260 else if(expected_ == value) 272 else if(expected_ == value)
@@ -262,11 +274,11 @@ class Option : public OptionBase&lt;Option&gt; { @@ -262,11 +274,11 @@ class Option : public OptionBase&lt;Option&gt; {
262 274
263 // Type must be a vector 275 // Type must be a vector
264 else if(type_size_ >= 0) 276 else if(type_size_ >= 0)
265 - throw IncorrectConstruction::ChangeNotVector(single_name()); 277 + throw IncorrectConstruction::ChangeNotVector(get_name());
266 278
267 // TODO: Can support multioption for non-1 values (except for join) 279 // TODO: Can support multioption for non-1 values (except for join)
268 else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) 280 else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw)
269 - throw IncorrectConstruction::AfterMultiOpt(single_name()); 281 + throw IncorrectConstruction::AfterMultiOpt(get_name());
270 282
271 expected_ = value; 283 expected_ = value;
272 return this; 284 return this;
@@ -295,7 +307,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -295,7 +307,7 @@ class Option : public OptionBase&lt;Option&gt; {
295 Option *needs(Option *opt) { 307 Option *needs(Option *opt) {
296 auto tup = requires_.insert(opt); 308 auto tup = requires_.insert(opt);
297 if(!tup.second) 309 if(!tup.second)
298 - throw OptionAlreadyAdded::Requires(single_name(), opt->single_name()); 310 + throw OptionAlreadyAdded::Requires(get_name(), opt->get_name());
299 return this; 311 return this;
300 } 312 }
301 313
@@ -356,7 +368,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -356,7 +368,7 @@ class Option : public OptionBase&lt;Option&gt; {
356 368
357 for(const Option_p &opt : parent->options_) 369 for(const Option_p &opt : parent->options_)
358 if(opt.get() != this && *opt == *this) 370 if(opt.get() != this && *opt == *this)
359 - throw OptionAlreadyAdded(opt->get_name()); 371 + throw OptionAlreadyAdded(opt->get_name(true, true));
360 372
361 return this; 373 return this;
362 } 374 }
@@ -365,7 +377,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -365,7 +377,7 @@ class Option : public OptionBase&lt;Option&gt; {
365 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { 377 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
366 378
367 if(get_items_expected() < 0) 379 if(get_items_expected() < 0)
368 - throw IncorrectConstruction::MultiOptionPolicy(single_name()); 380 + throw IncorrectConstruction::MultiOptionPolicy(get_name());
369 multi_option_policy_ = value; 381 multi_option_policy_ = value;
370 return this; 382 return this;
371 } 383 }
@@ -377,6 +389,21 @@ class Option : public OptionBase&lt;Option&gt; { @@ -377,6 +389,21 @@ class Option : public OptionBase&lt;Option&gt; {
377 /// The number of arguments the option expects 389 /// The number of arguments the option expects
378 int get_type_size() const { return type_size_; } 390 int get_type_size() const { return type_size_; }
379 391
  392 + /// The type name (for help printing)
  393 + std::string get_typeval() const { return typeval_; }
  394 +
  395 + /// The environment variable associated to this value
  396 + std::string get_envname() const { return envname_; }
  397 +
  398 + /// The set of options needed
  399 + std::set<Option *> get_needs() const { return requires_; }
  400 +
  401 + /// The set of options excluded
  402 + std::set<Option *> get_excludes() const { return excludes_; }
  403 +
  404 + /// The default value (for help printing)
  405 + std::string get_defaultval() const { return defaultval_; }
  406 +
380 /// The number of times the option expects to be included 407 /// The number of times the option expects to be included
381 int get_expected() const { return expected_; } 408 int get_expected() const { return expected_; }
382 409
@@ -416,91 +443,65 @@ class Option : public OptionBase&lt;Option&gt; { @@ -416,91 +443,65 @@ class Option : public OptionBase&lt;Option&gt; {
416 /// Get the description 443 /// Get the description
417 const std::string &get_description() const { return description_; } 444 const std::string &get_description() const { return description_; }
418 445
419 - // Just the pname  
420 - std::string get_pname() const { return pname_; }  
421 -  
422 ///@} 446 ///@}
423 /// @name Help tools 447 /// @name Help tools
424 ///@{ 448 ///@{
425 449
426 - /// Gets a , sep list of names. Does not include the positional name if opt_only=true.  
427 - std::string get_name(bool opt_only = false) const {  
428 - std::vector<std::string> name_list;  
429 - if(!opt_only && pname_.length() > 0)  
430 - name_list.push_back(pname_);  
431 - for(const std::string &sname : snames_)  
432 - name_list.push_back("-" + sname);  
433 - for(const std::string &lname : lnames_)  
434 - name_list.push_back("--" + lname);  
435 - return detail::join(name_list);  
436 - }  
437 -  
438 - /// The name and any extras needed for positionals  
439 - std::string help_positional() const {  
440 - std::string out = pname_;  
441 - if(get_expected() > 1)  
442 - out = out + "(" + std::to_string(get_expected()) + "x)";  
443 - else if(get_expected() == -1)  
444 - out = out + "...";  
445 - out = get_required() ? out : "[" + out + "]";  
446 - return out;  
447 - }  
448 -  
449 - /// The most descriptive name available  
450 - std::string single_name() const {  
451 - if(!lnames_.empty())  
452 - return std::string("--") + lnames_[0];  
453 - else if(!snames_.empty())  
454 - return std::string("-") + snames_[0];  
455 - else  
456 - return pname_;  
457 - }  
458 -  
459 - /// The first half of the help print, name plus default, etc. Setting opt_only to true avoids the positional name.  
460 - std::string help_name(bool opt_only = false) const {  
461 - std::stringstream out;  
462 - out << get_name(opt_only) << help_aftername();  
463 - return out.str();  
464 - }  
465 -  
466 - /// pname with type info  
467 - std::string help_pname() const {  
468 - std::stringstream out;  
469 - out << get_pname() << help_aftername();  
470 - return out.str();  
471 - }  
472 -  
473 - /// This is the part after the name is printed but before the description  
474 - std::string help_aftername() const {  
475 - std::stringstream out;  
476 -  
477 - if(get_type_size() != 0) {  
478 - if(!typeval_.empty())  
479 - out << " " << typeval_;  
480 - if(!defaultval_.empty())  
481 - out << "=" << defaultval_;  
482 - if(get_expected() > 1)  
483 - out << " x " << get_expected();  
484 - if(get_expected() == -1)  
485 - out << " ...";  
486 - if(get_required())  
487 - out << " (REQUIRED)";  
488 - }  
489 - if(!envname_.empty())  
490 - out << " (env:" << envname_ << ")";  
491 - if(!requires_.empty()) {  
492 - out << " Needs:";  
493 - for(const Option *opt : requires_)  
494 - out << " " << opt->single_name();  
495 - }  
496 - if(!excludes_.empty()) {  
497 - out << " Excludes:";  
498 - for(const Option *opt : excludes_)  
499 - out << " " << opt->single_name(); 450 + /// \brief Gets a comma seperated list of names.
  451 + /// Will include / prefer the positional name if positional is true.
  452 + /// If all_options is false, pick just the most descriptive name to show.
  453 + /// Use `get_name(true)` to get the positional name (replaces `get_pname`)
  454 + std::string get_name(bool positional = false, //<[input] Show the positional name
  455 + bool all_options = false //<[input] Show every option
  456 + ) const {
  457 +
  458 + if(all_options) {
  459 +
  460 + std::vector<std::string> name_list;
  461 +
  462 + /// The all list wil never include a positional unless asked or that's the only name.
  463 + if((positional && pname_.length()) || (snames_.empty() && lnames_.empty()))
  464 + name_list.push_back(pname_);
  465 +
  466 + for(const std::string &sname : snames_)
  467 + name_list.push_back("-" + sname);
  468 +
  469 + for(const std::string &lname : lnames_)
  470 + name_list.push_back("--" + lname);
  471 +
  472 + return detail::join(name_list);
  473 +
  474 + } else {
  475 +
  476 + // This returns the positional name no matter what
  477 + if(positional)
  478 + return pname_;
  479 +
  480 + // Prefer long name
  481 + else if(!lnames_.empty())
  482 + return std::string("--") + lnames_[0];
  483 +
  484 + // Or short name if no long name
  485 + else if(!snames_.empty())
  486 + return std::string("-") + snames_[0];
  487 +
  488 + // If positional is the only name, it's okay to use that
  489 + else
  490 + return pname_;
500 } 491 }
501 - return out.str();  
502 } 492 }
503 493
  494 + /// \brief Call this with a OptionFormatMode to run the currently configured help formatter.
  495 + ///
  496 + /// Changed in Version 1.6:
  497 + ///
  498 + /// * `help_positinoal` MOVED TO `help_usage` (name not included) or Usage mode
  499 + /// * `help_name` CHANGED to `help_name` with different true/false flags
  500 + /// * `pname` with type info MOVED to `help_name`
  501 + /// * `help_aftername()` MOVED to `help_opts()`
  502 + /// * Instead of `opt->help_mode()` use `opt->help(mode)`
  503 + std::string help(OptionFormatMode mode) const { return formatter_(this, mode); }
  504 +
504 ///@} 505 ///@}
505 /// @name Parser tools 506 /// @name Parser tools
506 ///@{ 507 ///@{
@@ -514,7 +515,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -514,7 +515,7 @@ class Option : public OptionBase&lt;Option&gt; {
514 for(const std::function<std::string(std::string &)> &vali : validators_) { 515 for(const std::function<std::string(std::string &)> &vali : validators_) {
515 std::string err_msg = vali(result); 516 std::string err_msg = vali(result);
516 if(!err_msg.empty()) 517 if(!err_msg.empty())
517 - throw ValidationError(single_name(), err_msg); 518 + throw ValidationError(get_name(), err_msg);
518 } 519 }
519 } 520 }
520 521
@@ -542,7 +543,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -542,7 +543,7 @@ class Option : public OptionBase&lt;Option&gt; {
542 // For now, vector of non size 1 types are not supported but possibility included here 543 // For now, vector of non size 1 types are not supported but possibility included here
543 if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) || 544 if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) ||
544 (get_items_expected() < 0 && results_.size() < static_cast<size_t>(-get_items_expected()))) 545 (get_items_expected() < 0 && results_.size() < static_cast<size_t>(-get_items_expected())))
545 - throw ArgumentMismatch(single_name(), get_items_expected(), results_.size()); 546 + throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());
546 else 547 else
547 local_result = !callback_(results_); 548 local_result = !callback_(results_);
548 } 549 }
@@ -651,17 +652,6 @@ class Option : public OptionBase&lt;Option&gt; { @@ -651,17 +652,6 @@ class Option : public OptionBase&lt;Option&gt; {
651 652
652 /// Get the typename for this option 653 /// Get the typename for this option
653 std::string get_type_name() const { return typeval_; } 654 std::string get_type_name() const { return typeval_; }
654 -  
655 - ///@}  
656 -  
657 - protected:  
658 - /// @name App Helpers  
659 - ///@{  
660 - /// Can print positional name detailed option if true  
661 - bool _has_help_positional() const {  
662 - return get_positional() && (has_description() || !requires_.empty() || !excludes_.empty());  
663 - }  
664 - ///@}  
665 }; 655 };
666 656
667 } // namespace CLI 657 } // namespace CLI
include/CLI/OptionFormatter.hpp 0 โ†’ 100644
  1 +#pragma once
  2 +
  3 +// Distributed under the 3-Clause BSD License. See accompanying
  4 +// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
  5 +
  6 +#include <string>
  7 +
  8 +#include "CLI/Option.hpp"
  9 +#include "CLI/Formatter.hpp"
  10 +
  11 +namespace CLI {
  12 +
  13 +inline std::string OptionFormatter::make_name(const Option *opt, OptionFormatMode mode) const {
  14 + if(mode == OptionFormatMode::Optional)
  15 + return opt->get_name(false, true);
  16 + else
  17 + return opt->get_name(true, false);
  18 +}
  19 +
  20 +inline std::string OptionFormatter::make_opts(const Option *opt) const {
  21 + std::stringstream out;
  22 +
  23 + if(opt->get_type_size() != 0) {
  24 + if(!opt->get_typeval().empty())
  25 + out << " " << get_label(opt->get_typeval());
  26 + if(!opt->get_defaultval().empty())
  27 + out << "=" << opt->get_defaultval();
  28 + if(opt->get_expected() > 1)
  29 + out << " x " << opt->get_expected();
  30 + if(opt->get_expected() == -1)
  31 + out << " ...";
  32 + if(opt->get_required())
  33 + out << " " << get_label("REQUIRED");
  34 + }
  35 + if(!opt->get_envname().empty())
  36 + out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
  37 + if(!opt->get_needs().empty()) {
  38 + out << " " << get_label("Needs") << ":";
  39 + for(const Option *op : opt->get_needs())
  40 + out << " " << op->get_name();
  41 + }
  42 + if(!opt->get_excludes().empty()) {
  43 + out << " " << get_label("Excludes") << ":";
  44 + for(const Option *op : opt->get_excludes())
  45 + out << " " << op->get_name();
  46 + }
  47 + return out.str();
  48 +}
  49 +
  50 +inline std::string OptionFormatter::make_desc(const Option *opt) const { return opt->get_description(); }
  51 +
  52 +inline std::string OptionFormatter::make_usage(const Option *opt) const {
  53 + // Note that these are positionals usages
  54 + std::stringstream out;
  55 + out << make_name(opt, OptionFormatMode::Usage);
  56 +
  57 + if(opt->get_expected() > 1)
  58 + out << "(" << std::to_string(opt->get_expected()) << "x)";
  59 + else if(opt->get_expected() < 0)
  60 + out << "...";
  61 +
  62 + return opt->get_required() ? out.str() : "[" + out.str() + "]";
  63 +}
  64 +
  65 +} // namespace CLI
include/CLI/StringTools.hpp
@@ -104,15 +104,16 @@ inline std::string trim_copy(const std::string &amp;str, const std::string &amp;filter) @@ -104,15 +104,16 @@ inline std::string trim_copy(const std::string &amp;str, const std::string &amp;filter)
104 return trim(s, filter); 104 return trim(s, filter);
105 } 105 }
106 /// Print a two part "help" string 106 /// Print a two part "help" string
107 -inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { 107 +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, size_t wid) {
108 name = " " + name; 108 name = " " + name;
109 out << std::setw(static_cast<int>(wid)) << std::left << name; 109 out << std::setw(static_cast<int>(wid)) << std::left << name;
110 if(!description.empty()) { 110 if(!description.empty()) {
111 if(name.length() >= wid) 111 if(name.length() >= wid)
112 - out << std::endl << std::setw(static_cast<int>(wid)) << ""; 112 + out << "\n" << std::setw(static_cast<int>(wid)) << "";
113 out << description; 113 out << description;
114 } 114 }
115 - out << std::endl; 115 + out << "\n";
  116 + return out;
116 } 117 }
117 118
118 /// Verify the first character of an option 119 /// Verify the first character of an option
tests/CMakeLists.txt
@@ -27,6 +27,7 @@ set(CLI11_TESTS @@ -27,6 +27,7 @@ set(CLI11_TESTS
27 CreationTest 27 CreationTest
28 SubcommandTest 28 SubcommandTest
29 HelpTest 29 HelpTest
  30 + FormatterTest
30 NewParseTest 31 NewParseTest
31 OptionalTest 32 OptionalTest
32 ) 33 )
tests/CreationTest.cpp
@@ -75,7 +75,7 @@ TEST_F(TApp, RecoverSubcommands) { @@ -75,7 +75,7 @@ TEST_F(TApp, RecoverSubcommands) {
75 CLI::App *app3 = app.add_subcommand("app3"); 75 CLI::App *app3 = app.add_subcommand("app3");
76 CLI::App *app4 = app.add_subcommand("app4"); 76 CLI::App *app4 = app.add_subcommand("app4");
77 77
78 - EXPECT_EQ(app.get_subcommands(false), std::vector<CLI::App *>({app1, app2, app3, app4})); 78 + EXPECT_EQ(app.get_subcommands({}), std::vector<CLI::App *>({app1, app2, app3, app4}));
79 } 79 }
80 80
81 TEST_F(TApp, MultipleSubcomMatchingWithCase) { 81 TEST_F(TApp, MultipleSubcomMatchingWithCase) {
@@ -329,10 +329,17 @@ TEST_F(TApp, GetNameCheck) { @@ -329,10 +329,17 @@ TEST_F(TApp, GetNameCheck) {
329 auto a = app.add_flag("--that"); 329 auto a = app.add_flag("--that");
330 auto b = app.add_flag("-x"); 330 auto b = app.add_flag("-x");
331 auto c = app.add_option("pos", x); 331 auto c = app.add_option("pos", x);
  332 + auto d = app.add_option("one,-o,--other", x);
332 333
333 - EXPECT_EQ(a->get_name(), "--that");  
334 - EXPECT_EQ(b->get_name(), "-x");  
335 - EXPECT_EQ(c->get_name(), "pos"); 334 + EXPECT_EQ(a->get_name(false, true), "--that");
  335 + EXPECT_EQ(b->get_name(false, true), "-x");
  336 + EXPECT_EQ(c->get_name(false, true), "pos");
  337 +
  338 + EXPECT_EQ(d->get_name(), "--other");
  339 + EXPECT_EQ(d->get_name(false, false), "--other");
  340 + EXPECT_EQ(d->get_name(false, true), "-o,--other");
  341 + EXPECT_EQ(d->get_name(true, true), "one,-o,--other");
  342 + EXPECT_EQ(d->get_name(true, false), "one");
336 } 343 }
337 344
338 TEST_F(TApp, SubcommandDefaults) { 345 TEST_F(TApp, SubcommandDefaults) {
tests/FormatterTest.cpp 0 โ†’ 100644
  1 +#ifdef CLI11_SINGLE_FILE
  2 +#include "CLI11.hpp"
  3 +#else
  4 +#include "CLI/CLI.hpp"
  5 +#endif
  6 +
  7 +#include "gtest/gtest.h"
  8 +#include "gmock/gmock.h"
  9 +#include <fstream>
  10 +
  11 +using ::testing::HasSubstr;
  12 +using ::testing::Not;
  13 +
  14 +TEST(Formatter, Nothing) {
  15 + CLI::App app{"My prog"};
  16 +
  17 + app.formatter(
  18 + [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
  19 +
  20 + std::string help = app.help();
  21 +
  22 + EXPECT_EQ(help, "This is really simple");
  23 +}
  24 +
  25 +TEST(Formatter, OptCustomize) {
  26 + CLI::App app{"My prog"};
  27 +
  28 + CLI::OptionFormatter optfmt;
  29 + optfmt.column_width(25);
  30 + optfmt.label("REQUIRED", "(MUST HAVE)");
  31 + app.option_defaults()->formatter(optfmt);
  32 +
  33 + int v;
  34 + app.add_option("--opt", v, "Something")->required();
  35 +
  36 + std::string help = app.help();
  37 +
  38 + EXPECT_THAT(help, HasSubstr("(MUST HAVE)"));
  39 + EXPECT_EQ(help,
  40 + "My prog\n"
  41 + "Usage: [OPTIONS]\n\n"
  42 + "Options:\n"
  43 + " -h,--help Print this help message and exit\n"
  44 + " --opt INT (MUST HAVE) Something\n");
  45 +}
  46 +
  47 +TEST(Formatter, AptCustomize) {
  48 + CLI::App app{"My prog"};
  49 + app.add_subcommand("subcom1", "This");
  50 +
  51 + CLI::AppFormatter appfmt;
  52 + appfmt.column_width(20);
  53 + appfmt.label("Usage", "Run");
  54 + app.formatter(appfmt);
  55 +
  56 + app.add_subcommand("subcom2", "This");
  57 +
  58 + std::string help = app.help();
  59 + EXPECT_EQ(help,
  60 + "My prog\n"
  61 + "Run: [OPTIONS] [SUBCOMMAND]\n\n"
  62 + "Options:\n"
  63 + " -h,--help Print this help message and exit\n\n"
  64 + "Subcommands:\n"
  65 + " subcom1 This\n"
  66 + " subcom2 This\n");
  67 +}
  68 +
  69 +TEST(Formatter, AllSub) {
  70 + CLI::App app{"My prog"};
  71 + CLI::App *sub = app.add_subcommand("subcom", "This");
  72 + sub->add_flag("--insub", "MyFlag");
  73 +
  74 + std::string help = app.help("", CLI::AppFormatMode::All);
  75 + EXPECT_THAT(help, HasSubstr("--insub"));
  76 + EXPECT_THAT(help, HasSubstr("subcom"));
  77 +}
tests/HelpTest.cpp
@@ -317,6 +317,27 @@ TEST(THelp, OnlyOneHelp) { @@ -317,6 +317,27 @@ TEST(THelp, OnlyOneHelp) {
317 EXPECT_THROW(app.parse(input), CLI::ExtrasError); 317 EXPECT_THROW(app.parse(input), CLI::ExtrasError);
318 } 318 }
319 319
  320 +TEST(THelp, OnlyOneAllHelp) {
  321 + CLI::App app{"My prog"};
  322 +
  323 + // It is not supported to have more than one help flag, last one wins
  324 + app.set_help_all_flag("--help-all", "No short name allowed");
  325 + app.set_help_all_flag("--yelp", "Alias for help");
  326 +
  327 + std::vector<std::string> input{"--help-all"};
  328 + EXPECT_THROW(app.parse(input), CLI::ExtrasError);
  329 +
  330 + app.reset();
  331 + std::vector<std::string> input2{"--yelp"};
  332 + EXPECT_THROW(app.parse(input2), CLI::CallForAllHelp);
  333 +
  334 + // Remove the flag
  335 + app.set_help_all_flag();
  336 + app.reset();
  337 + std::vector<std::string> input3{"--yelp"};
  338 + EXPECT_THROW(app.parse(input3), CLI::ExtrasError);
  339 +}
  340 +
320 TEST(THelp, RemoveHelp) { 341 TEST(THelp, RemoveHelp) {
321 CLI::App app{"My prog"}; 342 CLI::App app{"My prog"};
322 app.set_help_flag(); 343 app.set_help_flag();
@@ -385,9 +406,9 @@ TEST(THelp, NiceName) { @@ -385,9 +406,9 @@ TEST(THelp, NiceName) {
385 auto short_name = app.add_option("more,-x,-y", x); 406 auto short_name = app.add_option("more,-x,-y", x);
386 auto positional = app.add_option("posit", x); 407 auto positional = app.add_option("posit", x);
387 408
388 - EXPECT_EQ(long_name->single_name(), "--long");  
389 - EXPECT_EQ(short_name->single_name(), "-x");  
390 - EXPECT_EQ(positional->single_name(), "posit"); 409 + EXPECT_EQ(long_name->get_name(), "--long");
  410 + EXPECT_EQ(short_name->get_name(), "-x");
  411 + EXPECT_EQ(positional->get_name(), "posit");
391 } 412 }
392 413
393 TEST(Exit, ErrorWithHelp) { 414 TEST(Exit, ErrorWithHelp) {
@@ -401,6 +422,18 @@ TEST(Exit, ErrorWithHelp) { @@ -401,6 +422,18 @@ TEST(Exit, ErrorWithHelp) {
401 } 422 }
402 } 423 }
403 424
  425 +TEST(Exit, ErrorWithAllHelp) {
  426 + CLI::App app{"My prog"};
  427 + app.set_help_all_flag("--help-all", "All help");
  428 +
  429 + std::vector<std::string> input{"--help-all"};
  430 + try {
  431 + app.parse(input);
  432 + } catch(const CLI::CallForAllHelp &e) {
  433 + EXPECT_EQ(static_cast<int>(CLI::ExitCodes::Success), e.get_exit_code());
  434 + }
  435 +}
  436 +
404 TEST(Exit, ErrorWithoutHelp) { 437 TEST(Exit, ErrorWithoutHelp) {
405 CLI::App app{"My prog"}; 438 CLI::App app{"My prog"};
406 439
@@ -453,6 +486,29 @@ TEST_F(CapturedHelp, CallForHelp) { @@ -453,6 +486,29 @@ TEST_F(CapturedHelp, CallForHelp) {
453 EXPECT_EQ(out.str(), app.help()); 486 EXPECT_EQ(out.str(), app.help());
454 EXPECT_EQ(err.str(), ""); 487 EXPECT_EQ(err.str(), "");
455 } 488 }
  489 +TEST_F(CapturedHelp, CallForAllHelp) {
  490 + EXPECT_EQ(run(CLI::CallForAllHelp()), 0);
  491 + EXPECT_EQ(out.str(), app.help("", CLI::AppFormatMode::All));
  492 + EXPECT_EQ(err.str(), "");
  493 +}
  494 +TEST_F(CapturedHelp, CallForAllHelpOutput) {
  495 + app.add_subcommand("one");
  496 + CLI::App *sub = app.add_subcommand("two");
  497 + sub->add_flag("--three");
  498 +
  499 + EXPECT_EQ(run(CLI::CallForAllHelp()), 0);
  500 + EXPECT_EQ(out.str(), app.help("", CLI::AppFormatMode::All));
  501 + EXPECT_EQ(err.str(), "");
  502 + EXPECT_THAT(out.str(), HasSubstr("one"));
  503 + EXPECT_THAT(out.str(), HasSubstr("two"));
  504 + EXPECT_THAT(out.str(), HasSubstr("--three"));
  505 +}
  506 +TEST_F(CapturedHelp, NewFormattedHelp) {
  507 + app.formatter([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
  508 + EXPECT_EQ(run(CLI::CallForHelp()), 0);
  509 + EXPECT_EQ(out.str(), "New Help");
  510 + EXPECT_EQ(err.str(), "");
  511 +}
456 512
457 TEST_F(CapturedHelp, NormalError) { 513 TEST_F(CapturedHelp, NormalError) {
458 EXPECT_EQ(run(CLI::ExtrasError({"Thing"})), static_cast<int>(CLI::ExitCodes::ExtrasError)); 514 EXPECT_EQ(run(CLI::ExtrasError({"Thing"})), static_cast<int>(CLI::ExitCodes::ExtrasError));
tests/SubcommandTest.cpp
@@ -448,6 +448,13 @@ TEST_F(TApp, PrefixSubcom) { @@ -448,6 +448,13 @@ TEST_F(TApp, PrefixSubcom) {
448 EXPECT_EQ(subc->remaining(), std::vector<std::string>({"other", "--simple", "--mine"})); 448 EXPECT_EQ(subc->remaining(), std::vector<std::string>({"other", "--simple", "--mine"}));
449 } 449 }
450 450
  451 +TEST_F(TApp, InheritHelpAllFlag) {
  452 + app.set_help_all_flag("--help-all");
  453 + auto subc = app.add_subcommand("subc");
  454 + auto help_opt_list = subc->get_options([](const CLI::Option *opt) { return opt->get_name() == "--help-all"; });
  455 + EXPECT_EQ(help_opt_list.size(), (size_t)1);
  456 +}
  457 +
451 struct SubcommandProgram : public TApp { 458 struct SubcommandProgram : public TApp {
452 459
453 CLI::App *start; 460 CLI::App *start;
@@ -536,6 +543,8 @@ TEST_F(TApp, SubcomInheritCaseCheck) { @@ -536,6 +543,8 @@ TEST_F(TApp, SubcomInheritCaseCheck) {
536 543
537 run(); 544 run();
538 EXPECT_EQ((size_t)0, app.get_subcommands().size()); 545 EXPECT_EQ((size_t)0, app.get_subcommands().size());
  546 + EXPECT_EQ((size_t)2, app.get_subcommands({}).size());
  547 + EXPECT_EQ((size_t)1, app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size());
539 548
540 app.reset(); 549 app.reset();
541 args = {"SuB1"}; 550 args = {"SuB1"};