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 71 - Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later).
72 72 - Ability to add a configuration file (`ini` format), and produce it as well.
73 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 75 - Permissively licensed.
76 76  
77 77 ### Other parsers
... ... @@ -92,7 +92,7 @@ After I wrote this, I also found the following libraries:
92 92 | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
93 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 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 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 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 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 239 - `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
240 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 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 243 - `->description(str)`: Set/change the description.
244 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 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 285 case).
286 286  
287 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 289 subcommand name from matching.
290 290  
291 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 296 You are allowed to throw `CLI::Success` in the callbacks.
297 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 302 #### Subcommand options
300 303  
301 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 310 - `.require_subcommand()`: Require 1 or more subcommands.
308 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 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 315 - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
312 316 - `.get_subcommands(filter)`: The list of subcommands given on the command line.
313 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 63 "Subcommand: start"
64 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 84 add_cli_exe(validators validators.cpp)
67 85 add_test(NAME validators_help COMMAND validators --help)
68 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 49  
50 50 class App;
51 51  
52   -using App_p = std::unique_ptr<App>;
  52 +using App_p = std::shared_ptr<App>;
53 53  
54 54 /// Creates a command line program, with very few defaults.
55 55 /** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
... ... @@ -77,9 +77,12 @@ class App {
77 77 /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
78 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 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 86 /// This is a function that runs when complete. Great for subcommands. Can throw.
84 87 std::function<void()> callback_;
85 88  
... ... @@ -244,6 +247,7 @@ class App {
244 247 /// Set a name for the app (empty will use parser to set the name)
245 248 App *name(std::string app_name = "") {
246 249 name_ = app_name;
  250 + has_automatic_name_ = false;
247 251 return this;
248 252 }
249 253  
... ... @@ -1124,17 +1128,29 @@ class App {
1124 1128 ///@{
1125 1129  
1126 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 1146 subcommands_.push_back(std::move(subcom));
1133 1147 return subcommands_.back().get();
1134 1148 }
1135   -
1136 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 1151 App *get_subcommand(App *subcom) const {
  1152 + if(subcom == nullptr)
  1153 + throw OptionNotFound("nullptr passed");
1138 1154 for(const App_p &subcomptr : subcommands_)
1139 1155 if(subcomptr.get() == subcom)
1140 1156 return subcom;
... ... @@ -1148,9 +1164,41 @@ class App {
1148 1164 return subcomptr.get();
1149 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 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 1202 size_t count() const { return parsed_; }
1155 1203  
1156 1204 /// Changes the group membership
... ... @@ -1215,10 +1263,9 @@ class App {
1215 1263 /// Reset the parsed data
1216 1264 void clear() {
1217 1265  
1218   - parsed_ = false;
  1266 + parsed_ = 0;
1219 1267 missing_.clear();
1220 1268 parsed_subcommands_.clear();
1221   -
1222 1269 for(const Option_p &opt : options_) {
1223 1270 opt->clear();
1224 1271 }
... ... @@ -1231,8 +1278,10 @@ class App {
1231 1278 /// This must be called after the options are in but before the rest of the program.
1232 1279 void parse(int argc, const char *const *argv) {
1233 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 1283 name_ = argv[0];
  1284 + }
1236 1285  
1237 1286 std::vector<std::string> args;
1238 1287 for(int i = argc - 1; i > 0; i--)
... ... @@ -1248,7 +1297,8 @@ class App {
1248 1297  
1249 1298 if(program_name_included) {
1250 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 1302 name_ = nstr.first;
1253 1303 }
1254 1304 commandline = std::move(nstr.second);
... ... @@ -1276,11 +1326,14 @@ class App {
1276 1326 if(parsed_ > 0)
1277 1327 clear();
1278 1328  
1279   - // _parse is incremented in commands/subcommands,
  1329 + // parsed_ is incremented in commands/subcommands,
1280 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 1332 parsed_ = 1;
1283 1333 _validate();
  1334 + _configure();
  1335 + // set the parent as nullptr as this object should be the top now
  1336 + parent_ = nullptr;
1284 1337 parsed_ = 0;
1285 1338  
1286 1339 _parse(args);
... ... @@ -1599,10 +1652,28 @@ class App {
1599 1652 });
1600 1653 if(pcount > 1)
1601 1654 throw InvalidError(name_);
1602   - for(const App_p &app : subcommands_)
  1655 + for(const App_p &app : subcommands_) {
1603 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 1677 /// Internal function to run (App) callback, top down
1607 1678 void run_callback() {
1608 1679 pre_callback();
... ... @@ -1768,7 +1839,7 @@ class App {
1768 1839 // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
1769 1840  
1770 1841 for(App_p &sub : subcommands_) {
1771   - if(sub->count() > 0)
  1842 + if((sub->count() > 0) || (sub->name_.empty()))
1772 1843 sub->_process_requirements();
1773 1844 }
1774 1845 }
... ... @@ -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 1881 /// Internal parse function
1803 1882 void _parse(std::vector<std::string> &args) {
1804   - parsed_++;
  1883 + increment_parsed();
1805 1884 bool positional_only = false;
1806 1885  
1807 1886 while(!args.empty()) {
... ... @@ -1833,13 +1912,12 @@ class App {
1833 1912 /// Fill in a single config option
1834 1913 bool _parse_single_config(const ConfigItem &item, size_t level = 0) {
1835 1914 if(level < item.parents.size()) {
1836   - App *subcom;
1837 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 1918 } catch(const OptionNotFound &) {
1840 1919 return false;
1841 1920 }
1842   - return subcom->_parse_single_config(item, level + 1);
1843 1921 }
1844 1922  
1845 1923 Option *op;
... ... @@ -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 2015 if(parent_ != nullptr && fallthrough_)
1926 2016 return parent_->_parse_positional(args);
1927 2017 else {
... ... @@ -1997,6 +2087,17 @@ class App {
1997 2087  
1998 2088 // Option not found
1999 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 2101 // If a subcommand, try the master command
2001 2102 if(parent_ != nullptr && fallthrough_)
2002 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 140 // Make a list in definition order of the groups seen
141 141 std::vector<std::string> subcmd_groups_seen;
142 142 for(const App *com : subcommands) {
  143 + if(com->get_name().empty()) {
  144 + out << make_expanded(com);
  145 + continue;
  146 + }
143 147 std::string group_key = com->get_group();
144 148 if(!group_key.empty() &&
145 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 158 std::vector<const App *> subcommands_group = app->get_subcommands(
155 159 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
156 160 for(const App *new_com : subcommands_group) {
  161 + if(new_com->get_name().empty())
  162 + continue;
157 163 if(mode != AppFormatMode::All) {
158 164 out << make_subcommand(new_com);
159 165 } else {
... ...
tests/FormatterTest.cpp
... ... @@ -134,3 +134,30 @@ TEST(Formatter, AllSub) {
134 134 EXPECT_THAT(help, HasSubstr("--insub"));
135 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 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 237 TEST_F(TApp, FallThroughRegular) {
227 238 app.fallthrough();
228 239 int val = 1;
... ... @@ -365,6 +376,7 @@ TEST_F(TApp, BadSubcomSearch) {
365 376 auto two = one->add_subcommand("two");
366 377  
367 378 EXPECT_THROW(app.get_subcommand(two), CLI::OptionNotFound);
  379 + EXPECT_THROW(app.get_subcommand_ptr(two), CLI::OptionNotFound);
368 380 }
369 381  
370 382 TEST_F(TApp, PrefixProgram) {
... ... @@ -732,6 +744,32 @@ TEST_F(ManySubcommands, Required4Failure) {
732 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 773 TEST_F(ManySubcommands, Required1Fuzzy) {
736 774  
737 775 app.require_subcommand(0, 1);
... ... @@ -814,3 +852,154 @@ TEST_F(ManySubcommands, MaxCommands) {
814 852 args = {"sub1", "sub2", "sub3"};
815 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 +}
... ...