1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00

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.
This commit is contained in:
Philip Top 2019-02-06 01:27:06 -08:00 committed by Henry Schreiner
parent b4910df3d7
commit 598046c397
7 changed files with 408 additions and 26 deletions

View File

@ -71,7 +71,7 @@ An acceptable CLI parser library should be all of the following:
- Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later).
- Ability to add a configuration file (`ini` format), and produce it as well.
- Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications.
- Work with standard types, simple custom types, and extendible to exotic types.
- Work with standard types, simple custom types, and extensible to exotic types.
- Permissively licensed.
### Other parsers
@ -92,7 +92,7 @@ After I wrote this, I also found the following libraries:
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [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. |
| [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). |
| [ProgramOptions.hxx][] | Intresting library, less powerful and no subcommands. Nice callback system. |
| [ProgramOptions.hxx][] | Interesting library, less powerful and no subcommands. Nice callback system. |
| [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. |
| [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. |
| [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:
- `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
- `->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).
- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
- `->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
- `->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
- `->description(str)`: Set/change the description.
- `->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).
- `->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
case).
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.
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
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
subcommand name from matching.
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,
You are allowed to throw `CLI::Success` in the callbacks.
Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).
Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
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.
#### Subcommand options
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
- `.require_subcommand()`: Require 1 or more subcommands.
- `.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.
- `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
- `.add_subcommand(name, description="")` Add a subcommand, returns a pointer to the internally stored subcommand.
- `.add_subcommand(name="", description="")` Add a subcommand, returns a pointer to the internally stored subcommand.
- `.add_subcommand(shared_ptr<App>)` Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
- `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
- `.get_subcommands(filter)`: The list of subcommands given on the command line.
- `.get_parent()`: Get the parent App or nullptr if called on master App.

View File

@ -63,6 +63,24 @@ set_property(TEST subcommands_all PROPERTY PASS_REGULAR_EXPRESSION
"Subcommand: start"
"Subcommand: stop")
add_cli_exe(subcom_partitioned subcom_partitioned.cpp)
add_test(NAME subcom_partitioned_none COMMAND subcom_partitioned)
set_property(TEST subcom_partitioned_none PROPERTY PASS_REGULAR_EXPRESSION
"This is a timer:"
"--file is required"
"Run with --help for more information.")
add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2)
set_property(TEST subcom_partitioned_all PROPERTY PASS_REGULAR_EXPRESSION
"This is a timer:"
"Working on file: this, direct count: 1, opt count: 1"
"Working on count: 2, direct count: 2, opt count: 2"
"Some value: 1.2")
# test shows that the help prints out for unnamed subcommands
add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help)
set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION
"-f,--file TEXT REQUIRED"
"-d,--double FLOAT")
add_cli_exe(validators validators.cpp)
add_test(NAME validators_help COMMAND validators --help)
set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION

View File

@ -0,0 +1,37 @@
#include "CLI/CLI.hpp"
#include "CLI/Timer.hpp"
int main(int argc, char **argv) {
CLI::AutoTimer("This is a timer");
CLI::App app("K3Pi goofit fitter");
CLI::App_p impOpt = std::make_shared<CLI::App>("Important");
std::string file;
CLI::Option *opt = impOpt->add_option("-f,--file,file", file, "File name")->required();
int count;
CLI::Option *copt = impOpt->add_flag("-c,--count", count, "Counter")->required();
CLI::App_p otherOpt = std::make_shared<CLI::App>("Other");
double value; // = 3.14;
otherOpt->add_option("-d,--double", value, "Some Value");
// add the subapps to the main one
app.add_subcommand(impOpt);
app.add_subcommand(otherOpt);
try {
app.parse(argc, argv);
} catch(const CLI::ParseError &e) {
return app.exit(e);
}
std::cout << "Working on file: " << file << ", direct count: " << impOpt->count("--file")
<< ", opt count: " << opt->count() << std::endl;
std::cout << "Working on count: " << count << ", direct count: " << impOpt->count("--count")
<< ", opt count: " << copt->count() << std::endl;
std::cout << "Some value: " << value << std::endl;
return 0;
}

View File

@ -49,7 +49,7 @@ std::string help(const App *app, const Error &e);
class App;
using App_p = std::unique_ptr<App>;
using App_p = std::shared_ptr<App>;
/// Creates a command line program, with very few defaults.
/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
@ -77,9 +77,12 @@ class App {
/// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
bool allow_config_extras_{false};
/// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE
/// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE
bool prefix_command_{false};
/// if set to true the name was automatically generated from the command line vs a user set name
bool has_automatic_name_{false};
/// This is a function that runs when complete. Great for subcommands. Can throw.
std::function<void()> callback_;
@ -244,6 +247,7 @@ class App {
/// Set a name for the app (empty will use parser to set the name)
App *name(std::string app_name = "") {
name_ = app_name;
has_automatic_name_ = false;
return this;
}
@ -1124,17 +1128,29 @@ class App {
///@{
/// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
App *add_subcommand(std::string subcommand_name, std::string description = "") {
CLI::App_p subcom(new App(description, subcommand_name, this));
App *add_subcommand(std::string subcommand_name = "", std::string description = "") {
CLI::App_p subcom = std::shared_ptr<App>(new App(description, subcommand_name, this));
return add_subcommand(std::move(subcom));
}
/// Add a previously created app as a subcommand
App *add_subcommand(CLI::App_p subcom) {
if(!subcom)
throw IncorrectConstruction("passed App is not valid");
if(!subcom->name_.empty()) {
for(const auto &subc : subcommands_)
if(subc->check_name(subcommand_name) || subcom->check_name(subc->name_))
if(subc->check_name(subcom->name_) || subcom->check_name(subc->name_))
throw OptionAlreadyAdded(subc->name_);
}
subcom->parent_ = this;
subcommands_.push_back(std::move(subcom));
return subcommands_.back().get();
}
/// Check to see if a subcommand is part of this command (doesn't have to be in command line)
/// returns the first subcommand if passed a nullptr
App *get_subcommand(App *subcom) const {
if(subcom == nullptr)
throw OptionNotFound("nullptr passed");
for(const App_p &subcomptr : subcommands_)
if(subcomptr.get() == subcom)
return subcom;
@ -1148,9 +1164,41 @@ class App {
return subcomptr.get();
throw OptionNotFound(subcom);
}
/// Get a pointer to subcommand by index
App *get_subcommand(int index = 0) const {
if((index >= 0) && (index < subcommands_.size()))
return subcommands_[index].get();
throw OptionNotFound(std::to_string(index));
}
/// Check to see if a subcommand is part of this command and get a shared_ptr to it
CLI::App_p get_subcommand_ptr(App *subcom) const {
if(subcom == nullptr)
throw OptionNotFound("nullptr passed");
for(const App_p &subcomptr : subcommands_)
if(subcomptr.get() == subcom)
return subcomptr;
throw OptionNotFound(subcom->get_name());
}
/// Check to see if a subcommand is part of this command (text version)
CLI::App_p get_subcommand_ptr(std::string subcom) const {
for(const App_p &subcomptr : subcommands_)
if(subcomptr->check_name(subcom))
return subcomptr;
throw OptionNotFound(subcom);
}
/// Get an owning pointer to subcommand by index
CLI::App_p get_subcommand_ptr(int index = 0) const {
if((index >= 0) && (index < subcommands_.size()))
return subcommands_[index];
throw OptionNotFound(std::to_string(index));
}
/// No argument version of count counts the number of times this subcommand was
/// passed in. The main app will return 1.
/// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless
/// otherwise modified in a callback
size_t count() const { return parsed_; }
/// Changes the group membership
@ -1215,10 +1263,9 @@ class App {
/// Reset the parsed data
void clear() {
parsed_ = false;
parsed_ = 0;
missing_.clear();
parsed_subcommands_.clear();
for(const Option_p &opt : options_) {
opt->clear();
}
@ -1231,8 +1278,10 @@ class App {
/// This must be called after the options are in but before the rest of the program.
void parse(int argc, const char *const *argv) {
// If the name is not set, read from command line
if(name_.empty())
if((name_.empty()) || (has_automatic_name_)) {
has_automatic_name_ = true;
name_ = argv[0];
}
std::vector<std::string> args;
for(int i = argc - 1; i > 0; i--)
@ -1248,7 +1297,8 @@ class App {
if(program_name_included) {
auto nstr = detail::split_program_name(commandline);
if(name_.empty()) {
if((name_.empty()) || (has_automatic_name_)) {
has_automatic_name_ = true;
name_ = nstr.first;
}
commandline = std::move(nstr.second);
@ -1276,11 +1326,14 @@ class App {
if(parsed_ > 0)
clear();
// _parse is incremented in commands/subcommands,
// parsed_ is incremented in commands/subcommands,
// but placed here to make sure this is cleared when
// running parse after an error is thrown, even by _validate.
// running parse after an error is thrown, even by _validate or _configure.
parsed_ = 1;
_validate();
_configure();
// set the parent as nullptr as this object should be the top now
parent_ = nullptr;
parsed_ = 0;
_parse(args);
@ -1599,10 +1652,28 @@ class App {
});
if(pcount > 1)
throw InvalidError(name_);
for(const App_p &app : subcommands_)
for(const App_p &app : subcommands_) {
app->_validate();
}
}
/// configure subcommands to enable parsing through the current object
/// set the correct fallthrough and prefix for nameless subcommands and
/// makes sure parent is set correctly
void _configure() {
for(const App_p &app : subcommands_) {
if(app->has_automatic_name_) {
app->name_.clear();
}
if(app->name_.empty()) {
app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop
app->prefix_command_ = false;
}
// make sure the parent is set to be this object in preparation for parse
app->parent_ = this;
app->_configure();
}
}
/// Internal function to run (App) callback, top down
void run_callback() {
pre_callback();
@ -1768,7 +1839,7 @@ class App {
// Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
for(App_p &sub : subcommands_) {
if(sub->count() > 0)
if((sub->count() > 0) || (sub->name_.empty()))
sub->_process_requirements();
}
}
@ -1799,9 +1870,17 @@ class App {
}
}
/// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands
void increment_parsed() {
++parsed_;
for(App_p &sub : subcommands_) {
if(sub->get_name().empty())
sub->increment_parsed();
}
}
/// Internal parse function
void _parse(std::vector<std::string> &args) {
parsed_++;
increment_parsed();
bool positional_only = false;
while(!args.empty()) {
@ -1833,13 +1912,12 @@ class App {
/// Fill in a single config option
bool _parse_single_config(const ConfigItem &item, size_t level = 0) {
if(level < item.parents.size()) {
App *subcom;
try {
subcom = get_subcommand(item.parents.at(level));
auto subcom = get_subcommand(item.parents.at(level));
return subcom->_parse_single_config(item, level + 1);
} catch(const OptionNotFound &) {
return false;
}
return subcom->_parse_single_config(item, level + 1);
}
Option *op;
@ -1922,6 +2000,18 @@ class App {
}
}
for(auto &subc : subcommands_) {
if(subc->name_.empty()) {
subc->_parse_positional(args);
if(subc->missing_.empty()) { // check if it was used and is not in the missing category
return;
} else {
args.push_back(std::move(subc->missing_.front().second));
subc->missing_.clear();
}
}
}
if(parent_ != nullptr && fallthrough_)
return parent_->_parse_positional(args);
else {
@ -1997,6 +2087,17 @@ class App {
// Option not found
if(op_ptr == std::end(options_)) {
for(auto &subc : subcommands_) {
if(subc->name_.empty()) {
subc->_parse_arg(args, current_type);
if(subc->missing_.empty()) { // check if it was used and is not in the missing category
return;
} else {
args.push_back(std::move(subc->missing_.front().second));
subc->missing_.clear();
}
}
}
// If a subcommand, try the master command
if(parent_ != nullptr && fallthrough_)
return parent_->_parse_arg(args, current_type);

View File

@ -140,6 +140,10 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod
// Make a list in definition order of the groups seen
std::vector<std::string> subcmd_groups_seen;
for(const App *com : subcommands) {
if(com->get_name().empty()) {
out << make_expanded(com);
continue;
}
std::string group_key = com->get_group();
if(!group_key.empty() &&
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
std::vector<const App *> subcommands_group = app->get_subcommands(
[&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
for(const App *new_com : subcommands_group) {
if(new_com->get_name().empty())
continue;
if(mode != AppFormatMode::All) {
out << make_subcommand(new_com);
} else {

View File

@ -134,3 +134,30 @@ TEST(Formatter, AllSub) {
EXPECT_THAT(help, HasSubstr("--insub"));
EXPECT_THAT(help, HasSubstr("subcom"));
}
TEST(Formatter, NamelessSub) {
CLI::App app{"My prog"};
CLI::App *sub = app.add_subcommand("", "This subcommand");
sub->add_flag("--insub", "MyFlag");
std::string help = app.help("", CLI::AppFormatMode::Normal);
EXPECT_THAT(help, HasSubstr("--insub"));
EXPECT_THAT(help, HasSubstr("This subcommand"));
}
TEST(Formatter, NamelessSubInGroup) {
CLI::App app{"My prog"};
CLI::App *sub = app.add_subcommand("", "This subcommand");
CLI::App *sub2 = app.add_subcommand("sub2", "subcommand2");
sub->add_flag("--insub", "MyFlag");
int val;
sub2->add_option("pos", val, "positional");
sub->group("group1");
sub2->group("group1");
std::string help = app.help("", CLI::AppFormatMode::Normal);
EXPECT_THAT(help, HasSubstr("--insub"));
EXPECT_THAT(help, HasSubstr("This subcommand"));
EXPECT_THAT(help, HasSubstr("group1"));
EXPECT_THAT(help, HasSubstr("sub2"));
EXPECT_TRUE(help.find("pos") == std::string::npos);
}

View File

@ -223,6 +223,17 @@ TEST_F(TApp, NoFallThroughPositionals) {
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, NamelessSubComPositionals) {
auto sub = app.add_subcommand();
int val = 1;
sub->add_option("val", val);
args = {"2"};
run();
EXPECT_EQ(val, 2);
}
TEST_F(TApp, FallThroughRegular) {
app.fallthrough();
int val = 1;
@ -365,6 +376,7 @@ TEST_F(TApp, BadSubcomSearch) {
auto two = one->add_subcommand("two");
EXPECT_THROW(app.get_subcommand(two), CLI::OptionNotFound);
EXPECT_THROW(app.get_subcommand_ptr(two), CLI::OptionNotFound);
}
TEST_F(TApp, PrefixProgram) {
@ -732,6 +744,32 @@ TEST_F(ManySubcommands, Required4Failure) {
EXPECT_THROW(run(), CLI::RequiredError);
}
TEST_F(ManySubcommands, manyIndexQuery) {
auto s1 = app.get_subcommand(0);
auto s2 = app.get_subcommand(1);
auto s3 = app.get_subcommand(2);
auto s4 = app.get_subcommand(3);
EXPECT_EQ(s1, sub1);
EXPECT_EQ(s2, sub2);
EXPECT_EQ(s3, sub3);
EXPECT_EQ(s4, sub4);
EXPECT_THROW(app.get_subcommand(4), CLI::OptionNotFound);
auto s0 = app.get_subcommand();
EXPECT_EQ(s0, sub1);
}
TEST_F(ManySubcommands, manyIndexQueryPtr) {
auto s1 = app.get_subcommand_ptr(0);
auto s2 = app.get_subcommand_ptr(1);
auto s3 = app.get_subcommand_ptr(2);
auto s4 = app.get_subcommand_ptr(3);
EXPECT_EQ(s1.get(), sub1);
EXPECT_EQ(s2.get(), sub2);
EXPECT_EQ(s3.get(), sub3);
EXPECT_EQ(s4.get(), sub4);
EXPECT_THROW(app.get_subcommand_ptr(4), CLI::OptionNotFound);
}
TEST_F(ManySubcommands, Required1Fuzzy) {
app.require_subcommand(0, 1);
@ -814,3 +852,154 @@ TEST_F(ManySubcommands, MaxCommands) {
args = {"sub1", "sub2", "sub3"};
EXPECT_THROW(run(), CLI::ExtrasError);
}
TEST_F(TApp, UnnamedSub) {
double val;
auto sub = app.add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
args = {"-v", "4.56"};
run();
EXPECT_EQ(val, 4.56);
}
TEST_F(TApp, UnnamedSubMix) {
double val, val2, val3;
app.add_option("-t", val2);
auto sub1 = app.add_subcommand("", "empty name");
sub1->add_option("-v,--value", val);
auto sub2 = app.add_subcommand("", "empty name2");
sub2->add_option("-m,--mix", val3);
args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val3, 4.56);
}
TEST_F(TApp, UnnamedSubMixExtras) {
double val, val2;
app.add_option("-t", val2);
auto sub = app.add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
app.allow_extras();
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(app.remaining_size(), 2);
EXPECT_EQ(sub->remaining_size(), 0);
}
TEST_F(TApp, UnnamedSubNoExtras) {
double val, val2;
app.add_option("-t", val2);
auto sub = app.add_subcommand();
sub->add_option("-v,--value", val);
args = {"-t", "5.93", "-v", "-3"};
run();
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(app.remaining_size(), 0);
EXPECT_EQ(sub->remaining_size(), 0);
}
TEST(SharedSubTests, SharedSubcommand) {
double val, val2, val3, val4;
CLI::App app1{"test program1"};
app1.add_option("-t", val2);
auto sub = app1.add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
sub->add_option("-g", val4);
CLI::App app2{"test program2"};
app2.add_option("-m", val3);
// extract an owning ptr from app1 and add it to app2
auto subown = app1.get_subcommand_ptr(sub);
// add the extracted subcommand to a different app
app2.add_subcommand(std::move(subown));
EXPECT_THROW(app2.add_subcommand(CLI::App_p{}), CLI::IncorrectConstruction);
input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
input_t args2 = {"-m", "4.56", "-g", "8.235"};
std::reverse(std::begin(args1), std::end(args1));
std::reverse(std::begin(args2), std::end(args2));
app1.allow_extras();
app1.parse(args1);
app2.parse(args2);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val3, 4.56);
EXPECT_EQ(val4, 8.235);
}
TEST(SharedSubTests, SharedSubIndependent) {
double val, val2, val4;
CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
app1->allow_extras();
app1->add_option("-t", val2);
auto sub = app1->add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
sub->add_option("-g", val4);
// extract an owning ptr from app1 and add it to app2
auto subown = app1->get_subcommand_ptr(sub);
input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
input_t args2 = {"-m", "4.56", "-g", "8.235"};
std::reverse(std::begin(args1), std::end(args1));
std::reverse(std::begin(args2), std::end(args2));
app1->parse(args1);
// destroy the first parser
app1 = nullptr;
// parse with the extracted subcommand
subown->parse(args2);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val4, 8.235);
}
TEST(SharedSubTests, SharedSubIndependentReuse) {
double val, val2, val4;
CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
app1->allow_extras();
app1->add_option("-t", val2);
auto sub = app1->add_subcommand("", "empty name");
sub->add_option("-v,--value", val);
sub->add_option("-g", val4);
// extract an owning ptr from app1 and add it to app2
auto subown = app1->get_subcommand_ptr(sub);
input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
std::reverse(std::begin(args1), std::end(args1));
auto args2 = args1;
app1->parse(args1);
// parse with the extracted subcommand
subown->parse("program1 -m 4.56 -g 8.235", true);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
EXPECT_EQ(val4, 8.235);
val = 0.0;
val2 = 0.0;
EXPECT_EQ(subown->get_name(), "program1");
// this tests the name reset in subcommand since it was automatic
app1->parse(args2);
EXPECT_EQ(val, -3.0);
EXPECT_EQ(val2, 5.93);
}
TEST_F(ManySubcommands, getSubtests) {
CLI::App_p sub2p = app.get_subcommand_ptr(sub2);
EXPECT_EQ(sub2p.get(), sub2);
EXPECT_THROW(app.get_subcommand_ptr(nullptr), CLI::OptionNotFound);
EXPECT_THROW(app.get_subcommand(nullptr), CLI::OptionNotFound);
CLI::App_p sub3p = app.get_subcommand_ptr(2);
EXPECT_EQ(sub3p.get(), sub3);
}