diff --git a/CHANGELOG.md b/CHANGELOG.md index 84282184..8c8e4c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 3ff5bee2..7a9b3b85 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/examples/try1.cpp b/examples/try1.cpp index 9d1a4cdf..f4a37bb0 100644 --- a/examples/try1.cpp +++ b/examples/try1.cpp @@ -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; } diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 804fcdf6..5b103e2c 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -51,7 +51,7 @@ protected: std::vector subcommands; bool parsed {false}; - App* subcommand {nullptr}; + std::vector 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 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 &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; } diff --git a/include/CLI/Ini.hpp b/include/CLI/Ini.hpp index 54e87a03..fd4c9211 100644 --- a/include/CLI/Ini.hpp +++ b/include/CLI/Ini.hpp @@ -27,7 +27,7 @@ std::vector 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("="); diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp index 67e25231..d2e811ea 100644 --- a/include/CLI/StringTools.hpp +++ b/include/CLI/StringTools.hpp @@ -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; +} } } diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index 65e30a2e..632b9c2c 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -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); }