1
0
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:
Henry Fredrick Schreiner 2017-02-20 12:08:21 -05:00
parent 217075b2d1
commit 983529a379
7 changed files with 63 additions and 22 deletions

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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("=");

View File

@ -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;
}
}
}

View File

@ -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);
}