Skip to content

Commit 1b62996

Browse files
committed
WIP
1 parent ad1525d commit 1b62996

File tree

7 files changed

+268
-12
lines changed

7 files changed

+268
-12
lines changed

deps/rabbit/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
150150
include ../../rabbitmq-components.mk
151151
include ../../erlang.mk
152152

153-
CLI_SCRIPTS := scripts/rmq3
153+
CLI_SCRIPTS := scripts/rmq
154154

155155
ebin/$(PROJECT).app:: $(CLI_SCRIPTS)
156156

deps/rabbit/src/rabbit_cli_cmd_list_exchanges.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ list_exchanges(Args) ->
3232
rabbit_exchange,
3333
info_all,
3434
[VHost, InfoKeys]),
35-
io:format("~p~n", [Ret]).
35+
rabbit_cli_output:sync_notify(Ret).
3636

3737
get_nodename(#{node := Nodename}) ->
3838
Nodename;

deps/rabbit/src/rabbit_cli_cmd_version.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ cli() ->
1414
show_version(_) ->
1515
ok = application:load(rabbit),
1616
{ok, Version} = application:get_key(rabbit, vsn),
17-
io:format("~s~n", [Version]).
17+
rabbit_cli_output:sync_notify(Version).

deps/rabbit/src/rabbit_cli_escript.erl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ main(Args) ->
88
ProgName = list_to_atom(filename:basename(ScriptName, ".bat")),
99

1010
%% Add RabbitMQ components to Erlang code path.
11-
ErlLibs = filename:join([ScriptDir, "..", "plugins"]),
11+
ErlLibs = case filename:basename(ScriptDir) of
12+
"scripts" -> filename:join([ScriptDir, "..", ".."]);
13+
_ -> filename:join([ScriptDir, "..", "plugins"])
14+
end,
1215
lists:foreach(
1316
fun(Dir) -> true = code:add_path(Dir) end,
1417
filelib:wildcard(filename:join([ErlLibs, "*", "ebin"]))),
1518

1619
%% Run the CLI.
17-
Mods = [% FIXME: Generate that list.
18-
rabbit_cli_global_options,
19-
rabbit_cli_cmd_list_exchanges,
20-
rabbit_cli_cmd_version
21-
],
22-
cli:run(Args, #{progname => ProgName,
23-
modules => Mods,
24-
warn => suppress}).
20+
case rabbit_cli_main:run(ProgName, Args) of
21+
ok -> halt();
22+
_ -> halt(1)
23+
end.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
-module(rabbit_cli_main).
2+
3+
-export([run/2]).
4+
5+
-spec run(string(), iodata()) -> ok.
6+
7+
run(_ProgName, RawArgs) ->
8+
%% 1. Argument parsing.
9+
CommandMods = discover_commands(),
10+
CommandMap = collect_args_spec(CommandMods, #{}),
11+
Ret = argparse:parse(RawArgs, CommandMap),
12+
13+
%% 2. Output setup.
14+
rabbit_cli_output:setup(Ret),
15+
logger:set_primary_config(level, debug),
16+
%logger:i(),
17+
logger:debug("argparse:parse/2 -> ~p~n", [Ret]),
18+
19+
%% 3. Command execution.
20+
case Ret of
21+
{ArgMap, PathTo} ->
22+
run_handler(
23+
CommandMap, ArgMap, PathTo, undefined);
24+
ArgMap ->
25+
%{ maps:find(default, Options), Modules, Options}
26+
run_handler(
27+
CommandMap, ArgMap, {[], CommandMap}, {CommandMods, #{}})
28+
end,
29+
30+
31+
%% 4. Close output and return exit status.
32+
rabbit_cli_output:close(),
33+
ok.
34+
35+
discover_commands() ->
36+
[% TODO: Generate that list.
37+
rabbit_cli_global_options,
38+
rabbit_cli_cmd_list_exchanges,
39+
rabbit_cli_cmd_version
40+
].
41+
42+
%% -------------------------------------------------------------------
43+
%% Copied from `cli` module (argparse 1.1.0).
44+
%% -------------------------------------------------------------------
45+
46+
collect_args_spec(Modules, Options) when is_list(Modules) ->
47+
lists:foldl(
48+
fun (Mod, Cmds) ->
49+
ModCmd =
50+
try
51+
{_, MCmd} = argparse:validate(Mod:cli(), Options),
52+
MCmd
53+
catch
54+
_:_ ->
55+
%% TODO: Handle error.
56+
#{}
57+
end,
58+
59+
%% handlers: use first non-empty handler
60+
Cmds1 =
61+
case maps:find(handler, ModCmd) of
62+
{ok, _Handler} when is_map_key(handler, Cmds) ->
63+
%% TODO: Warn about duplicate handlers.
64+
Cmds;
65+
{ok, Handler} ->
66+
Cmds#{handler => Handler};
67+
error ->
68+
Cmds
69+
end,
70+
71+
%% help: concatenate help lines
72+
Cmds2 =
73+
case is_map_key(help, ModCmd) of
74+
true ->
75+
Cmds1#{
76+
help =>
77+
maps:get(help, ModCmd) ++
78+
maps:get(help, Cmds1, "")
79+
};
80+
false ->
81+
Cmds1
82+
end,
83+
84+
Cmds3 = merge_arguments(
85+
maps:get(arguments, ModCmd, []),
86+
Cmds2),
87+
merge_commands(
88+
maps:get(commands, ModCmd, #{}),
89+
Mod,
90+
Options,
91+
Cmds3)
92+
end, #{}, Modules).
93+
94+
merge_arguments([], Existing) ->
95+
Existing;
96+
merge_arguments(Args, Existing) ->
97+
ExistingArgs = maps:get(arguments, Existing, []),
98+
Existing#{arguments => ExistingArgs ++ Args}.
99+
100+
%% argparse accepts a map of commands, which means, commands names
101+
%% can never clash. Yet for cli it is possible when multiple modules
102+
%% export command with the same name. For this case, skip duplicate
103+
%% command names, emitting a warning.
104+
merge_commands(Cmds, Mod, Options, Existing) ->
105+
MergedCmds = maps:fold(
106+
fun (Name, Cmd, Acc) ->
107+
case maps:find(Name, Acc) of
108+
error ->
109+
%% merge command with name Name into Acc-umulator
110+
Acc#{
111+
Name =>
112+
create_handlers(
113+
Mod, Name, Cmd, maps:find(default, Options))
114+
};
115+
{ok, _Another} ->
116+
%% TODO: Warn about command conflict.
117+
Acc
118+
end
119+
end, maps:get(commands, Existing, #{}), Cmds),
120+
Existing#{commands => MergedCmds}.
121+
122+
%% Descends into sub-commands creating handlers where applicable
123+
create_handlers(Mod, _CmdName, Cmd0, DefaultTerm) ->
124+
Handler =
125+
case maps:find(handler, Cmd0) of
126+
% TODO:
127+
%error ->
128+
% make_handler(CmdName, Mod, DefaultTerm);
129+
%{ok, optional} ->
130+
% make_handler(CmdName, Mod, DefaultTerm);
131+
{ok, Existing} ->
132+
Existing
133+
end,
134+
%%
135+
Cmd = Cmd0#{handler => Handler},
136+
case maps:find(commands, Cmd) of
137+
error ->
138+
Cmd;
139+
{ok, Sub} ->
140+
NewCmds = maps:map(
141+
fun (CN, CV) ->
142+
create_handlers(Mod, CN, CV, DefaultTerm)
143+
end,
144+
Sub),
145+
Cmd#{commands => NewCmds}
146+
end.
147+
148+
run_handler(CmdMap, ArgMap, {Path, #{handler := {Mod, ModFun, Default}}}, _MO) ->
149+
ArgList = arg_map_to_arg_list(CmdMap, Path, ArgMap, Default),
150+
%% if argument count may not match, better error can be produced
151+
erlang:apply(Mod, ModFun, ArgList);
152+
run_handler(_CmdMap, ArgMap, {_Path, #{handler := {Mod, ModFun}}}, _MO) when is_atom(Mod), is_atom(ModFun) ->
153+
Mod:ModFun(ArgMap);
154+
run_handler(CmdMap, ArgMap, {Path, #{handler := {Fun, Default}}}, _MO) when is_function(Fun) ->
155+
ArgList = arg_map_to_arg_list(CmdMap, Path, ArgMap, Default),
156+
%% if argument count may not match, better error can be produced
157+
erlang:apply(Fun, ArgList);
158+
run_handler(_CmdMap, ArgMap, {_Path, #{handler := Handler}}, _MO) when is_function(Handler, 1) ->
159+
Handler(ArgMap);
160+
%% below is compatibility mode: cli/1 behaviour has been removed in 1.1.0, but
161+
%% is still honoured for existing users
162+
run_handler(CmdMap, ArgMap, {[], _}, {Modules, Options}) when is_map_key(default, Options) ->
163+
ArgList = arg_map_to_arg_list(CmdMap, [], ArgMap, maps:get(default, Options)),
164+
exec_cli(Modules, CmdMap, ArgList, Options);
165+
run_handler(CmdMap, ArgMap, {[], _}, {Modules, Options}) ->
166+
% {undefined, {ok, Default}, Modules, Options}
167+
exec_cli(Modules, CmdMap, [ArgMap], Options).
168+
169+
%% finds first module that exports ctl/1 and execute it
170+
exec_cli([], CmdMap, _ArgMap, ArgOpts) ->
171+
%% command not found, let's print usage
172+
io:format(argparse:help(CmdMap, ArgOpts));
173+
exec_cli([Mod|Tail], CmdMap, Args, ArgOpts) ->
174+
case erlang:function_exported(Mod, cli, length(Args)) of
175+
true ->
176+
erlang:apply(Mod, cli, Args);
177+
false ->
178+
exec_cli(Tail, CmdMap, Args, ArgOpts)
179+
end.
180+
181+
%% Given command map, path to reach a specific command, and a parsed argument
182+
%% map, returns a list of arguments (effectively used to transform map-based
183+
%% callback handler into positional).
184+
arg_map_to_arg_list(Command, Path, ArgMap, Default) ->
185+
AllArgs = collect_arguments(Command, Path, []),
186+
[maps:get(Arg, ArgMap, Default) || #{name := Arg} <- AllArgs].
187+
188+
%% recursively descend into Path, ignoring arguments with duplicate names
189+
collect_arguments(Command, [], Acc) ->
190+
Acc ++ maps:get(arguments, Command, []);
191+
collect_arguments(Command, [H|Tail], Acc) ->
192+
Args = maps:get(arguments, Command, []),
193+
Next = maps:get(H, maps:get(commands, Command, H)),
194+
collect_arguments(Next, Tail, Acc ++ Args).
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-module(rabbit_cli_output).
2+
3+
-export([setup/1,
4+
close/0,
5+
notify/1,
6+
sync_notify/1,
7+
log/2]).
8+
9+
-define(EVENT_MGR_REF, ?MODULE).
10+
11+
setup(Args) ->
12+
case gen_event:start_link({local, ?EVENT_MGR_REF}, []) of
13+
{ok, Pid} ->
14+
ok = gen_event:add_sup_handler(
15+
Pid,
16+
rabbit_cli_output_console,
17+
Args),
18+
ok = logger:add_handler(
19+
?EVENT_MGR_REF, ?MODULE, #{}),
20+
ok = logger:remove_handler(default),
21+
%% TODO: Register output for Erlang Logger.
22+
{ok, Pid}
23+
end.
24+
25+
close() ->
26+
gen_event:stop(rabbit_cli_output).
27+
28+
notify(Event) ->
29+
gen_event:notify(?EVENT_MGR_REF, Event).
30+
31+
sync_notify(Event) ->
32+
gen_event:sync_notify(?EVENT_MGR_REF, Event).
33+
34+
log(LogEvent, Config) ->
35+
sync_notify({log_event, LogEvent, Config}).
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-module(rabbit_cli_output_console).
2+
-behaviour(gen_event).
3+
4+
-export([init/1,
5+
handle_call/2,
6+
handle_event/2,
7+
handle_info/2,
8+
terminate/2,
9+
code_change/3]).
10+
11+
init(_) ->
12+
{ok, undefined}.
13+
14+
handle_call(_Request, State) ->
15+
{ok, ok, State}.
16+
17+
handle_event(Event, State) ->
18+
io:format("~p~n", [Event]),
19+
{ok, State}.
20+
21+
handle_info(_Info, State) ->
22+
{ok, State}.
23+
24+
terminate(_Arg, _State) ->
25+
ok.
26+
27+
code_change(_OldVsn, State, _Extra) ->
28+
{ok, State}.

0 commit comments

Comments
 (0)