diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8d79c0..253d69da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Ini no longer lists the help pointer * Added test for inclusion in multiple files and linking, fixed issues (rarely needed for CLI, but nice for tools) * Support for complex numbers +* Subcommands now test true/false directly, cleaner parse ## Version 0.8 diff --git a/README.md b/README.md index e4ee06d6..d3a7b596 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,10 @@ Subcommands are supported, and can be nested infinitely. To add a subcommand, ca case). If you want to require 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. -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 `.set_callback`. If you throw `CLI::Success`, you can +If an App (main or subcommand) has been parsed on the command line, it's "value" is true, and it is false otherwise. +All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an App pointer or a string name was collected on the command line. + +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 `.set_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. If you want only one, use `app.require_subcommand(1)`. 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). diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 32181e47..4c3998b0 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -98,8 +98,8 @@ protected: /// A pointer to the parent if this is a subcommand App* parent_ {nullptr}; - /// A list of all the subcommand pointers that have been collected on the command line - std::vector selected_subcommands_; + /// True if this command/subcommand was parsed + bool parsed_ {false}; /// -1 for 1 or more, 0 for not required, # for exact number required int require_subcommand_ = 0; @@ -168,6 +168,9 @@ public: return this; } + /// Check to see if this subcommand was parsed, true only if received on command line. + operator bool () const { return parsed_;} + /// 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. App* require_subcommand(int value = -1) { @@ -472,7 +475,29 @@ public: return subcommands_.back().get(); } - + /// Check to see if a subcommand is part of this command (doesn't have to be in command line) + App* get_subcommand(App* subcom) const { + for(const App_p &subcomptr : subcommands_) + if(subcomptr.get() == subcom) + return subcom; + throw CLI::OptionNotFound(subcom->get_name()); + } + + /// Check to see if a subcommand is part of this command (text version) + App* get_subcommand(std::string subcom) { + for(const App_p &subcomptr : subcommands_) + if(subcomptr->check_name(subcom)) + return subcomptr.get(); + throw CLI::OptionNotFound(subcom); + } + + /// Check to see if a subcommand is part of this command (text version, const) + App* get_subcommand(std::string subcom) const { + for(const App_p &subcomptr : subcommands_) + if(subcomptr->check_name(subcom)) + return subcomptr.get(); + throw CLI::OptionNotFound(subcom); + } ///@} /// @name Extras for subclassing @@ -524,7 +549,7 @@ public: /// Reset the parsed data void reset() { - selected_subcommands_.clear(); + parsed_ = false; missing_.clear(); for(const Option_p &opt : options_) { @@ -550,24 +575,24 @@ public: } /// 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 { + std::vector get_subcommands() const { + std::vector subcomms; for(const App_p &subcomptr : subcommands_) - if(subcomptr->check_name(name)) - return got_subcommand(subcomptr.get()); - throw CLI::OptionNotFound(name); + if(subcomptr->parsed_) + subcomms.push_back(subcomptr.get()); + return subcomms; + } + + + /// Check to see if given subcommand was selected + bool got_subcommand(App* subcom) const { + // get subcom needed to verify that this was a real subcommand + return get_subcommand(subcom)->parsed_; + } + + /// Check with name instead of pointer to see if subcommand was selected + bool got_subcommand(std::string name) const { + return get_subcommand(name)->parsed_; } ///@} @@ -621,8 +646,9 @@ public: else prev += " " + name_; - if(selected_subcommands_.size() > 0) - return selected_subcommands_.at(0)->help(wid, prev); + auto selected_subcommands = get_subcommands(); + if(selected_subcommands.size() > 0) + return selected_subcommands.at(0)->help(wid, prev); std::stringstream out; out << description_ << std::endl; @@ -769,7 +795,7 @@ protected: pre_callback(); if(callback_) callback_(); - for(App* subc : selected_subcommands_) { + for(App* subc : get_subcommands()) { subc->run_callback(); } } @@ -803,6 +829,7 @@ protected: /// Internal parse function void _parse(std::vector &args) { + parsed_ = true; bool positional_only = false; while(args.size()>0) { @@ -863,9 +890,10 @@ protected: throw ExcludesError(opt->get_name(), opt_ex->get_name()); } - if(require_subcommand_ < 0 && selected_subcommands_.size() == 0) + auto selected_subcommands =get_subcommands(); + if(require_subcommand_ < 0 && selected_subcommands.size() == 0) throw RequiredError("Subcommand required"); - else if(require_subcommand_ > 0 && (int) selected_subcommands_.size() != require_subcommand_) + else if(require_subcommand_ > 0 && (int) selected_subcommands.size() != require_subcommand_) throw RequiredError(std::to_string(require_subcommand_) + " subcommand(s) required"); // Convert missing (pairs) to extras (string only) @@ -1002,7 +1030,6 @@ protected: for(const App_p &com : subcommands_) { if(com->check_name(args.back())){ args.pop_back(); - selected_subcommands_.push_back(com.get()); com->_parse(args); return; } diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index 4a93d515..3e77e8c8 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -28,14 +28,18 @@ TEST_F(TApp, MultiSubFallthrough) { // No explicit fallthrough auto sub1 = app.add_subcommand("sub1"); - app.add_subcommand("sub2"); + auto sub2 = app.add_subcommand("sub2"); args = {"sub1", "sub2"}; run(); EXPECT_TRUE(app.got_subcommand("sub1")); EXPECT_TRUE(app.got_subcommand(sub1)); + EXPECT_TRUE(*sub1); + EXPECT_TRUE(app.got_subcommand("sub2")); + EXPECT_TRUE(app.got_subcommand(sub2)); + EXPECT_TRUE(*sub2); app.reset(); app.require_subcommand(); @@ -58,6 +62,9 @@ TEST_F(TApp, MultiSubFallthrough) { EXPECT_TRUE(app.got_subcommand("sub1")); EXPECT_FALSE(app.got_subcommand("sub2")); + + EXPECT_TRUE(*sub1); + EXPECT_FALSE(*sub2); EXPECT_THROW(app.got_subcommand("sub3"), CLI::OptionNotFound); }