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 27 ### Version 1.5.3: Compiler compatibility
2 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 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 194 * `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
195 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 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 248 * `.get_parent()`: Get the parent App or nullptr if called on master App.
248 249 * `.get_options()`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
249 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 252 * `.get_description()`: Access the description.
251 253 * `.parsed()`: True if this subcommand was given on the command line.
252 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 256 * `.allow_extras()`: Do not throw an error if extra arguments are left over.
255 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 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 261 * `.set_failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
258 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 304  
301 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 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 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 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 71 add_test(NAME enum_pass COMMAND enum -l 1)
72 72 add_test(NAME enum_fail COMMAND enum -l 4)
73 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 76 add_cli_exe(modhelp modhelp.cpp)
77 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 83 set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION
84 84 "Working on file: this\.txt"
85 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 3 int main(int argc, char **argv) {
4 4  
5 5 CLI::App app("K3Pi goofit fitter");
  6 + app.set_help_all_flag("--help-all", "Expand all help");
6 7 app.add_flag("--random", "Some random flag");
7 8 CLI::App *start = app.add_subcommand("start", "A great subcommand");
8 9 CLI::App *stop = app.add_subcommand("stop", "Do you really want to stop?");
... ...
include/CLI/App.hpp
... ... @@ -24,6 +24,7 @@
24 24 #include "CLI/Split.hpp"
25 25 #include "CLI/StringTools.hpp"
26 26 #include "CLI/TypeTools.hpp"
  27 +#include "CLI/Formatter.hpp"
27 28  
28 29 namespace CLI {
29 30  
... ... @@ -102,6 +103,12 @@ class App {
102 103 /// A pointer to the help flag if there is one INHERITABLE
103 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 112 /// The error message printing function INHERITABLE
106 113 std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple;
107 114  
... ... @@ -141,7 +148,7 @@ class App {
141 148 /// True if this command/subcommand was parsed
142 149 bool parsed_{false};
143 150  
144   - /// Minimum required subcommands
  151 + /// Minimum required subcommands (not inheritable!)
145 152 size_t require_subcommand_min_ = 0;
146 153  
147 154 /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE
... ... @@ -171,7 +178,10 @@ class App {
171 178 // Inherit if not from a nullptr
172 179 if(parent_ != nullptr) {
173 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 186 /// OptionDefaults
177 187 option_defaults_ = parent_->option_defaults_;
... ... @@ -185,6 +195,7 @@ class App {
185 195 fallthrough_ = parent_->fallthrough_;
186 196 group_ = parent_->group_;
187 197 footer_ = parent_->footer_;
  198 + formatter_ = parent_->formatter_;
188 199 require_subcommand_max_ = parent_->require_subcommand_max_;
189 200 }
190 201 }
... ... @@ -250,6 +261,12 @@ class App {
250 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 270 /// Check to see if this subcommand was parsed, true only if received on command line.
254 271 bool parsed() const { return parsed_; }
255 272  
... ... @@ -388,6 +405,22 @@ class App {
388 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 424 /// Add option for flag
392 425 Option *add_flag(std::string name, std::string description = "") {
393 426 CLI::callback_t fun = [](CLI::results_t) { return true; };
... ... @@ -764,6 +797,11 @@ class App {
764 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 805 if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
768 806 if(failure_message_)
769 807 err << failure_message_(this, e) << std::flush;
... ... @@ -801,18 +839,43 @@ class App {
801 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 881 /// Check to see if given subcommand was selected
... ... @@ -885,105 +948,20 @@ class App {
885 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 954 if(prev.empty())
892   - prev = name_;
  955 + prev = get_name();
893 956 else
894   - prev += " " + name_;
  957 + prev += " " + get_name();
895 958  
  959 + // Delegate to subcommand if needed
896 960 auto selected_subcommands = get_subcommands();
897 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 971 /// Get the app or subcommand description
994 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 977 std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
1000 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 988 return options;
1003 989 }
1004 990  
... ... @@ -1035,6 +1021,9 @@ class App {
1035 1021 /// Get a pointer to the help flag. (const)
1036 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 1027 /// Get a pointer to the config option.
1039 1028 Option *get_config_ptr() { return config_ptr_; }
1040 1029  
... ... @@ -1058,6 +1047,20 @@ class App {
1058 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 1064 /// This gets a vector of pointers with the original parse order
1062 1065 const std::vector<Option *> &parse_order() const { return parse_order_; }
1063 1066  
... ... @@ -1161,6 +1164,10 @@ class App {
1161 1164 throw CallForHelp();
1162 1165 }
1163 1166  
  1167 + if(help_all_ptr_ != nullptr && help_all_ptr_->count() > 0) {
  1168 + throw CallForAllHelp();
  1169 + }
  1170 +
1164 1171 // Process an INI file
1165 1172 if(config_ptr_ != nullptr) {
1166 1173 if(*config_ptr_) {
... ... @@ -1221,20 +1228,20 @@ class App {
1221 1228 if(opt->get_required() || opt->count() != 0) {
1222 1229 // Make sure enough -N arguments parsed (+N is already handled in parsing function)
1223 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 1233 // Required but empty
1227 1234 if(opt->get_required() && opt->count() == 0)
1228   - throw RequiredError(opt->single_name());
  1235 + throw RequiredError(opt->get_name());
1229 1236 }
1230 1237 // Requires
1231 1238 for(const Option *opt_req : opt->requires_)
1232 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 1241 // Excludes
1235 1242 for(const Option *opt_ex : opt->excludes_)
1236 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 1247 auto selected_subcommands = get_subcommands();
... ... @@ -1508,7 +1515,7 @@ class App {
1508 1515 }
1509 1516  
1510 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 1531 inline std::string simple(const App *app, const Error &e) {
1525 1532 std::string header = std::string(e.what()) + "\n";
1526 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 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 24  
25 25 #include "CLI/Validators.hpp"
26 26  
  27 +#include "CLI/Formatter.hpp"
  28 +
27 29 #include "CLI/Option.hpp"
28 30  
  31 +#include "CLI/OptionFormatter.hpp"
  32 +
29 33 #include "CLI/App.hpp"
  34 +
  35 +#include "CLI/AppFormatter.hpp"
... ...
include/CLI/Error.hpp
... ... @@ -157,6 +157,13 @@ class CallForHelp : public ParseError {
157 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 167 /// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
161 168 class RuntimeError : public ParseError {
162 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 16 #include "CLI/Macros.hpp"
17 17 #include "CLI/Split.hpp"
18 18 #include "CLI/StringTools.hpp"
  19 +#include "CLI/Formatter.hpp"
19 20  
20 21 namespace CLI {
21 22  
... ... @@ -48,12 +49,17 @@ template &lt;typename CRTP&gt; class OptionBase {
48 49 /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
49 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 56 template <typename T> void copy_to(T *other) const {
52 57 other->group(group_);
53 58 other->required(required_);
54 59 other->ignore_case(ignore_case_);
55 60 other->configurable(configurable_);
56 61 other->multi_option_policy(multi_option_policy_);
  62 + other->formatter(formatter_);
57 63 }
58 64  
59 65 public:
... ... @@ -75,6 +81,12 @@ template &lt;typename CRTP&gt; class OptionBase {
75 81 /// Support Plumbum term
76 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 90 // Getters
79 91  
80 92 /// Get the group of this option
... ... @@ -250,11 +262,11 @@ class Option : public OptionBase&lt;Option&gt; {
250 262 Option *expected(int value) {
251 263 // Break if this is a flag
252 264 if(type_size_ == 0)
253   - throw IncorrectConstruction::SetFlag(single_name());
  265 + throw IncorrectConstruction::SetFlag(get_name(true, true));
254 266  
255 267 // Setting 0 is not allowed
256 268 else if(value == 0)
257   - throw IncorrectConstruction::Set0Opt(single_name());
  269 + throw IncorrectConstruction::Set0Opt(get_name());
258 270  
259 271 // No change is okay, quit now
260 272 else if(expected_ == value)
... ... @@ -262,11 +274,11 @@ class Option : public OptionBase&lt;Option&gt; {
262 274  
263 275 // Type must be a vector
264 276 else if(type_size_ >= 0)
265   - throw IncorrectConstruction::ChangeNotVector(single_name());
  277 + throw IncorrectConstruction::ChangeNotVector(get_name());
266 278  
267 279 // TODO: Can support multioption for non-1 values (except for join)
268 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 283 expected_ = value;
272 284 return this;
... ... @@ -295,7 +307,7 @@ class Option : public OptionBase&lt;Option&gt; {
295 307 Option *needs(Option *opt) {
296 308 auto tup = requires_.insert(opt);
297 309 if(!tup.second)
298   - throw OptionAlreadyAdded::Requires(single_name(), opt->single_name());
  310 + throw OptionAlreadyAdded::Requires(get_name(), opt->get_name());
299 311 return this;
300 312 }
301 313  
... ... @@ -356,7 +368,7 @@ class Option : public OptionBase&lt;Option&gt; {
356 368  
357 369 for(const Option_p &opt : parent->options_)
358 370 if(opt.get() != this && *opt == *this)
359   - throw OptionAlreadyAdded(opt->get_name());
  371 + throw OptionAlreadyAdded(opt->get_name(true, true));
360 372  
361 373 return this;
362 374 }
... ... @@ -365,7 +377,7 @@ class Option : public OptionBase&lt;Option&gt; {
365 377 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
366 378  
367 379 if(get_items_expected() < 0)
368   - throw IncorrectConstruction::MultiOptionPolicy(single_name());
  380 + throw IncorrectConstruction::MultiOptionPolicy(get_name());
369 381 multi_option_policy_ = value;
370 382 return this;
371 383 }
... ... @@ -377,6 +389,21 @@ class Option : public OptionBase&lt;Option&gt; {
377 389 /// The number of arguments the option expects
378 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 407 /// The number of times the option expects to be included
381 408 int get_expected() const { return expected_; }
382 409  
... ... @@ -416,91 +443,65 @@ class Option : public OptionBase&lt;Option&gt; {
416 443 /// Get the description
417 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 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 506 /// @name Parser tools
506 507 ///@{
... ... @@ -514,7 +515,7 @@ class Option : public OptionBase&lt;Option&gt; {
514 515 for(const std::function<std::string(std::string &)> &vali : validators_) {
515 516 std::string err_msg = vali(result);
516 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 543 // For now, vector of non size 1 types are not supported but possibility included here
543 544 if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) ||
544 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 547 else
547 548 local_result = !callback_(results_);
548 549 }
... ... @@ -651,17 +652,6 @@ class Option : public OptionBase&lt;Option&gt; {
651 652  
652 653 /// Get the typename for this option
653 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 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 104 return trim(s, filter);
105 105 }
106 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 108 name = " " + name;
109 109 out << std::setw(static_cast<int>(wid)) << std::left << name;
110 110 if(!description.empty()) {
111 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 113 out << description;
114 114 }
115   - out << std::endl;
  115 + out << "\n";
  116 + return out;
116 117 }
117 118  
118 119 /// Verify the first character of an option
... ...
tests/CMakeLists.txt
... ... @@ -27,6 +27,7 @@ set(CLI11_TESTS
27 27 CreationTest
28 28 SubcommandTest
29 29 HelpTest
  30 + FormatterTest
30 31 NewParseTest
31 32 OptionalTest
32 33 )
... ...
tests/CreationTest.cpp
... ... @@ -75,7 +75,7 @@ TEST_F(TApp, RecoverSubcommands) {
75 75 CLI::App *app3 = app.add_subcommand("app3");
76 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 81 TEST_F(TApp, MultipleSubcomMatchingWithCase) {
... ... @@ -329,10 +329,17 @@ TEST_F(TApp, GetNameCheck) {
329 329 auto a = app.add_flag("--that");
330 330 auto b = app.add_flag("-x");
331 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 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 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 341 TEST(THelp, RemoveHelp) {
321 342 CLI::App app{"My prog"};
322 343 app.set_help_flag();
... ... @@ -385,9 +406,9 @@ TEST(THelp, NiceName) {
385 406 auto short_name = app.add_option("more,-x,-y", x);
386 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 414 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 437 TEST(Exit, ErrorWithoutHelp) {
405 438 CLI::App app{"My prog"};
406 439  
... ... @@ -453,6 +486,29 @@ TEST_F(CapturedHelp, CallForHelp) {
453 486 EXPECT_EQ(out.str(), app.help());
454 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 513 TEST_F(CapturedHelp, NormalError) {
458 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 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 458 struct SubcommandProgram : public TApp {
452 459  
453 460 CLI::App *start;
... ... @@ -536,6 +543,8 @@ TEST_F(TApp, SubcomInheritCaseCheck) {
536 543  
537 544 run();
538 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 549 app.reset();
541 550 args = {"SuB1"};
... ...