mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-02 13:43:52 +00:00
Adding subcom chaining (untested)
This commit is contained in:
parent
217075b2d1
commit
983529a379
@ -1,6 +1,7 @@
|
||||
## Version 0.5 (in progress)
|
||||
|
||||
* Added `->allow_extras()` to disable error on failure. Parse returns a vector of leftover options. Renamed error to `ExtrasError`, and now triggers on extra options too.
|
||||
* Subcommands now can be "chained", that is, left over arguments can now include subcommands that then get parsed. Subcommands are now a list (`get_subcommands`). Added `got_subcommand(App_or_name)` to check for subcommands.
|
||||
* Added `.allow_extras()` to disable error on failure. Parse returns a vector of leftover options. Renamed error to `ExtrasError`, and now triggers on extra options too.
|
||||
* Added `require_subcommand` to `App`, to simplify forcing subcommands. Do not "chain" with `add_subcommand`, since that is the subcommand, not the master `App`.
|
||||
* Added printout of ini file text given parsed options, skips flags.
|
||||
* Support for quotes and spaces in ini files
|
||||
|
@ -159,7 +159,7 @@ everything after that is positional only.
|
||||
|
||||
Subcommands are naturally supported, with an infinite depth. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands.
|
||||
|
||||
All `App`s have a `get_subcommand()` method, which returns a pointer to the subcommand passed on the command line, or `nullptr` if no subcommand was given. A simple compare of this pointer to each subcommand allows choosing based on subcommand. For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.add_callback`. If you throw `CLI::Success`, you can
|
||||
All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommand passed on the command line. A simple compare of these pointers to each subcommand allows choosing based on subcommand, facilitated by a `got_subcommand(App_or_name) method that will check the list for you. For many cases, however, using an app's callback may be easier. Every app executes a callback function after it parses; just use a lambda function (with capture to get parsed values) to `.add_callback`. If you throw `CLI::Success`, you can
|
||||
even exit the program through the callback. The main `App` has a callback slot, as well, but it is generally not as useful.
|
||||
|
||||
|
||||
|
@ -21,8 +21,8 @@ int main (int argc, char** argv) {
|
||||
|
||||
std::cout << "Working on file: " << file << ", direct count: " << start->count("--file") << std::endl;
|
||||
std::cout << "Working on count: " << s->count() << ", direct count: " << stop->count("--count") << std::endl;
|
||||
if(app.get_subcommand() != nullptr)
|
||||
std::cout << "Subcommand:" << app.get_subcommand()->get_name() << std::endl;
|
||||
for(auto subcom : app.get_subcommands())
|
||||
std::cout << "Subcommand:" << subcom->get_name() << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ protected:
|
||||
|
||||
std::vector<App_p> subcommands;
|
||||
bool parsed {false};
|
||||
App* subcommand {nullptr};
|
||||
std::vector<App*> selected_subcommands;
|
||||
bool required_subcommand = false;
|
||||
std::string progname {"program"};
|
||||
Option* help_flag {nullptr};
|
||||
@ -61,6 +61,8 @@ protected:
|
||||
std::string ini_file;
|
||||
bool ini_required {false};
|
||||
Option* ini_setting {nullptr};
|
||||
|
||||
bool case_insensitive {false};
|
||||
|
||||
|
||||
public:
|
||||
@ -86,7 +88,7 @@ public:
|
||||
void reset() {
|
||||
|
||||
parsed = false;
|
||||
subcommand = nullptr;
|
||||
selected_subcommands.clear();
|
||||
|
||||
for(const Option_p &opt : options) {
|
||||
opt->clear();
|
||||
@ -396,8 +398,8 @@ public:
|
||||
else
|
||||
prev += " " + name;
|
||||
|
||||
if(subcommand != nullptr)
|
||||
return subcommand->help(wid, prev);
|
||||
if(selected_subcommands.size() > 0)
|
||||
return selected_subcommands.at(0)->help(wid, prev);
|
||||
|
||||
std::stringstream out;
|
||||
out << prog_description << std::endl;
|
||||
@ -467,9 +469,23 @@ public:
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// Get a subcommand pointer to the currently selected subcommand (after parsing)
|
||||
App* get_subcommand() {
|
||||
return subcommand;
|
||||
/// Get a subcommand pointer list to the currently selected subcommands (after parsing)
|
||||
std::vector<App*> get_subcommands() {
|
||||
return selected_subcommands;
|
||||
}
|
||||
|
||||
|
||||
/// Check to see if selected subcommand in list
|
||||
bool got_subcommand(App* subcom) const {
|
||||
return std::find(std::begin(selected_subcommands), std::end(selected_subcommands), subcom) != std::end(selected_subcommands);
|
||||
}
|
||||
|
||||
/// Check with name instead of pointer
|
||||
bool got_subcommand(std::string name) const {
|
||||
for(const auto subcomptr : selected_subcommands)
|
||||
if(subcomptr->check_name(name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Get the name of the current app
|
||||
@ -477,7 +493,25 @@ public:
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Check the name, case insensitive if set
|
||||
bool check_name(std::string name_to_check) const {
|
||||
std::string local_name = name;
|
||||
if(case_insensitive) {
|
||||
local_name = detail::to_lower(name);
|
||||
name_to_check = detail::to_lower(name_to_check);
|
||||
}
|
||||
|
||||
return local_name == name_to_check;
|
||||
}
|
||||
|
||||
/// Accept any case
|
||||
App* anycase(bool value = true) {
|
||||
case_insensitive = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Require a subcommand to be given (does not affect help call)
|
||||
/// Does not return a pointer since it is supposed to be called on the main App.
|
||||
void require_subcommand(bool value = true) {
|
||||
required_subcommand = value;
|
||||
}
|
||||
@ -497,7 +531,7 @@ protected:
|
||||
if(current == "--")
|
||||
return detail::Classifer::POSITIONAL_MARK;
|
||||
for(const App_p &com : subcommands) {
|
||||
if(com->name == current)
|
||||
if(com->check_name(current))
|
||||
return detail::Classifer::SUBCOMMAND;
|
||||
}
|
||||
if(detail::split_long(current, dummy1, dummy2))
|
||||
@ -625,7 +659,7 @@ protected:
|
||||
throw ExcludesError(opt->get_name(), opt_ex->get_name());
|
||||
}
|
||||
|
||||
if(required_subcommand && subcommand == nullptr)
|
||||
if(required_subcommand && selected_subcommands.size() == 0)
|
||||
throw RequiredError("Subcommand required");
|
||||
|
||||
// Convert missing (pairs) to extras (string only)
|
||||
@ -647,11 +681,12 @@ protected:
|
||||
}
|
||||
|
||||
|
||||
/// Parse a subcommand, modify args and continue
|
||||
void _parse_subcommand(std::vector<std::string> &args) {
|
||||
for(const App_p &com : subcommands) {
|
||||
if(com->name == args.back()){
|
||||
if(com->check_name(args.back())){
|
||||
args.pop_back();
|
||||
subcommand = com.get();
|
||||
selected_subcommands.push_back(com.get());
|
||||
com->parse(args);
|
||||
return;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ std::vector<std::string> parse_ini(std::istream &input) {
|
||||
size_t len = line.length();
|
||||
if(len > 1 && line[0] == '[' && line[len-1] == ']') {
|
||||
section = line.substr(1,len-2);
|
||||
std::transform(std::begin(section), std::end(section), std::begin(section), ::tolower);
|
||||
section = detail::to_lower(section);
|
||||
} else if (len > 0) {
|
||||
// Find = in string, split and recombine
|
||||
auto pos = line.find("=");
|
||||
|
@ -113,7 +113,12 @@ inline bool valid_name_string(const std::string &str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// Return a lower case version of a string
|
||||
std::string inline to_lower(std::string str) {
|
||||
std::transform(std::begin(str), std::end(str), std::begin(str),
|
||||
[](const std::string::value_type x){return std::tolower(x,std::locale());});
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,19 @@ TEST_F(TApp, BasicSubcommands) {
|
||||
auto sub2 = app.add_subcommand("sub2");
|
||||
|
||||
EXPECT_NO_THROW(run());
|
||||
EXPECT_EQ(nullptr, app.get_subcommand());
|
||||
EXPECT_EQ(0, app.get_subcommands().size());
|
||||
|
||||
app.reset();
|
||||
args = {"sub1"};
|
||||
EXPECT_NO_THROW(run());
|
||||
EXPECT_EQ(sub1, app.get_subcommand());
|
||||
EXPECT_EQ(sub1, app.get_subcommands().at(0));
|
||||
|
||||
app.reset();
|
||||
EXPECT_EQ(nullptr, app.get_subcommand());
|
||||
EXPECT_EQ(0, app.get_subcommands().size());
|
||||
|
||||
args = {"sub2"};
|
||||
EXPECT_NO_THROW(run());
|
||||
EXPECT_EQ(sub2, app.get_subcommand());
|
||||
EXPECT_EQ(sub2, app.get_subcommands().at(0));
|
||||
}
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@ TEST_F(SubcommandProgram, Working) {
|
||||
EXPECT_NO_THROW(run());
|
||||
|
||||
EXPECT_EQ(1, dummy);
|
||||
EXPECT_EQ(start, app.get_subcommand());
|
||||
EXPECT_EQ(start, app.get_subcommands().at(0));
|
||||
EXPECT_EQ("filename", file);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user