From ce22011db90ceaa817c70108b85334fa034a7dfc Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 16 Sep 2014 14:32:37 +0100 Subject: Extract commonality between rabbit_control_main and rabbit_plugins_main. --- src/rabbit_cli.erl | 184 ++++++++++++++++++++++++++++++++++++++++++++ src/rabbit_control_main.erl | 130 +++++-------------------------- src/rabbit_misc.erl | 66 ---------------- src/rabbit_plugins_main.erl | 101 +++--------------------- 4 files changed, 214 insertions(+), 267 deletions(-) create mode 100644 src/rabbit_cli.erl (limited to 'src') diff --git a/src/rabbit_cli.erl b/src/rabbit_cli.erl new file mode 100644 index 00000000..0c19c727 --- /dev/null +++ b/src/rabbit_cli.erl @@ -0,0 +1,184 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_cli). +-include("rabbit_cli.hrl"). + +-export([main/3, parse_arguments/4]). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(optdef() :: flag | {option, string()}). + +-spec(parse_arguments/4 :: + ([{atom(), [{string(), optdef()}]} | atom()], + [{string(), optdef()}], + string(), + [string()]) + -> {'ok', {atom(), [{string(), string()}], [string()]}} | + 'no_command'). + +-endif. + +%%---------------------------------------------------------------------------- + +main(ParseFun, DoFun, UsageMod) -> + {ok, [[NodeStr|_]|_]} = init:get_argument(nodename), + {Command, Opts, Args} = + case ParseFun(init:get_plain_arguments(), NodeStr) of + {ok, Res} -> Res; + no_command -> print_error("could not recognise command", []), + usage(UsageMod) + end, + Node = proplists:get_value(?NODE_OPT, Opts), + PrintInvalidCommandError = + fun () -> + print_error("invalid command '~s'", + [string:join([atom_to_list(Command) | Args], " ")]) + end, + + %% The reason we don't use a try/catch here is that rpc:call turns + %% thrown errors into normal return values + case catch DoFun(Command, Node, Args, Opts) of + ok -> + rabbit_misc:quit(0); + {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> %% < R15 + PrintInvalidCommandError(), + usage(UsageMod); + {'EXIT', {function_clause, [{?MODULE, action, _, _} | _]}} -> %% >= R15 + PrintInvalidCommandError(), + usage(UsageMod); + {error, {missing_dependencies, Missing, Blame}} -> + print_error("dependent plugins ~p not found; used by ~p.", + [Missing, Blame]), + rabbit_misc:quit(2); + {'EXIT', {badarg, _}} -> + print_error("invalid parameter: ~p", [Args]), + usage(UsageMod); + {error, {Problem, Reason}} when is_atom(Problem), is_binary(Reason) -> + %% We handle this common case specially to avoid ~p since + %% that has i18n issues + print_error("~s: ~s", [Problem, Reason]), + rabbit_misc:quit(2); + {error, Reason} -> + print_error("~p", [Reason]), + rabbit_misc:quit(2); + {error_string, Reason} -> + print_error("~s", [Reason]), + rabbit_misc:quit(2); + {badrpc, {'EXIT', Reason}} -> + print_error("~p", [Reason]), + rabbit_misc:quit(2); + {badrpc, Reason} -> + print_error("unable to connect to node ~w: ~w", [Node, Reason]), + print_badrpc_diagnostics([Node]), + rabbit_misc:quit(2); + {badrpc_multi, Reason, Nodes} -> + print_error("unable to connect to nodes ~p: ~w", [Nodes, Reason]), + print_badrpc_diagnostics(Nodes), + rabbit_misc:quit(2); + Other -> + print_error("~p", [Other]), + rabbit_misc:quit(2) + end. + +usage(Mod) -> + io:format("~s", [Mod:usage()]), + rabbit_misc:quit(1). + +%%---------------------------------------------------------------------------- + +parse_arguments(Commands, GlobalDefs, NodeOpt, CmdLine) -> + case parse_arguments(Commands, GlobalDefs, CmdLine) of + {ok, {Cmd, Opts0, Args}} -> + Opts = [case K of + NodeOpt -> {NodeOpt, rabbit_nodes:make(V)}; + _ -> {K, V} + end || {K, V} <- Opts0], + {ok, {Cmd, Opts, Args}}; + E -> + E + end. + +%% Takes: +%% * A list of [{atom(), [{string(), optdef()]} | atom()], where the atom()s +%% are the accepted commands and the optional [string()] is the list of +%% accepted options for that command +%% * A list [{string(), optdef()}] of options valid for all commands +%% * The list of arguments given by the user +%% +%% Returns either {ok, {atom(), [{string(), string()}], [string()]} which are +%% respectively the command, the key-value pairs of the options and the leftover +%% arguments; or no_command if no command could be parsed. +parse_arguments(Commands, GlobalDefs, As) -> + lists:foldl(maybe_process_opts(GlobalDefs, As), no_command, Commands). + +maybe_process_opts(GDefs, As) -> + fun({C, Os}, no_command) -> + process_opts(atom_to_list(C), dict:from_list(GDefs ++ Os), As); + (C, no_command) -> + (maybe_process_opts(GDefs, As))({C, []}, no_command); + (_, {ok, Res}) -> + {ok, Res} + end. + +process_opts(C, Defs, As0) -> + KVs0 = dict:map(fun (_, flag) -> false; + (_, {option, V}) -> V + end, Defs), + process_opts(Defs, C, As0, not_found, KVs0, []). + +%% Consume flags/options until you find the correct command. If there are no +%% arguments or the first argument is not the command we're expecting, fail. +%% Arguments to this are: definitions, cmd we're looking for, args we +%% haven't parsed, whether we have found the cmd, options we've found, +%% plain args we've found. +process_opts(_Defs, C, [], found, KVs, Outs) -> + {ok, {list_to_atom(C), dict:to_list(KVs), lists:reverse(Outs)}}; +process_opts(_Defs, _C, [], not_found, _, _) -> + no_command; +process_opts(Defs, C, [A | As], Found, KVs, Outs) -> + OptType = case dict:find(A, Defs) of + error -> none; + {ok, flag} -> flag; + {ok, {option, _}} -> option + end, + case {OptType, C, Found} of + {flag, _, _} -> process_opts( + Defs, C, As, Found, dict:store(A, true, KVs), + Outs); + {option, _, _} -> case As of + [] -> no_command; + [V | As1] -> process_opts( + Defs, C, As1, Found, + dict:store(A, V, KVs), Outs) + end; + {none, A, _} -> process_opts(Defs, C, As, found, KVs, Outs); + {none, _, found} -> process_opts(Defs, C, As, found, KVs, [A | Outs]); + {none, _, _} -> no_command + end. + +%%---------------------------------------------------------------------------- + +fmt_stderr(Format, Args) -> rabbit_misc:format_stderr(Format ++ "~n", Args). + +print_error(Format, Args) -> fmt_stderr("Error: " ++ Format, Args). + +print_badrpc_diagnostics(Nodes) -> + fmt_stderr(rabbit_nodes:diagnostics(Nodes), []). + diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 70fe10e6..afc2caa0 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -16,29 +16,13 @@ -module(rabbit_control_main). -include("rabbit.hrl"). +-include("rabbit_cli.hrl"). -export([start/0, stop/0, parse_arguments/2, action/5, sync_queue/1, cancel_sync_queue/1]). --define(RPC_TIMEOUT, infinity). -define(EXTERNAL_CHECK_INTERVAL, 1000). --define(QUIET_OPT, "-q"). --define(NODE_OPT, "-n"). --define(VHOST_OPT, "-p"). --define(PRIORITY_OPT, "--priority"). --define(APPLY_TO_OPT, "--apply-to"). --define(RAM_OPT, "--ram"). --define(OFFLINE_OPT, "--offline"). - --define(QUIET_DEF, {?QUIET_OPT, flag}). --define(NODE_DEF(Node), {?NODE_OPT, {option, Node}}). --define(VHOST_DEF, {?VHOST_OPT, {option, "/"}}). --define(PRIORITY_DEF, {?PRIORITY_OPT, {option, "0"}}). --define(APPLY_TO_DEF, {?APPLY_TO_OPT, {option, "all"}}). --define(RAM_DEF, {?RAM_OPT, flag}). --define(OFFLINE_DEF, {?OFFLINE_OPT, flag}). - -define(GLOBAL_DEFS(Node), [?QUIET_DEF, ?NODE_DEF(Node)]). -define(COMMANDS, @@ -131,7 +115,6 @@ (atom(), node(), [string()], [{string(), any()}], fun ((string(), [any()]) -> 'ok')) -> 'ok'). --spec(usage/0 :: () -> no_return()). -endif. @@ -139,79 +122,24 @@ start() -> start_distribution(), - {ok, [[NodeStr|_]|_]} = init:get_argument(nodename), - {Command, Opts, Args} = - case parse_arguments(init:get_plain_arguments(), NodeStr) of - {ok, Res} -> Res; - no_command -> print_error("could not recognise command", []), - usage() - end, - Quiet = proplists:get_bool(?QUIET_OPT, Opts), - Node = proplists:get_value(?NODE_OPT, Opts), - Inform = case Quiet of - true -> fun (_Format, _Args1) -> ok end; - false -> fun (Format, Args1) -> - io:format(Format ++ " ...~n", Args1) - end - end, - PrintInvalidCommandError = - fun () -> - print_error("invalid command '~s'", - [string:join([atom_to_list(Command) | Args], " ")]) - end, - - %% The reason we don't use a try/catch here is that rpc:call turns - %% thrown errors into normal return values - case catch do_action(Command, Node, Args, Opts, Inform) of - ok -> - case Quiet of - true -> ok; - false -> io:format("...done.~n") - end, - rabbit_misc:quit(0); - {ok, Info} -> - case Quiet of - true -> ok; - false -> io:format("...done (~p).~n", [Info]) - end, - rabbit_misc:quit(0); - {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> %% < R15 - PrintInvalidCommandError(), - usage(); - {'EXIT', {function_clause, [{?MODULE, action, _, _} | _]}} -> %% >= R15 - PrintInvalidCommandError(), - usage(); - {'EXIT', {badarg, _}} -> - print_error("invalid parameter: ~p", [Args]), - usage(); - {error, {Problem, Reason}} when is_atom(Problem), is_binary(Reason) -> - %% We handle this common case specially to avoid ~p since - %% that has i18n issues - print_error("~s: ~s", [Problem, Reason]), - rabbit_misc:quit(2); - {error, Reason} -> - print_error("~p", [Reason]), - rabbit_misc:quit(2); - {error_string, Reason} -> - print_error("~s", [Reason]), - rabbit_misc:quit(2); - {badrpc, {'EXIT', Reason}} -> - print_error("~p", [Reason]), - rabbit_misc:quit(2); - {badrpc, Reason} -> - print_error("unable to connect to node ~w: ~w", [Node, Reason]), - print_badrpc_diagnostics([Node]), - rabbit_misc:quit(2); - {badrpc_multi, Reason, Nodes} -> - print_error("unable to connect to nodes ~p: ~w", [Nodes, Reason]), - print_badrpc_diagnostics(Nodes), - rabbit_misc:quit(2); - Other -> - print_error("~p", [Other]), - rabbit_misc:quit(2) - end. + rabbit_cli:main( + fun (Args, NodeStr) -> + parse_arguments(Args, NodeStr) + end, + fun (Command, Node, Args, Opts) -> + Quiet = proplists:get_bool(?QUIET_OPT, Opts), + Inform = case Quiet of + true -> fun (_Format, _Args1) -> ok end; + false -> fun (Format, Args1) -> + io:format(Format ++ " ...~n", Args1) + end + end, + do_action(Command, Node, Args, Opts, Inform) + end, rabbit_ctl_usage). -fmt_stderr(Format, Args) -> rabbit_misc:format_stderr(Format ++ "~n", Args). +parse_arguments(CmdLine, NodeStr) -> + rabbit_cli:parse_arguments( + ?COMMANDS, ?GLOBAL_DEFS(NodeStr), ?NODE_OPT, CmdLine). print_report(Node, {Descr, Module, InfoFun, KeysFun}) -> io:format("~s:~n", [Descr]), @@ -230,31 +158,9 @@ print_report0(Node, {Module, InfoFun, KeysFun}, VHostArg) -> end, io:nl(). -print_error(Format, Args) -> fmt_stderr("Error: " ++ Format, Args). - -print_badrpc_diagnostics(Nodes) -> - fmt_stderr(rabbit_nodes:diagnostics(Nodes), []). - stop() -> ok. -usage() -> - io:format("~s", [rabbit_ctl_usage:usage()]), - rabbit_misc:quit(1). - -parse_arguments(CmdLine, NodeStr) -> - case rabbit_misc:parse_arguments( - ?COMMANDS, ?GLOBAL_DEFS(NodeStr), CmdLine) of - {ok, {Cmd, Opts0, Args}} -> - Opts = [case K of - ?NODE_OPT -> {?NODE_OPT, rabbit_nodes:make(V)}; - _ -> {K, V} - end || {K, V} <- Opts0], - {ok, {Cmd, Opts, Args}}; - E -> - E - end. - %%---------------------------------------------------------------------------- do_action(Command, Node, Args, Opts, Inform) -> diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index c5b566de..3696698c 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -49,7 +49,6 @@ -export([version_minor_equivalent/2]). -export([dict_cons/3, orddict_cons/3, gb_trees_cons/3]). -export([gb_trees_fold/3, gb_trees_foreach/2]). --export([parse_arguments/3]). -export([all_module_attributes/1, build_acyclic_graph/3]). -export([now_ms/0]). -export([const/1]). @@ -86,7 +85,6 @@ -type(ok_or_error() :: rabbit_types:ok_or_error(any())). -type(thunk(T) :: fun(() -> T)). -type(resource_name() :: binary()). --type(optdef() :: flag | {option, string()}). -type(channel_or_connection_exit() :: rabbit_types:channel_exit() | rabbit_types:connection_exit()). -type(digraph_label() :: term()). @@ -207,12 +205,6 @@ -spec(gb_trees_fold/3 :: (fun ((any(), any(), A) -> A), A, gb_tree()) -> A). -spec(gb_trees_foreach/2 :: (fun ((any(), any()) -> any()), gb_tree()) -> 'ok'). --spec(parse_arguments/3 :: - ([{atom(), [{string(), optdef()}]} | atom()], - [{string(), optdef()}], - [string()]) - -> {'ok', {atom(), [{string(), string()}], [string()]}} | - 'no_command'). -spec(all_module_attributes/1 :: (atom()) -> [{atom(), atom(), [term()]}]). -spec(build_acyclic_graph/3 :: @@ -786,64 +778,6 @@ gb_trees_fold1(Fun, Acc, {Key, Val, It}) -> gb_trees_foreach(Fun, Tree) -> gb_trees_fold(fun (Key, Val, Acc) -> Fun(Key, Val), Acc end, ok, Tree). -%% Takes: -%% * A list of [{atom(), [{string(), optdef()]} | atom()], where the atom()s -%% are the accepted commands and the optional [string()] is the list of -%% accepted options for that command -%% * A list [{string(), optdef()}] of options valid for all commands -%% * The list of arguments given by the user -%% -%% Returns either {ok, {atom(), [{string(), string()}], [string()]} which are -%% respectively the command, the key-value pairs of the options and the leftover -%% arguments; or no_command if no command could be parsed. -parse_arguments(Commands, GlobalDefs, As) -> - lists:foldl(maybe_process_opts(GlobalDefs, As), no_command, Commands). - -maybe_process_opts(GDefs, As) -> - fun({C, Os}, no_command) -> - process_opts(atom_to_list(C), dict:from_list(GDefs ++ Os), As); - (C, no_command) -> - (maybe_process_opts(GDefs, As))({C, []}, no_command); - (_, {ok, Res}) -> - {ok, Res} - end. - -process_opts(C, Defs, As0) -> - KVs0 = dict:map(fun (_, flag) -> false; - (_, {option, V}) -> V - end, Defs), - process_opts(Defs, C, As0, not_found, KVs0, []). - -%% Consume flags/options until you find the correct command. If there are no -%% arguments or the first argument is not the command we're expecting, fail. -%% Arguments to this are: definitions, cmd we're looking for, args we -%% haven't parsed, whether we have found the cmd, options we've found, -%% plain args we've found. -process_opts(_Defs, C, [], found, KVs, Outs) -> - {ok, {list_to_atom(C), dict:to_list(KVs), lists:reverse(Outs)}}; -process_opts(_Defs, _C, [], not_found, _, _) -> - no_command; -process_opts(Defs, C, [A | As], Found, KVs, Outs) -> - OptType = case dict:find(A, Defs) of - error -> none; - {ok, flag} -> flag; - {ok, {option, _}} -> option - end, - case {OptType, C, Found} of - {flag, _, _} -> process_opts( - Defs, C, As, Found, dict:store(A, true, KVs), - Outs); - {option, _, _} -> case As of - [] -> no_command; - [V | As1] -> process_opts( - Defs, C, As1, Found, - dict:store(A, V, KVs), Outs) - end; - {none, A, _} -> process_opts(Defs, C, As, found, KVs, Outs); - {none, _, found} -> process_opts(Defs, C, As, found, KVs, [A | Outs]); - {none, _, _} -> no_command - end. - now_ms() -> timer:now_diff(now(), {0,0,0}) div 1000. diff --git a/src/rabbit_plugins_main.erl b/src/rabbit_plugins_main.erl index 278fcf98..ba992c91 100644 --- a/src/rabbit_plugins_main.erl +++ b/src/rabbit_plugins_main.erl @@ -16,28 +16,11 @@ -module(rabbit_plugins_main). -include("rabbit.hrl"). +-include("rabbit_cli.hrl"). -export([start/0, stop/0]). -export([action/6]). --define(NODE_OPT, "-n"). --define(VERBOSE_OPT, "-v"). --define(MINIMAL_OPT, "-m"). --define(ENABLED_OPT, "-E"). --define(ENABLED_ALL_OPT, "-e"). --define(OFFLINE_OPT, "--offline"). --define(ONLINE_OPT, "--online"). - --define(NODE_DEF(Node), {?NODE_OPT, {option, Node}}). --define(VERBOSE_DEF, {?VERBOSE_OPT, flag}). --define(MINIMAL_DEF, {?MINIMAL_OPT, flag}). --define(ENABLED_DEF, {?ENABLED_OPT, flag}). --define(ENABLED_ALL_DEF, {?ENABLED_ALL_OPT, flag}). --define(OFFLINE_DEF, {?OFFLINE_OPT, flag}). --define(ONLINE_DEF, {?ONLINE_OPT, flag}). - --define(RPC_TIMEOUT, infinity). - -define(GLOBAL_DEFS(Node), [?NODE_DEF(Node)]). -define(COMMANDS, @@ -53,61 +36,21 @@ -spec(start/0 :: () -> no_return()). -spec(stop/0 :: () -> 'ok'). --spec(usage/0 :: () -> no_return()). -endif. %%---------------------------------------------------------------------------- start() -> - {ok, [[PluginsFile|_]|_]} = - init:get_argument(enabled_plugins_file), - {ok, [[NodeStr|_]|_]} = init:get_argument(nodename), - {ok, [[PluginsDir|_]|_]} = init:get_argument(plugins_dist_dir), - {Command, Opts, Args} = - case parse_arguments(init:get_plain_arguments(), NodeStr) of - {ok, Res} -> Res; - no_command -> print_error("could not recognise command", []), - usage() - end, - - PrintInvalidCommandError = - fun () -> - print_error("invalid command '~s'", - [string:join([atom_to_list(Command) | Args], " ")]) - end, - - Node = proplists:get_value(?NODE_OPT, Opts), - case catch action(Command, Node, Args, Opts, PluginsFile, PluginsDir) of - ok -> - rabbit_misc:quit(0); - {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> - PrintInvalidCommandError(), - usage(); - {'EXIT', {function_clause, [{?MODULE, action, _, _} | _]}} -> - PrintInvalidCommandError(), - usage(); - {error, {missing_dependencies, Missing, Blame}} -> - print_error("dependent plugins ~p not found; used by ~p.", - [Missing, Blame]), - rabbit_misc:quit(2); - {error, Reason} -> - print_error("~p", [Reason]), - rabbit_misc:quit(2); - {error_string, Reason} -> - print_error("~s", [Reason]), - rabbit_misc:quit(2); - {badrpc, {'EXIT', Reason}} -> - print_error("~p", [Reason]), - rabbit_misc:quit(2); - {badrpc, Reason} -> - print_error("unable to connect to node ~w: ~w", [Node, Reason]), - print_badrpc_diagnostics([Node]), - rabbit_misc:quit(2); - Other -> - print_error("~p", [Other]), - rabbit_misc:quit(2) - end. + {ok, [[PluginsFile|_]|_]} = init:get_argument(enabled_plugins_file), + {ok, [[PluginsDir |_]|_]} = init:get_argument(plugins_dist_dir), + rabbit_cli:main( + fun (Args, NodeStr) -> + parse_arguments(Args, NodeStr) + end, + fun (Command, Node, Args, Opts) -> + action(Command, Node, Args, Opts, PluginsFile, PluginsDir) + end, rabbit_plugins_usage). stop() -> ok. @@ -115,17 +58,8 @@ stop() -> %%---------------------------------------------------------------------------- parse_arguments(CmdLine, NodeStr) -> - case rabbit_misc:parse_arguments( - ?COMMANDS, ?GLOBAL_DEFS(NodeStr), CmdLine) of - {ok, {Cmd, Opts0, Args}} -> - Opts = [case K of - ?NODE_OPT -> {?NODE_OPT, rabbit_nodes:make(V)}; - _ -> {K, V} - end || {K, V} <- Opts0], - {ok, {Cmd, Opts, Args}}; - E -> - E - end. + rabbit_cli:parse_arguments( + ?COMMANDS, ?GLOBAL_DEFS(NodeStr), ?NODE_OPT, CmdLine). action(list, Node, [], Opts, PluginsFile, PluginsDir) -> action(list, Node, [".*"], Opts, PluginsFile, PluginsDir); @@ -212,17 +146,6 @@ action(sync, Node, [], _Opts, PluginsFile, _PluginsDir) -> %%---------------------------------------------------------------------------- -fmt_stderr(Format, Args) -> rabbit_misc:format_stderr(Format ++ "~n", Args). - -print_error(Format, Args) -> fmt_stderr("Error: " ++ Format, Args). - -print_badrpc_diagnostics(Nodes) -> - fmt_stderr(rabbit_nodes:diagnostics(Nodes), []). - -usage() -> - io:format("~s", [rabbit_plugins_usage:usage()]), - rabbit_misc:quit(1). - %% Pretty print a list of plugins. format_plugins(Node, Pattern, Opts, PluginsFile, PluginsDir) -> Verbose = proplists:get_bool(?VERBOSE_OPT, Opts), -- cgit v1.2.1