Commit 598046c397ddfa8238dc3102bfb7afab1365a76b

Authored by Philip Top
Committed by Henry Schreiner
1 parent b4910df3

Add unnamed subcommand (#216)

increment the parse_ variable on unnamed subcommands. 

update the readme, and add a formatter test for nameless subcommands in nondefault group with other named subcommands.

add a test of default arguments

add a formatter test

add tests for unnamed subcommands and an example of the partitioned subcommands.

change the app_p to be a shared_ptr so you can add an App later on and merge them together

add the ability to add unnamed subcommands that allow partitioning on options into multiple apps.
README.md
@@ -71,7 +71,7 @@ An acceptable CLI parser library should be all of the following: @@ -71,7 +71,7 @@ An acceptable CLI parser library should be all of the following:
71 - Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later). 71 - Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later).
72 - Ability to add a configuration file (`ini` format), and produce it as well. 72 - Ability to add a configuration file (`ini` format), and produce it as well.
73 - Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications. 73 - Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications.
74 -- Work with standard types, simple custom types, and extendible to exotic types. 74 +- Work with standard types, simple custom types, and extensible to exotic types.
75 - Permissively licensed. 75 - Permissively licensed.
76 76
77 ### Other parsers 77 ### Other parsers
@@ -92,7 +92,7 @@ After I wrote this, I also found the following libraries: @@ -92,7 +92,7 @@ After I wrote this, I also found the following libraries:
92 | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 92 | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
93 | [GFlags][] | The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars. | 93 | [GFlags][] | The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars. |
94 | [GetOpt][] | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors). | 94 | [GetOpt][] | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors). |
95 -| [ProgramOptions.hxx][] | Intresting library, less powerful and no subcommands. Nice callback system. | 95 +| [ProgramOptions.hxx][] | Interesting library, less powerful and no subcommands. Nice callback system. |
96 | [Args][] | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose. | 96 | [Args][] | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose. |
97 | [Argument Aggregator][] | I'm a big fan of the [fmt][] library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands. | 97 | [Argument Aggregator][] | I'm a big fan of the [fmt][] library, and the try-catch statement looks familiar. :thumbsup: Doesn't seem to support subcommands. |
98 | [Clara][] | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope. | 98 | [Clara][] | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope. |
@@ -239,7 +239,7 @@ Before parsing, you can set the following options: @@ -239,7 +239,7 @@ Before parsing, you can set the following options:
239 - `->envname(name)`: Gets the value from the environment if present and not passed on the command line. 239 - `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
240 - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). 240 - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
241 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). 241 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
242 -- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with optionone. This does not apply to short form options since they only have one character 242 +- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character
243 - `->description(str)`: Set/change the description. 243 - `->description(str)`: Set/change the description.
244 - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which always default to take last). 244 - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which always default to take last).
245 - `->check(CLI::ExistingFile)`: Requires that the file exists if given. 245 - `->check(CLI::ExistingFile)`: Requires that the file exists if given.
@@ -285,7 +285,7 @@ Subcommands are supported, and can be nested infinitely. To add a subcommand, ca @@ -285,7 +285,7 @@ Subcommands are supported, and can be nested infinitely. To add a subcommand, ca
285 case). 285 case).
286 286
287 If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed. 287 If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed.
288 -0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximimum number allows you to keep arguments that match a previous 288 +0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous
289 subcommand name from matching. 289 subcommand name from matching.
290 290
291 If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool). 291 If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool).
@@ -296,6 +296,9 @@ even exit the program through the callback. The main `App` has a callback slot, @@ -296,6 +296,9 @@ even exit the program through the callback. The main `App` has a callback slot,
296 You are allowed to throw `CLI::Success` in the callbacks. 296 You are allowed to throw `CLI::Success` in the callbacks.
297 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). 297 Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
298 298
  299 +Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
  300 +Nameless subcommands function a little like groups in the main `App`. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed.
  301 +
299 #### Subcommand options 302 #### Subcommand options
300 303
301 There are several options that are supported on the main app and subcommands. These are: 304 There are several options that are supported on the main app and subcommands. These are:
@@ -307,7 +310,8 @@ There are several options that are supported on the main app and subcommands. Th @@ -307,7 +310,8 @@ There are several options that are supported on the main app and subcommands. Th
307 - `.require_subcommand()`: Require 1 or more subcommands. 310 - `.require_subcommand()`: Require 1 or more subcommands.
308 - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more. 311 - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more.
309 - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. 312 - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
310 -- `.add_subcommand(name, description="")` Add a subcommand, returns a pointer to the internally stored subcommand. 313 +- `.add_subcommand(name="", description="")` Add a subcommand, returns a pointer to the internally stored subcommand.
  314 +- `.add_subcommand(shared_ptr<App>)` Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
311 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. 315 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
312 - `.get_subcommands(filter)`: The list of subcommands given on the command line. 316 - `.get_subcommands(filter)`: The list of subcommands given on the command line.
313 - `.get_parent()`: Get the parent App or nullptr if called on master App. 317 - `.get_parent()`: Get the parent App or nullptr if called on master App.
examples/CMakeLists.txt
@@ -63,6 +63,24 @@ set_property(TEST subcommands_all PROPERTY PASS_REGULAR_EXPRESSION @@ -63,6 +63,24 @@ set_property(TEST subcommands_all PROPERTY PASS_REGULAR_EXPRESSION
63 "Subcommand: start" 63 "Subcommand: start"
64 "Subcommand: stop") 64 "Subcommand: stop")
65 65
  66 +add_cli_exe(subcom_partitioned subcom_partitioned.cpp)
  67 +add_test(NAME subcom_partitioned_none COMMAND subcom_partitioned)
  68 +set_property(TEST subcom_partitioned_none PROPERTY PASS_REGULAR_EXPRESSION
  69 + "This is a timer:"
  70 + "--file is required"
  71 + "Run with --help for more information.")
  72 +add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2)
  73 +set_property(TEST subcom_partitioned_all PROPERTY PASS_REGULAR_EXPRESSION
  74 + "This is a timer:"
  75 + "Working on file: this, direct count: 1, opt count: 1"
  76 + "Working on count: 2, direct count: 2, opt count: 2"
  77 + "Some value: 1.2")
  78 + # test shows that the help prints out for unnamed subcommands
  79 +add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help)
  80 +set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION
  81 + "-f,--file TEXT REQUIRED"
  82 + "-d,--double FLOAT")
  83 +
66 add_cli_exe(validators validators.cpp) 84 add_cli_exe(validators validators.cpp)
67 add_test(NAME validators_help COMMAND validators --help) 85 add_test(NAME validators_help COMMAND validators --help)
68 set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION 86 set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION
examples/subcom_partitioned.cpp 0 โ†’ 100644
  1 +#include "CLI/CLI.hpp"
  2 +#include "CLI/Timer.hpp"
  3 +
  4 +int main(int argc, char **argv) {
  5 + CLI::AutoTimer("This is a timer");
  6 +
  7 + CLI::App app("K3Pi goofit fitter");
  8 +
  9 + CLI::App_p impOpt = std::make_shared<CLI::App>("Important");
  10 + std::string file;
  11 + CLI::Option *opt = impOpt->add_option("-f,--file,file", file, "File name")->required();
  12 +
  13 + int count;
  14 + CLI::Option *copt = impOpt->add_flag("-c,--count", count, "Counter")->required();
  15 +
  16 + CLI::App_p otherOpt = std::make_shared<CLI::App>("Other");
  17 + double value; // = 3.14;
  18 + otherOpt->add_option("-d,--double", value, "Some Value");
  19 +
  20 + // add the subapps to the main one
  21 + app.add_subcommand(impOpt);
  22 + app.add_subcommand(otherOpt);
  23 +
  24 + try {
  25 + app.parse(argc, argv);
  26 + } catch(const CLI::ParseError &e) {
  27 + return app.exit(e);
  28 + }
  29 +
  30 + std::cout << "Working on file: " << file << ", direct count: " << impOpt->count("--file")
  31 + << ", opt count: " << opt->count() << std::endl;
  32 + std::cout << "Working on count: " << count << ", direct count: " << impOpt->count("--count")
  33 + << ", opt count: " << copt->count() << std::endl;
  34 + std::cout << "Some value: " << value << std::endl;
  35 +
  36 + return 0;
  37 +}
include/CLI/App.hpp
@@ -49,7 +49,7 @@ std::string help(const App *app, const Error &amp;e); @@ -49,7 +49,7 @@ std::string help(const App *app, const Error &amp;e);
49 49
50 class App; 50 class App;
51 51
52 -using App_p = std::unique_ptr<App>; 52 +using App_p = std::shared_ptr<App>;
53 53
54 /// Creates a command line program, with very few defaults. 54 /// Creates a command line program, with very few defaults.
55 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated 55 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
@@ -77,9 +77,12 @@ class App { @@ -77,9 +77,12 @@ class App {
77 /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE 77 /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
78 bool allow_config_extras_{false}; 78 bool allow_config_extras_{false};
79 79
80 - /// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE 80 + /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE
81 bool prefix_command_{false}; 81 bool prefix_command_{false};
82 82
  83 + /// if set to true the name was automatically generated from the command line vs a user set name
  84 + bool has_automatic_name_{false};
  85 +
83 /// This is a function that runs when complete. Great for subcommands. Can throw. 86 /// This is a function that runs when complete. Great for subcommands. Can throw.
84 std::function<void()> callback_; 87 std::function<void()> callback_;
85 88
@@ -244,6 +247,7 @@ class App { @@ -244,6 +247,7 @@ class App {
244 /// Set a name for the app (empty will use parser to set the name) 247 /// Set a name for the app (empty will use parser to set the name)
245 App *name(std::string app_name = "") { 248 App *name(std::string app_name = "") {
246 name_ = app_name; 249 name_ = app_name;
  250 + has_automatic_name_ = false;
247 return this; 251 return this;
248 } 252 }
249 253
@@ -1124,17 +1128,29 @@ class App { @@ -1124,17 +1128,29 @@ class App {
1124 ///@{ 1128 ///@{
1125 1129
1126 /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag 1130 /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
1127 - App *add_subcommand(std::string subcommand_name, std::string description = "") {  
1128 - CLI::App_p subcom(new App(description, subcommand_name, this));  
1129 - for(const auto &subc : subcommands_)  
1130 - if(subc->check_name(subcommand_name) || subcom->check_name(subc->name_))  
1131 - throw OptionAlreadyAdded(subc->name_); 1131 + App *add_subcommand(std::string subcommand_name = "", std::string description = "") {
  1132 + CLI::App_p subcom = std::shared_ptr<App>(new App(description, subcommand_name, this));
  1133 + return add_subcommand(std::move(subcom));
  1134 + }
  1135 +
  1136 + /// Add a previously created app as a subcommand
  1137 + App *add_subcommand(CLI::App_p subcom) {
  1138 + if(!subcom)
  1139 + throw IncorrectConstruction("passed App is not valid");
  1140 + if(!subcom->name_.empty()) {
  1141 + for(const auto &subc : subcommands_)
  1142 + if(subc->check_name(subcom->name_) || subcom->check_name(subc->name_))
  1143 + throw OptionAlreadyAdded(subc->name_);
  1144 + }
  1145 + subcom->parent_ = this;
1132 subcommands_.push_back(std::move(subcom)); 1146 subcommands_.push_back(std::move(subcom));
1133 return subcommands_.back().get(); 1147 return subcommands_.back().get();
1134 } 1148 }
1135 -  
1136 /// Check to see if a subcommand is part of this command (doesn't have to be in command line) 1149 /// Check to see if a subcommand is part of this command (doesn't have to be in command line)
  1150 + /// returns the first subcommand if passed a nullptr
1137 App *get_subcommand(App *subcom) const { 1151 App *get_subcommand(App *subcom) const {
  1152 + if(subcom == nullptr)
  1153 + throw OptionNotFound("nullptr passed");
1138 for(const App_p &subcomptr : subcommands_) 1154 for(const App_p &subcomptr : subcommands_)
1139 if(subcomptr.get() == subcom) 1155 if(subcomptr.get() == subcom)
1140 return subcom; 1156 return subcom;
@@ -1148,9 +1164,41 @@ class App { @@ -1148,9 +1164,41 @@ class App {
1148 return subcomptr.get(); 1164 return subcomptr.get();
1149 throw OptionNotFound(subcom); 1165 throw OptionNotFound(subcom);
1150 } 1166 }
  1167 + /// Get a pointer to subcommand by index
  1168 + App *get_subcommand(int index = 0) const {
  1169 + if((index >= 0) && (index < subcommands_.size()))
  1170 + return subcommands_[index].get();
  1171 + throw OptionNotFound(std::to_string(index));
  1172 + }
  1173 +
  1174 + /// Check to see if a subcommand is part of this command and get a shared_ptr to it
  1175 + CLI::App_p get_subcommand_ptr(App *subcom) const {
  1176 + if(subcom == nullptr)
  1177 + throw OptionNotFound("nullptr passed");
  1178 + for(const App_p &subcomptr : subcommands_)
  1179 + if(subcomptr.get() == subcom)
  1180 + return subcomptr;
  1181 + throw OptionNotFound(subcom->get_name());
  1182 + }
  1183 +
  1184 + /// Check to see if a subcommand is part of this command (text version)
  1185 + CLI::App_p get_subcommand_ptr(std::string subcom) const {
  1186 + for(const App_p &subcomptr : subcommands_)
  1187 + if(subcomptr->check_name(subcom))
  1188 + return subcomptr;
  1189 + throw OptionNotFound(subcom);
  1190 + }
  1191 +
  1192 + /// Get an owning pointer to subcommand by index
  1193 + CLI::App_p get_subcommand_ptr(int index = 0) const {
  1194 + if((index >= 0) && (index < subcommands_.size()))
  1195 + return subcommands_[index];
  1196 + throw OptionNotFound(std::to_string(index));
  1197 + }
1151 1198
1152 /// No argument version of count counts the number of times this subcommand was 1199 /// No argument version of count counts the number of times this subcommand was
1153 - /// passed in. The main app will return 1. 1200 + /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless
  1201 + /// otherwise modified in a callback
1154 size_t count() const { return parsed_; } 1202 size_t count() const { return parsed_; }
1155 1203
1156 /// Changes the group membership 1204 /// Changes the group membership
@@ -1215,10 +1263,9 @@ class App { @@ -1215,10 +1263,9 @@ class App {
1215 /// Reset the parsed data 1263 /// Reset the parsed data
1216 void clear() { 1264 void clear() {
1217 1265
1218 - parsed_ = false; 1266 + parsed_ = 0;
1219 missing_.clear(); 1267 missing_.clear();
1220 parsed_subcommands_.clear(); 1268 parsed_subcommands_.clear();
1221 -  
1222 for(const Option_p &opt : options_) { 1269 for(const Option_p &opt : options_) {
1223 opt->clear(); 1270 opt->clear();
1224 } 1271 }
@@ -1231,8 +1278,10 @@ class App { @@ -1231,8 +1278,10 @@ class App {
1231 /// This must be called after the options are in but before the rest of the program. 1278 /// This must be called after the options are in but before the rest of the program.
1232 void parse(int argc, const char *const *argv) { 1279 void parse(int argc, const char *const *argv) {
1233 // If the name is not set, read from command line 1280 // If the name is not set, read from command line
1234 - if(name_.empty()) 1281 + if((name_.empty()) || (has_automatic_name_)) {
  1282 + has_automatic_name_ = true;
1235 name_ = argv[0]; 1283 name_ = argv[0];
  1284 + }
1236 1285
1237 std::vector<std::string> args; 1286 std::vector<std::string> args;
1238 for(int i = argc - 1; i > 0; i--) 1287 for(int i = argc - 1; i > 0; i--)
@@ -1248,7 +1297,8 @@ class App { @@ -1248,7 +1297,8 @@ class App {
1248 1297
1249 if(program_name_included) { 1298 if(program_name_included) {
1250 auto nstr = detail::split_program_name(commandline); 1299 auto nstr = detail::split_program_name(commandline);
1251 - if(name_.empty()) { 1300 + if((name_.empty()) || (has_automatic_name_)) {
  1301 + has_automatic_name_ = true;
1252 name_ = nstr.first; 1302 name_ = nstr.first;
1253 } 1303 }
1254 commandline = std::move(nstr.second); 1304 commandline = std::move(nstr.second);
@@ -1276,11 +1326,14 @@ class App { @@ -1276,11 +1326,14 @@ class App {
1276 if(parsed_ > 0) 1326 if(parsed_ > 0)
1277 clear(); 1327 clear();
1278 1328
1279 - // _parse is incremented in commands/subcommands, 1329 + // parsed_ is incremented in commands/subcommands,
1280 // but placed here to make sure this is cleared when 1330 // but placed here to make sure this is cleared when
1281 - // running parse after an error is thrown, even by _validate. 1331 + // running parse after an error is thrown, even by _validate or _configure.
1282 parsed_ = 1; 1332 parsed_ = 1;
1283 _validate(); 1333 _validate();
  1334 + _configure();
  1335 + // set the parent as nullptr as this object should be the top now
  1336 + parent_ = nullptr;
1284 parsed_ = 0; 1337 parsed_ = 0;
1285 1338
1286 _parse(args); 1339 _parse(args);
@@ -1599,10 +1652,28 @@ class App { @@ -1599,10 +1652,28 @@ class App {
1599 }); 1652 });
1600 if(pcount > 1) 1653 if(pcount > 1)
1601 throw InvalidError(name_); 1654 throw InvalidError(name_);
1602 - for(const App_p &app : subcommands_) 1655 + for(const App_p &app : subcommands_) {
1603 app->_validate(); 1656 app->_validate();
  1657 + }
1604 } 1658 }
1605 1659
  1660 + /// configure subcommands to enable parsing through the current object
  1661 + /// set the correct fallthrough and prefix for nameless subcommands and
  1662 + /// makes sure parent is set correctly
  1663 + void _configure() {
  1664 + for(const App_p &app : subcommands_) {
  1665 + if(app->has_automatic_name_) {
  1666 + app->name_.clear();
  1667 + }
  1668 + if(app->name_.empty()) {
  1669 + app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop
  1670 + app->prefix_command_ = false;
  1671 + }
  1672 + // make sure the parent is set to be this object in preparation for parse
  1673 + app->parent_ = this;
  1674 + app->_configure();
  1675 + }
  1676 + }
1606 /// Internal function to run (App) callback, top down 1677 /// Internal function to run (App) callback, top down
1607 void run_callback() { 1678 void run_callback() {
1608 pre_callback(); 1679 pre_callback();
@@ -1768,7 +1839,7 @@ class App { @@ -1768,7 +1839,7 @@ class App {
1768 // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item. 1839 // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
1769 1840
1770 for(App_p &sub : subcommands_) { 1841 for(App_p &sub : subcommands_) {
1771 - if(sub->count() > 0) 1842 + if((sub->count() > 0) || (sub->name_.empty()))
1772 sub->_process_requirements(); 1843 sub->_process_requirements();
1773 } 1844 }
1774 } 1845 }
@@ -1799,9 +1870,17 @@ class App { @@ -1799,9 +1870,17 @@ class App {
1799 } 1870 }
1800 } 1871 }
1801 1872
  1873 + /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands
  1874 + void increment_parsed() {
  1875 + ++parsed_;
  1876 + for(App_p &sub : subcommands_) {
  1877 + if(sub->get_name().empty())
  1878 + sub->increment_parsed();
  1879 + }
  1880 + }
1802 /// Internal parse function 1881 /// Internal parse function
1803 void _parse(std::vector<std::string> &args) { 1882 void _parse(std::vector<std::string> &args) {
1804 - parsed_++; 1883 + increment_parsed();
1805 bool positional_only = false; 1884 bool positional_only = false;
1806 1885
1807 while(!args.empty()) { 1886 while(!args.empty()) {
@@ -1833,13 +1912,12 @@ class App { @@ -1833,13 +1912,12 @@ class App {
1833 /// Fill in a single config option 1912 /// Fill in a single config option
1834 bool _parse_single_config(const ConfigItem &item, size_t level = 0) { 1913 bool _parse_single_config(const ConfigItem &item, size_t level = 0) {
1835 if(level < item.parents.size()) { 1914 if(level < item.parents.size()) {
1836 - App *subcom;  
1837 try { 1915 try {
1838 - subcom = get_subcommand(item.parents.at(level)); 1916 + auto subcom = get_subcommand(item.parents.at(level));
  1917 + return subcom->_parse_single_config(item, level + 1);
1839 } catch(const OptionNotFound &) { 1918 } catch(const OptionNotFound &) {
1840 return false; 1919 return false;
1841 } 1920 }
1842 - return subcom->_parse_single_config(item, level + 1);  
1843 } 1921 }
1844 1922
1845 Option *op; 1923 Option *op;
@@ -1922,6 +2000,18 @@ class App { @@ -1922,6 +2000,18 @@ class App {
1922 } 2000 }
1923 } 2001 }
1924 2002
  2003 + for(auto &subc : subcommands_) {
  2004 + if(subc->name_.empty()) {
  2005 + subc->_parse_positional(args);
  2006 + if(subc->missing_.empty()) { // check if it was used and is not in the missing category
  2007 + return;
  2008 + } else {
  2009 + args.push_back(std::move(subc->missing_.front().second));
  2010 + subc->missing_.clear();
  2011 + }
  2012 + }
  2013 + }
  2014 +
1925 if(parent_ != nullptr && fallthrough_) 2015 if(parent_ != nullptr && fallthrough_)
1926 return parent_->_parse_positional(args); 2016 return parent_->_parse_positional(args);
1927 else { 2017 else {
@@ -1997,6 +2087,17 @@ class App { @@ -1997,6 +2087,17 @@ class App {
1997 2087
1998 // Option not found 2088 // Option not found
1999 if(op_ptr == std::end(options_)) { 2089 if(op_ptr == std::end(options_)) {
  2090 + for(auto &subc : subcommands_) {
  2091 + if(subc->name_.empty()) {
  2092 + subc->_parse_arg(args, current_type);
  2093 + if(subc->missing_.empty()) { // check if it was used and is not in the missing category
  2094 + return;
  2095 + } else {
  2096 + args.push_back(std::move(subc->missing_.front().second));
  2097 + subc->missing_.clear();
  2098 + }
  2099 + }
  2100 + }
2000 // If a subcommand, try the master command 2101 // If a subcommand, try the master command
2001 if(parent_ != nullptr && fallthrough_) 2102 if(parent_ != nullptr && fallthrough_)
2002 return parent_->_parse_arg(args, current_type); 2103 return parent_->_parse_arg(args, current_type);
include/CLI/Formatter.hpp
@@ -140,6 +140,10 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod @@ -140,6 +140,10 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod
140 // Make a list in definition order of the groups seen 140 // Make a list in definition order of the groups seen
141 std::vector<std::string> subcmd_groups_seen; 141 std::vector<std::string> subcmd_groups_seen;
142 for(const App *com : subcommands) { 142 for(const App *com : subcommands) {
  143 + if(com->get_name().empty()) {
  144 + out << make_expanded(com);
  145 + continue;
  146 + }
143 std::string group_key = com->get_group(); 147 std::string group_key = com->get_group();
144 if(!group_key.empty() && 148 if(!group_key.empty() &&
145 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) { 149 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
@@ -154,6 +158,8 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod @@ -154,6 +158,8 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod
154 std::vector<const App *> subcommands_group = app->get_subcommands( 158 std::vector<const App *> subcommands_group = app->get_subcommands(
155 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); }); 159 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
156 for(const App *new_com : subcommands_group) { 160 for(const App *new_com : subcommands_group) {
  161 + if(new_com->get_name().empty())
  162 + continue;
157 if(mode != AppFormatMode::All) { 163 if(mode != AppFormatMode::All) {
158 out << make_subcommand(new_com); 164 out << make_subcommand(new_com);
159 } else { 165 } else {
tests/FormatterTest.cpp
@@ -134,3 +134,30 @@ TEST(Formatter, AllSub) { @@ -134,3 +134,30 @@ TEST(Formatter, AllSub) {
134 EXPECT_THAT(help, HasSubstr("--insub")); 134 EXPECT_THAT(help, HasSubstr("--insub"));
135 EXPECT_THAT(help, HasSubstr("subcom")); 135 EXPECT_THAT(help, HasSubstr("subcom"));
136 } 136 }
  137 +
  138 +TEST(Formatter, NamelessSub) {
  139 + CLI::App app{"My prog"};
  140 + CLI::App *sub = app.add_subcommand("", "This subcommand");
  141 + sub->add_flag("--insub", "MyFlag");
  142 +
  143 + std::string help = app.help("", CLI::AppFormatMode::Normal);
  144 + EXPECT_THAT(help, HasSubstr("--insub"));
  145 + EXPECT_THAT(help, HasSubstr("This subcommand"));
  146 +}
  147 +
  148 +TEST(Formatter, NamelessSubInGroup) {
  149 + CLI::App app{"My prog"};
  150 + CLI::App *sub = app.add_subcommand("", "This subcommand");
  151 + CLI::App *sub2 = app.add_subcommand("sub2", "subcommand2");
  152 + sub->add_flag("--insub", "MyFlag");
  153 + int val;
  154 + sub2->add_option("pos", val, "positional");
  155 + sub->group("group1");
  156 + sub2->group("group1");
  157 + std::string help = app.help("", CLI::AppFormatMode::Normal);
  158 + EXPECT_THAT(help, HasSubstr("--insub"));
  159 + EXPECT_THAT(help, HasSubstr("This subcommand"));
  160 + EXPECT_THAT(help, HasSubstr("group1"));
  161 + EXPECT_THAT(help, HasSubstr("sub2"));
  162 + EXPECT_TRUE(help.find("pos") == std::string::npos);
  163 +}
tests/SubcommandTest.cpp
@@ -223,6 +223,17 @@ TEST_F(TApp, NoFallThroughPositionals) { @@ -223,6 +223,17 @@ TEST_F(TApp, NoFallThroughPositionals) {
223 EXPECT_THROW(run(), CLI::ExtrasError); 223 EXPECT_THROW(run(), CLI::ExtrasError);
224 } 224 }
225 225
  226 +TEST_F(TApp, NamelessSubComPositionals) {
  227 +
  228 + auto sub = app.add_subcommand();
  229 + int val = 1;
  230 + sub->add_option("val", val);
  231 +
  232 + args = {"2"};
  233 + run();
  234 + EXPECT_EQ(val, 2);
  235 +}
  236 +
226 TEST_F(TApp, FallThroughRegular) { 237 TEST_F(TApp, FallThroughRegular) {
227 app.fallthrough(); 238 app.fallthrough();
228 int val = 1; 239 int val = 1;
@@ -365,6 +376,7 @@ TEST_F(TApp, BadSubcomSearch) { @@ -365,6 +376,7 @@ TEST_F(TApp, BadSubcomSearch) {
365 auto two = one->add_subcommand("two"); 376 auto two = one->add_subcommand("two");
366 377
367 EXPECT_THROW(app.get_subcommand(two), CLI::OptionNotFound); 378 EXPECT_THROW(app.get_subcommand(two), CLI::OptionNotFound);
  379 + EXPECT_THROW(app.get_subcommand_ptr(two), CLI::OptionNotFound);
368 } 380 }
369 381
370 TEST_F(TApp, PrefixProgram) { 382 TEST_F(TApp, PrefixProgram) {
@@ -732,6 +744,32 @@ TEST_F(ManySubcommands, Required4Failure) { @@ -732,6 +744,32 @@ TEST_F(ManySubcommands, Required4Failure) {
732 EXPECT_THROW(run(), CLI::RequiredError); 744 EXPECT_THROW(run(), CLI::RequiredError);
733 } 745 }
734 746
  747 +TEST_F(ManySubcommands, manyIndexQuery) {
  748 + auto s1 = app.get_subcommand(0);
  749 + auto s2 = app.get_subcommand(1);
  750 + auto s3 = app.get_subcommand(2);
  751 + auto s4 = app.get_subcommand(3);
  752 + EXPECT_EQ(s1, sub1);
  753 + EXPECT_EQ(s2, sub2);
  754 + EXPECT_EQ(s3, sub3);
  755 + EXPECT_EQ(s4, sub4);
  756 + EXPECT_THROW(app.get_subcommand(4), CLI::OptionNotFound);
  757 + auto s0 = app.get_subcommand();
  758 + EXPECT_EQ(s0, sub1);
  759 +}
  760 +
  761 +TEST_F(ManySubcommands, manyIndexQueryPtr) {
  762 + auto s1 = app.get_subcommand_ptr(0);
  763 + auto s2 = app.get_subcommand_ptr(1);
  764 + auto s3 = app.get_subcommand_ptr(2);
  765 + auto s4 = app.get_subcommand_ptr(3);
  766 + EXPECT_EQ(s1.get(), sub1);
  767 + EXPECT_EQ(s2.get(), sub2);
  768 + EXPECT_EQ(s3.get(), sub3);
  769 + EXPECT_EQ(s4.get(), sub4);
  770 + EXPECT_THROW(app.get_subcommand_ptr(4), CLI::OptionNotFound);
  771 +}
  772 +
735 TEST_F(ManySubcommands, Required1Fuzzy) { 773 TEST_F(ManySubcommands, Required1Fuzzy) {
736 774
737 app.require_subcommand(0, 1); 775 app.require_subcommand(0, 1);
@@ -814,3 +852,154 @@ TEST_F(ManySubcommands, MaxCommands) { @@ -814,3 +852,154 @@ TEST_F(ManySubcommands, MaxCommands) {
814 args = {"sub1", "sub2", "sub3"}; 852 args = {"sub1", "sub2", "sub3"};
815 EXPECT_THROW(run(), CLI::ExtrasError); 853 EXPECT_THROW(run(), CLI::ExtrasError);
816 } 854 }
  855 +
  856 +TEST_F(TApp, UnnamedSub) {
  857 + double val;
  858 + auto sub = app.add_subcommand("", "empty name");
  859 + sub->add_option("-v,--value", val);
  860 + args = {"-v", "4.56"};
  861 +
  862 + run();
  863 + EXPECT_EQ(val, 4.56);
  864 +}
  865 +
  866 +TEST_F(TApp, UnnamedSubMix) {
  867 + double val, val2, val3;
  868 + app.add_option("-t", val2);
  869 + auto sub1 = app.add_subcommand("", "empty name");
  870 + sub1->add_option("-v,--value", val);
  871 + auto sub2 = app.add_subcommand("", "empty name2");
  872 + sub2->add_option("-m,--mix", val3);
  873 + args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
  874 +
  875 + run();
  876 + EXPECT_EQ(val, -3.0);
  877 + EXPECT_EQ(val2, 5.93);
  878 + EXPECT_EQ(val3, 4.56);
  879 +}
  880 +
  881 +TEST_F(TApp, UnnamedSubMixExtras) {
  882 + double val, val2;
  883 + app.add_option("-t", val2);
  884 + auto sub = app.add_subcommand("", "empty name");
  885 + sub->add_option("-v,--value", val);
  886 + args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
  887 + app.allow_extras();
  888 + run();
  889 + EXPECT_EQ(val, -3.0);
  890 + EXPECT_EQ(val2, 5.93);
  891 + EXPECT_EQ(app.remaining_size(), 2);
  892 + EXPECT_EQ(sub->remaining_size(), 0);
  893 +}
  894 +
  895 +TEST_F(TApp, UnnamedSubNoExtras) {
  896 + double val, val2;
  897 + app.add_option("-t", val2);
  898 + auto sub = app.add_subcommand();
  899 + sub->add_option("-v,--value", val);
  900 + args = {"-t", "5.93", "-v", "-3"};
  901 + run();
  902 + EXPECT_EQ(val, -3.0);
  903 + EXPECT_EQ(val2, 5.93);
  904 + EXPECT_EQ(app.remaining_size(), 0);
  905 + EXPECT_EQ(sub->remaining_size(), 0);
  906 +}
  907 +
  908 +TEST(SharedSubTests, SharedSubcommand) {
  909 + double val, val2, val3, val4;
  910 + CLI::App app1{"test program1"};
  911 +
  912 + app1.add_option("-t", val2);
  913 + auto sub = app1.add_subcommand("", "empty name");
  914 + sub->add_option("-v,--value", val);
  915 + sub->add_option("-g", val4);
  916 + CLI::App app2{"test program2"};
  917 + app2.add_option("-m", val3);
  918 + // extract an owning ptr from app1 and add it to app2
  919 + auto subown = app1.get_subcommand_ptr(sub);
  920 + // add the extracted subcommand to a different app
  921 + app2.add_subcommand(std::move(subown));
  922 + EXPECT_THROW(app2.add_subcommand(CLI::App_p{}), CLI::IncorrectConstruction);
  923 + input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
  924 + input_t args2 = {"-m", "4.56", "-g", "8.235"};
  925 + std::reverse(std::begin(args1), std::end(args1));
  926 + std::reverse(std::begin(args2), std::end(args2));
  927 + app1.allow_extras();
  928 + app1.parse(args1);
  929 +
  930 + app2.parse(args2);
  931 +
  932 + EXPECT_EQ(val, -3.0);
  933 + EXPECT_EQ(val2, 5.93);
  934 + EXPECT_EQ(val3, 4.56);
  935 + EXPECT_EQ(val4, 8.235);
  936 +}
  937 +
  938 +TEST(SharedSubTests, SharedSubIndependent) {
  939 + double val, val2, val4;
  940 + CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
  941 + app1->allow_extras();
  942 + app1->add_option("-t", val2);
  943 + auto sub = app1->add_subcommand("", "empty name");
  944 + sub->add_option("-v,--value", val);
  945 + sub->add_option("-g", val4);
  946 +
  947 + // extract an owning ptr from app1 and add it to app2
  948 + auto subown = app1->get_subcommand_ptr(sub);
  949 +
  950 + input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
  951 + input_t args2 = {"-m", "4.56", "-g", "8.235"};
  952 + std::reverse(std::begin(args1), std::end(args1));
  953 + std::reverse(std::begin(args2), std::end(args2));
  954 +
  955 + app1->parse(args1);
  956 + // destroy the first parser
  957 + app1 = nullptr;
  958 + // parse with the extracted subcommand
  959 + subown->parse(args2);
  960 +
  961 + EXPECT_EQ(val, -3.0);
  962 + EXPECT_EQ(val2, 5.93);
  963 + EXPECT_EQ(val4, 8.235);
  964 +}
  965 +
  966 +TEST(SharedSubTests, SharedSubIndependentReuse) {
  967 + double val, val2, val4;
  968 + CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
  969 + app1->allow_extras();
  970 + app1->add_option("-t", val2);
  971 + auto sub = app1->add_subcommand("", "empty name");
  972 + sub->add_option("-v,--value", val);
  973 + sub->add_option("-g", val4);
  974 +
  975 + // extract an owning ptr from app1 and add it to app2
  976 + auto subown = app1->get_subcommand_ptr(sub);
  977 +
  978 + input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
  979 + std::reverse(std::begin(args1), std::end(args1));
  980 + auto args2 = args1;
  981 + app1->parse(args1);
  982 +
  983 + // parse with the extracted subcommand
  984 + subown->parse("program1 -m 4.56 -g 8.235", true);
  985 +
  986 + EXPECT_EQ(val, -3.0);
  987 + EXPECT_EQ(val2, 5.93);
  988 + EXPECT_EQ(val4, 8.235);
  989 + val = 0.0;
  990 + val2 = 0.0;
  991 + EXPECT_EQ(subown->get_name(), "program1");
  992 + // this tests the name reset in subcommand since it was automatic
  993 + app1->parse(args2);
  994 + EXPECT_EQ(val, -3.0);
  995 + EXPECT_EQ(val2, 5.93);
  996 +}
  997 +
  998 +TEST_F(ManySubcommands, getSubtests) {
  999 + CLI::App_p sub2p = app.get_subcommand_ptr(sub2);
  1000 + EXPECT_EQ(sub2p.get(), sub2);
  1001 + EXPECT_THROW(app.get_subcommand_ptr(nullptr), CLI::OptionNotFound);
  1002 + EXPECT_THROW(app.get_subcommand(nullptr), CLI::OptionNotFound);
  1003 + CLI::App_p sub3p = app.get_subcommand_ptr(2);
  1004 + EXPECT_EQ(sub3p.get(), sub3);
  1005 +}