tool: rewrite parser logic While leaf and non-leaf actions share some common properties, there is much they don't share. Rather than shoehorn both into the same Action paradigm, I think it makes more sense to consider them separately.
This patch splits Action into either Mode (non-leaf node) or Action (leaf node). The common properties are now found in the Label struct. Additionally, each kind of node is now structured as a class with proper encapsulation, builders, and other goodies. Overall this simplifies the command line parsing logic, hides more internal details, and reduces the boilerplate needed to add a mode or an action. There's no change to the tool's interface with the outside world. Change-Id: I794fc527525a57283f0165e262283adf14160def Reviewed-on: http://gerrit.cloudera.org:8080/3996 Tested-by: Kudu Jenkins Reviewed-by: Todd Lipcon <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/kudu/repo Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/5ef37438 Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/5ef37438 Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/5ef37438 Branch: refs/heads/master Commit: 5ef374388cfe88e7efec26121a2fe02e7d32eba5 Parents: 401985e Author: Adar Dembo <[email protected]> Authored: Sun Aug 14 22:13:04 2016 -0700 Committer: Todd Lipcon <[email protected]> Committed: Tue Aug 16 05:57:18 2016 +0000 ---------------------------------------------------------------------- src/kudu/tools/tool_action.cc | 89 +++++++++++---- src/kudu/tools/tool_action.h | 178 +++++++++++++++++++++++------- src/kudu/tools/tool_action_fs.cc | 56 +++++----- src/kudu/tools/tool_action_tablet.cc | 92 ++++++++------- src/kudu/tools/tool_main.cc | 118 ++++++++++---------- 5 files changed, 333 insertions(+), 200 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool_action.cc b/src/kudu/tools/tool_action.cc index 4daf931..317c162 100644 --- a/src/kudu/tools/tool_action.cc +++ b/src/kudu/tools/tool_action.cc @@ -18,6 +18,7 @@ #include "kudu/tools/tool_action.h" #include <deque> +#include <memory> #include <string> #include <vector> @@ -26,35 +27,89 @@ using std::deque; using std::string; +using std::unique_ptr; using std::vector; using strings::Substitute; namespace kudu { namespace tools { -string BuildActionChainString(const vector<Action>& chain) { - return JoinMapped(chain, [](const Action& a){ return a.name; }, " "); +namespace { + +string BuildUsageString(const vector<Mode*>& chain) { + string modes = JoinMapped(chain, [](Mode* a){ return a->name(); }, " "); + return Substitute("Usage: $0", modes); +} + +} // anonymous namespace + +ModeBuilder::ModeBuilder(const Label& label) + : label_(label) { +} + +ModeBuilder& ModeBuilder::AddMode(unique_ptr<Mode> mode) { + submodes_.push_back(std::move(mode)); + return *this; } -string BuildUsageString(const vector<Action>& chain) { - return Substitute("Usage: $0", BuildActionChainString(chain)); +ModeBuilder& ModeBuilder::AddAction(unique_ptr<Action> action) { + actions_.push_back(std::move(action)); + return *this; } -string BuildHelpString(const vector<Action>& sub_actions, string usage_str) { - string msg = Substitute("$0 <action>\n", usage_str); +unique_ptr<Mode> ModeBuilder::Build() { + unique_ptr<Mode> mode(new Mode()); + mode->label_ = label_; + mode->submodes_ = std::move(submodes_); + mode->actions_ = std::move(actions_); + return mode; +} + +// Get help for this mode, passing in its parent mode chain. +string Mode::BuildHelp(const vector<Mode*>& chain) const { + string msg = Substitute("$0 <action>\n", BuildUsageString(chain)); msg += "Action can be one of the following:\n"; - for (const auto& a : sub_actions) { - msg += Substitute(" $0 : $1\n", a.name, a.description); + for (const auto& m : modes()) { + msg += Substitute(" $0 : $1\n", m->name(), m->description()); + } + for (const auto& a : actions()) { + msg += Substitute(" $0 : $1\n", a->name(), a->description()); } return msg; } -string BuildLeafActionHelpString(const vector<Action>& chain) { - DCHECK(!chain.empty()); - Action action = chain.back(); - string msg = Substitute("$0", BuildUsageString(chain)); +Mode::Mode() { +} + +ActionBuilder::ActionBuilder(const Label& label, const ActionRunner& runner) + : label_(label), + runner_(runner) { +} + +ActionBuilder& ActionBuilder::AddGflag(const string& gflag) { + gflags_.push_back(gflag); + return *this; +} + +unique_ptr<Action> ActionBuilder::Build() { + unique_ptr<Action> action(new Action()); + action->label_ = label_; + action->runner_ = runner_; + action->gflags_ = gflags_; + return action; +} + +Action::Action() { +} + +Status Action::Run(const vector<Mode*>& chain, deque<string> args) const { + return runner_(chain, this, args); +} + +string Action::BuildHelp(const vector<Mode*>& chain) const { + string msg = Substitute("$0 $1", BuildUsageString(chain), name()); string gflags_msg; - for (const auto& gflag : action.gflags) { + for (const auto& gflag : gflags_) { google::CommandLineFlagInfo gflag_info = google::GetCommandLineFlagInfoOrDie(gflag.c_str()); string noun; @@ -69,17 +124,11 @@ string BuildLeafActionHelpString(const vector<Action>& chain) { gflags_msg += google::DescribeOneFlag(gflag_info); } msg += "\n"; - msg += Substitute("$0\n", action.description); + msg += Substitute("$0\n", label_.description); msg += gflags_msg; return msg; } -string BuildNonLeafActionHelpString(const vector<Action>& chain) { - string usage = BuildUsageString(chain); - DCHECK(!chain.empty()); - return BuildHelpString(chain.back().sub_actions, usage); -} - Status ParseAndRemoveArg(const char* arg_name, deque<string>* remaining_args, string* parsed_arg) { http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action.h ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool_action.h b/src/kudu/tools/tool_action.h index 6e6cc5a..cea7c7e 100644 --- a/src/kudu/tools/tool_action.h +++ b/src/kudu/tools/tool_action.h @@ -19,23 +19,39 @@ #include <deque> #include <glog/logging.h> +#include <memory> #include <string> #include <vector> +#include "kudu/gutil/strings/join.h" #include "kudu/gutil/strings/substitute.h" #include "kudu/util/status.h" namespace kudu { namespace tools { -// Encapsulates all knowledge for a particular tool action. +class Action; +class Mode; + +// The command line tool is structured as a tree with two kinds of nodes: modes +// and actions. Actions are leaf nodes, each representing a particular +// operation that the tool can take. Modes are non-leaf nodes that are +// basically just intuitive groupings of actions. +// +// Regardless of type, every node has a name which is used to match it against +// a command line argument during parsing. Additionally, every node has a +// description, displayed (along with the name) in help text. // -// All actions are arranged in a tree. Leaf actions are invokable: they will -// do something meaningful when run() is called. Non-leaf actions do not have -// a run(); an attempt to invoke them will yield help() instead. +// Every node (be it action or mode) has pointers to its children, but not to +// its parent mode. As such, operations that require information from the +// parent modes expect the caller to provide those modes as a "mode chain". // -// Sample action tree: +// Sample node tree: // +// root +// | +// | +// | // fs // | | // +--+ +--+ @@ -43,50 +59,127 @@ namespace tools { // format print_uuid // // Given this tree: -// - "<program> fs" will print some text explaining all of fs's actions. +// - "<program> fs" will show all of fs's possible actions. // - "<program> fs format" will format a filesystem. // - "<program> fs print_uuid" will print a filesystem's UUID. -struct Action { - // The name of the action (e.g. "fs"). + +// Properties common to all nodes. +struct Label { + // The node's name (e.g. "fs"). Uniquely identifies the node action amongst + // its siblings in the tree. std::string name; - // The description of the action (e.g. "Operate on a local Kudu filesystem"). + // The node's description (e.g. "Operate on a local Kudu filesystem"). std::string description; +}; - // Invokes an action, passing in the complete action chain and all remaining - // command line arguments. The arguments are passed by value so that the - // function can modify them if need be. - std::function<Status(const std::vector<Action>&, - std::deque<std::string>)> run; +// Builds a new mode (non-leaf) node. +class ModeBuilder { + public: + // Creates a new ModeBuilder with a specific label. + explicit ModeBuilder(const Label& label); - // Get help for an action, passing in the complete action chain. - std::function<std::string(const std::vector<Action>&)> help; + // Adds a new mode (non-leaf child node) to this builder. + ModeBuilder& AddMode(std::unique_ptr<Mode> mode); - // This action's children. - std::vector<Action> sub_actions; + // Adds a new action (leaf child node) to this builder. + ModeBuilder& AddAction(std::unique_ptr<Action> action); - // This action's gflags (if any). - std::vector<std::string> gflags; + // Creates a mode using builder state. + // + // May only be called once. + std::unique_ptr<Mode> Build(); + + private: + const Label label_; + + std::vector<std::unique_ptr<Mode>> submodes_; + + std::vector<std::unique_ptr<Action>> actions_; }; -// Constructs a string with the names of all actions in the chain -// (e.g. "<program> fs format"). -std::string BuildActionChainString(const std::vector<Action>& chain); +// A non-leaf node in the tree, representing a logical grouping for actions or +// more modes. +class Mode { + public: + + // Returns the help for this mode given its parent mode chain. + std::string BuildHelp(const std::vector<Mode*>& chain) const; -// Constructs a usage string (e.g. "Usage: <program> fs format"). -std::string BuildUsageString(const std::vector<Action>& chain); + const std::string& name() const { return label_.name; } + + const std::string& description() const { return label_.description; } + + const std::vector<std::unique_ptr<Mode>>& modes() const { return submodes_; } + + const std::vector<std::unique_ptr<Action>>& actions() const { return actions_; } + + private: + friend class ModeBuilder; + + Mode(); + + Label label_; + + std::vector<std::unique_ptr<Mode>> submodes_; + + std::vector<std::unique_ptr<Action>> actions_; +}; -// Constructs a help string suitable for leaf actions. -std::string BuildLeafActionHelpString(const std::vector<Action>& chain); +// Function signature for any operation represented by an action. When run, the +// operation receives the parent mode chain, the current action, and any +// remaining command line arguments. +typedef std::function<Status(const std::vector<Mode*>&, + const Action*, + std::deque<std::string>)> ActionRunner; -// Constructs a help string suitable for non-leaf actions. -std::string BuildNonLeafActionHelpString(const std::vector<Action>& chain); +// Builds a new action (leaf) node. +class ActionBuilder { + public: + // Creates a new ActionBuilder with a specific label and action runner. + ActionBuilder(const Label& label, const ActionRunner& runner); -// Constructs a string appropriate for displaying program help, using -// 'sub_actions' as a list of actions to include and 'usage_str' as a string -// to prepend. -std::string BuildHelpString(const std::vector<Action>& sub_actions, - std::string usage_str); + // Add a new gflag to this builder. They are used when generating help. + ActionBuilder& AddGflag(const std::string& gflag); + + // Creates an action using builder state. + std::unique_ptr<Action> Build(); + + private: + Label label_; + + ActionRunner runner_; + + std::vector<std::string> gflags_; +}; + +// A leaf node in the tree, representing a logical operation taken by the tool. +class Action { + public: + + // Returns the help for this action given its parent mode chain. + std::string BuildHelp(const std::vector<Mode*>& chain) const; + + // Runs the operation represented by this action, given a parent mode chain + // and list of extra command line arguments. + Status Run(const std::vector<Mode*>& chain, std::deque<std::string> args) const; + + const std::string& name() const { return label_.name; } + + const std::string& description() const { return label_.description; } + + private: + friend class ActionBuilder; + + Action(); + + Label label_; + + ActionRunner runner_; + + // This action's gflags (if any). + std::vector<std::string> gflags_; +}; // Removes one argument from 'remaining_args' and stores it in 'parsed_arg'. // @@ -98,22 +191,23 @@ Status ParseAndRemoveArg(const char* arg_name, // Checks that 'args' is empty. If not, returns a bad status. template <typename CONTAINER> -Status CheckNoMoreArgs(const std::vector<Action>& chain, +Status CheckNoMoreArgs(const std::vector<Mode*>& chain, + const Action* action, const CONTAINER& args) { if (args.empty()) { return Status::OK(); } DCHECK(!chain.empty()); - Action action = chain.back(); - return Status::InvalidArgument(strings::Substitute( - "too many arguments\n$0", action.help(chain))); + return Status::InvalidArgument( + strings::Substitute("too many arguments: '$0'\n$1", + JoinStrings(args, " "), action->BuildHelp(chain))); } -// Returns the "fs" action node. -Action BuildFsAction(); +// Returns a new "fs" mode node. +std::unique_ptr<Mode> BuildFsMode(); -// Returns the "tablet" action node. -Action BuildTabletAction(); +// Returns a new "tablet" mode node. +std::unique_ptr<Mode> BuildTabletMode(); } // namespace tools } // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action_fs.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool_action_fs.cc b/src/kudu/tools/tool_action_fs.cc index b0b7c20..a77bc06 100644 --- a/src/kudu/tools/tool_action_fs.cc +++ b/src/kudu/tools/tool_action_fs.cc @@ -21,6 +21,7 @@ #include <deque> #include <gflags/gflags.h> #include <iostream> +#include <memory> #include <string> #include "kudu/fs/fs_manager.h" @@ -30,6 +31,7 @@ using std::cout; using std::deque; using std::endl; using std::string; +using std::unique_ptr; using std::vector; DEFINE_string(uuid, "", @@ -40,8 +42,10 @@ namespace tools { namespace { -Status Format(const vector<Action>& chain, deque<string> args) { - RETURN_NOT_OK(CheckNoMoreArgs(chain, args)); +Status Format(const vector<Mode*>& chain, + const Action* action, + deque<string> args) { + RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args)); FsManager fs_manager(Env::Default(), FsManagerOpts()); boost::optional<string> uuid; @@ -51,8 +55,10 @@ Status Format(const vector<Action>& chain, deque<string> args) { return fs_manager.CreateInitialFileSystemLayout(uuid); } -Status PrintUuid(const vector<Action>& chain, deque<string> args) { - RETURN_NOT_OK(CheckNoMoreArgs(chain, args)); +Status PrintUuid(const vector<Mode*>& chain, + const Action* action, + deque<string> args) { + RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args)); FsManagerOpts opts; opts.read_only = true; @@ -64,30 +70,24 @@ Status PrintUuid(const vector<Action>& chain, deque<string> args) { } // anonymous namespace -Action BuildFsAction() { - Action fs_format; - fs_format.name = "format"; - fs_format.description = "Format a new Kudu filesystem"; - fs_format.help = &BuildLeafActionHelpString; - fs_format.run = &Format; - fs_format.gflags = { "fs_wal_dir", "fs_data_dirs", "uuid" }; - - Action fs_print_uuid; - fs_print_uuid.name = "print_uuid"; - fs_print_uuid.description = "Print the UUID of a Kudu filesystem"; - fs_print_uuid.help = &BuildLeafActionHelpString; - fs_print_uuid.run = &PrintUuid; - fs_print_uuid.gflags = { "fs_wal_dir", "fs_data_dirs" }; - - Action fs; - fs.name = "fs"; - fs.description = "Operate on a local Kudu filesystem"; - fs.help = &BuildNonLeafActionHelpString; - fs.sub_actions = { - fs_format, - fs_print_uuid - }; - return fs; +unique_ptr<Mode> BuildFsMode() { + unique_ptr<Action> format = ActionBuilder( + { "format", "Format a new Kudu filesystem" }, &Format) + .AddGflag("fs_wal_dir") + .AddGflag("fs_data_dirs") + .AddGflag("uuid") + .Build(); + + unique_ptr<Action> print_uuid = ActionBuilder( + { "print_uuid", "Print the UUID of a Kudu filesystem" }, &PrintUuid) + .AddGflag("fs_wal_dir") + .AddGflag("fs_data_dirs") + .Build(); + + return ModeBuilder({ "fs", "Operate on a local Kudu filesystem" }) + .AddAction(std::move(format)) + .AddAction(std::move(print_uuid)) + .Build(); } } // namespace tools http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action_tablet.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool_action_tablet.cc b/src/kudu/tools/tool_action_tablet.cc index 1dbd253..a34ced7 100644 --- a/src/kudu/tools/tool_action_tablet.cc +++ b/src/kudu/tools/tool_action_tablet.cc @@ -95,11 +95,13 @@ Status ParsePeerString(const string& peer_str, return Status::OK(); } -Status PrintReplicaUuids(const vector<Action>& chain, deque<string> args) { +Status PrintReplicaUuids(const vector<Mode*>& chain, + const Action* action, + deque<string> args) { // Parse tablet ID argument. string tablet_id; RETURN_NOT_OK(ParseAndRemoveArg("tablet ID", &args, &tablet_id)); - RETURN_NOT_OK(CheckNoMoreArgs(chain, args)); + RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args)); FsManagerOpts opts; opts.read_only = true; @@ -116,7 +118,9 @@ Status PrintReplicaUuids(const vector<Action>& chain, deque<string> args) { return Status::OK(); } -Status RewriteRaftConfig(const vector<Action>& chain, deque<string> args) { +Status RewriteRaftConfig(const vector<Mode*>& chain, + const Action* action, + deque<string> args) { // Parse tablet ID argument. string tablet_id; RETURN_NOT_OK(ParseAndRemoveArg("tablet ID", &args, &tablet_id)); @@ -171,14 +175,16 @@ Status RewriteRaftConfig(const vector<Action>& chain, deque<string> args) { return cmeta->Flush(); } -Status Copy(const vector<Action>& chain, deque<string> args) { +Status Copy(const vector<Mode*>& chain, + const Action* action, + deque<string> args) { // Parse the tablet ID and source arguments. string tablet_id; RETURN_NOT_OK(ParseAndRemoveArg("tablet ID", &args, &tablet_id)); string rpc_address; RETURN_NOT_OK(ParseAndRemoveArg("source RPC address of form hostname:port", &args, &rpc_address)); - RETURN_NOT_OK(CheckNoMoreArgs(chain, args)); + RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args)); HostPort hp; RETURN_NOT_OK(ParseHostPortString(rpc_address, &hp)); @@ -197,52 +203,40 @@ Status Copy(const vector<Action>& chain, deque<string> args) { } // anonymous namespace -Action BuildTabletAction() { +unique_ptr<Mode> BuildTabletMode() { // TODO: Need to include required arguments in the help for these actions. - Action tablet_print_replica_uuids; - tablet_print_replica_uuids.name = "print_replica_uuids"; - tablet_print_replica_uuids.description = - "Print all replica UUIDs found in a tablet's Raft configuration"; - tablet_print_replica_uuids.help = &BuildLeafActionHelpString; - tablet_print_replica_uuids.run = &PrintReplicaUuids; - tablet_print_replica_uuids.gflags = { "fs_wal_dir", "fs_data_dirs" }; - - - Action tablet_rewrite_raft_config; - tablet_rewrite_raft_config.name = "rewrite_raft_config"; - tablet_rewrite_raft_config.description = - "Rewrite a replica's Raft configuration"; - tablet_rewrite_raft_config.help = &BuildLeafActionHelpString; - tablet_rewrite_raft_config.run = &RewriteRaftConfig; - tablet_rewrite_raft_config.gflags = { "fs_wal_dir", "fs_data_dirs" }; - - Action tablet_cmeta; - tablet_cmeta.name = "cmeta"; - tablet_cmeta.description = - "Operate on a local Kudu tablet's consensus metadata file"; - tablet_cmeta.help = &BuildNonLeafActionHelpString; - tablet_cmeta.sub_actions = { - std::move(tablet_print_replica_uuids), - std::move(tablet_rewrite_raft_config), - }; - - Action tablet_copy; - tablet_copy.name = "copy"; - tablet_copy.description = "Copy a replica from a remote server"; - tablet_copy.help = &BuildLeafActionHelpString; - tablet_copy.run = &Copy; - tablet_copy.gflags = { "fs_wal_dir", "fs_data_dirs" }; - - Action tablet; - tablet.name = "tablet"; - tablet.description = "Operate on a local Kudu replica"; - tablet.help = &BuildNonLeafActionHelpString; - tablet.sub_actions = { - tablet_cmeta, - tablet_copy - }; - return tablet; + unique_ptr<Action> print_replica_uuids = ActionBuilder( + { "print_replica_uuids", + "Print all replica UUIDs found in a tablet's Raft configuration" }, + &PrintReplicaUuids) + .AddGflag("fs_wal_dir") + .AddGflag("fs_data_dirs") + .Build(); + + unique_ptr<Action> rewrite_raft_config = ActionBuilder( + { "rewrite_raft_config", "Rewrite a replica's Raft configuration" }, + &RewriteRaftConfig) + .AddGflag("fs_wal_dir") + .AddGflag("fs_data_dirs") + .Build(); + + unique_ptr<Mode> cmeta = ModeBuilder( + { "cmeta", "Operate on a local Kudu tablet's consensus metadata file" }) + .AddAction(std::move(print_replica_uuids)) + .AddAction(std::move(rewrite_raft_config)) + .Build(); + + unique_ptr<Action> copy = ActionBuilder( + { "copy", "Copy a replica from a remote server" }, &Copy) + .AddGflag("fs_wal_dir") + .AddGflag("fs_data_dirs") + .Build(); + + return ModeBuilder({ "tablet", "Operate on a local Kudu replica" }) + .AddMode(std::move(cmeta)) + .AddAction(std::move(copy)) + .Build(); } } // namespace tools http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_main.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/tool_main.cc b/src/kudu/tools/tool_main.cc index 5ffc4a7..96a74fa 100644 --- a/src/kudu/tools/tool_main.cc +++ b/src/kudu/tools/tool_main.cc @@ -19,6 +19,7 @@ #include <gflags/gflags.h> #include <glog/logging.h> #include <iostream> +#include <memory> #include <string> #include <vector> @@ -40,16 +41,17 @@ using std::cout; using std::deque; using std::endl; using std::string; +using std::unique_ptr; using std::vector; using strings::Substitute; namespace kudu { namespace tools { -int DispatchCommand(const vector<Action>& chain, const deque<string>& args) { - DCHECK(!chain.empty()); - Action action = chain.back(); - Status s = action.run(chain, args); +int DispatchCommand(const vector<Mode*>& chain, + Action* action, + const deque<string>& args) { + Status s = action->Run(chain, args); if (s.ok()) { return 0; } else { @@ -58,63 +60,70 @@ int DispatchCommand(const vector<Action>& chain, const deque<string>& args) { } } -int RunTool(const Action& root, int argc, char** argv, bool show_help) { +int RunTool(int argc, char** argv, bool show_help) { + unique_ptr<Mode> root = + ModeBuilder({ argv[0], "" }) // root mode description isn't printed + .AddMode(BuildFsMode()) + .AddMode(BuildTabletMode()) + .Build(); + // Initialize arg parsing state. - vector<Action> chain = { root }; + vector<Mode*> chain = { root.get() }; - // Parse the arguments, matching them up with actions. + // Parse the arguments, matching each to a mode or action. for (int i = 1; i < argc; i++) { - const Action* cur = &chain.back(); - const auto& sub_actions = cur->sub_actions; - if (sub_actions.empty()) { - // We've reached an invokable action. - if (show_help) { - cerr << cur->help(chain) << endl; - return 1; - } else { - // Invoke it with whatever arguments remain. - deque<string> remaining_args; - for (int j = i; j < argc; j++) { - remaining_args.push_back(argv[j]); - } - return DispatchCommand(chain, remaining_args); + Mode* cur = chain.back(); + Mode* next_mode = nullptr; + Action* next_action = nullptr; + + // Match argument with a mode. + for (const auto& m : cur->modes()) { + if (m->name() == argv[i]) { + next_mode = m.get(); + break; } } - // This action is not invokable. Interpret the next command line argument - // as a subaction and continue parsing. - const Action* next = nullptr; - for (const auto& a : sub_actions) { - if (a.name == argv[i]) { - next = &a; + // Match argument with an action. + for (const auto& a : cur->actions()) { + if (a->name() == argv[i]) { + next_action = a.get(); break; } } - if (next == nullptr) { - // We couldn't find a subaction for the next argument. Raise an error. - string msg = Substitute("$0 $1\n", - BuildActionChainString(chain), argv[i]); - msg += BuildHelpString(sub_actions, BuildUsageString(chain)); - Status s = Status::InvalidArgument(msg); - cerr << s.ToString() << endl; + // If both matched, there's an error with the tree. + DCHECK(!next_mode || !next_action); + + if (next_mode) { + // Add the mode and keep parsing. + chain.push_back(next_mode); + } else if (next_action) { + if (show_help) { + cerr << next_action->BuildHelp(chain) << endl; + return 1; + } else { + // Invoke the action with whatever arguments remain, skipping this one. + deque<string> remaining_args; + for (int j = i + 1; j < argc; j++) { + remaining_args.push_back(argv[j]); + } + return DispatchCommand(chain, next_action, remaining_args); + } + } else { + // Couldn't match the argument at all. Print the help. + Status s = Status::InvalidArgument( + Substitute("unknown command '$0'\n", argv[i])); + cerr << s.ToString() << cur->BuildHelp(chain) << endl; return 1; } - - // We're done parsing this argument. Loop and continue. - chain.emplace_back(*next); } - // We made it to a subaction with no arguments left. Run the subaction if - // possible, otherwise print its help. - const Action* last = &chain.back(); - if (show_help || !last->run) { - cerr << last->help(chain) << endl; - return 1; - } else { - DCHECK(last->run); - return DispatchCommand(chain, {}); - } + // Ran out of arguments before reaching an action. Print the last mode's help. + DCHECK(!chain.empty()); + const Mode* last = chain.back(); + cerr << last->BuildHelp(chain) << endl; + return 1; } } // namespace tools @@ -145,21 +154,8 @@ static bool ParseCommandLineFlags(int* argc, char*** argv) { } int main(int argc, char** argv) { - kudu::tools::Action root = { - argv[0], - "The root action", // doesn't matter, won't get printed - nullptr, - &kudu::tools::BuildNonLeafActionHelpString, - { - kudu::tools::BuildFsAction(), - kudu::tools::BuildTabletAction() - }, - {} // no gflags - }; - string usage = root.help({ root }); - google::SetUsageMessage(usage); bool show_help = ParseCommandLineFlags(&argc, &argv); FLAGS_logtostderr = true; kudu::InitGoogleLoggingSafe(argv[0]); - return kudu::tools::RunTool(root, argc, argv, show_help); + return kudu::tools::RunTool(argc, argv, show_help); }
