mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +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:
parent
b4910df3d7
commit
598046c397
14
README.md
14
README.md
@ -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.
|
||||
|
@ -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
|
||||
|
37
examples/subcom_partitioned.cpp
Normal file
37
examples/subcom_partitioned.cpp
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user