Skip to content

Commit 4c44183

Browse files
author
Peter Tihanyi
committed
Add wildcard command support
1 parent 61a5eb3 commit 4c44183

6 files changed

+149
-18
lines changed

README.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,24 @@ Example:
7373
cli_console:register(["list", "partitions"], [All, Node, Limit], fun list_partitions/1, "List partitions").
7474
```
7575

76+
Or
77+
78+
Example:
79+
```erlang
80+
cli_console:register(["list", "partitions", Node], [All, Limit], funlist_partitions/1, "List partitions").
81+
```
82+
7683
####Run command
7784

7885
Example:
7986
```erlang
80-
cli_console:run("list partitions --limit 123 -node=test --all").
87+
cli_console:run("list partitions --limit 123 -node=testnode --all").
88+
```
89+
90+
or
91+
92+
```erlang
93+
cli_console:run("list partitions testnode --limit 123 --all").
8194
```
8295

8396
Output:

src/cli_console_command.erl

+80-12
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ init(_) ->
6666
handle_call({run, Command, Args}, From, State = #state{}) ->
6767
case ets:lookup(?ETS_TABLE_NAME, Command) of
6868
[] ->
69-
{reply, {error, command_not_found}, State};
70-
[{_, ArgsDef, Fun, _Description}] ->
71-
CommandPid = spawn_run_command(Command, Args, ArgsDef, From, Fun),
72-
RunningCommands = State#state.running_commands,
73-
{noreply,
74-
State#state{running_commands = [{CommandPid, From} | RunningCommands]}}
69+
case get_wildcard_command(Command) of
70+
[] ->
71+
{reply, {error, command_not_found}, State};
72+
Result ->
73+
evaluate_command_lookup_result(Result, Command, Args, From, State)
74+
end;
75+
Else ->
76+
evaluate_command_lookup_result(Else, Command, Args, From, State)
7577
end;
7678
handle_call({get_help, Command}, _From, State = #state{}) ->
7779
Help = do_get_help(Command),
@@ -107,6 +109,13 @@ spawn_run_command(Command, Args, ArgsDef, From, Fun) ->
107109
gen_server:reply(From, CommandResult)
108110
end).
109111

112+
evaluate_command_lookup_result([{CommandSpec, ArgsDef, Fun, _Description}],
113+
Command, Args, From, State) ->
114+
NewArgs = extract_command_args(CommandSpec, Command, Args),
115+
CommandPid = spawn_run_command(Command, NewArgs, ArgsDef, From, Fun),
116+
RunningCommands = State#state.running_commands,
117+
{noreply, State#state{running_commands = [{CommandPid, From} | RunningCommands]}}.
118+
110119
run_command(Command, Args, ArgsDef, Fun) ->
111120
case convert(ArgsDef, Args) of
112121
{ok, NewArgs} ->
@@ -223,7 +232,7 @@ is_numeric(Value) ->
223232
do_get_help([]) ->
224233
ets:foldr(fun({Command, _, _, Desc}, Acc) ->
225234
[{Command, Desc} | Acc]
226-
end, [], cli_commands);
235+
end, [], ?ETS_TABLE_NAME);
227236
do_get_help(InCommands) ->
228237
case ets:lookup(?ETS_TABLE_NAME, InCommands) of
229238
[] ->
@@ -233,9 +242,9 @@ do_get_help(InCommands) ->
233242
end.
234243

235244
do_help_lookup(InCommands) ->
236-
case ets:select(cli_commands, get_select_matchspec(InCommands, false)) of
245+
case ets:select(?ETS_TABLE_NAME, get_select_matchspec(InCommands, false)) of
237246
[] ->
238-
ets:select(cli_commands, get_select_matchspec(InCommands, true));
247+
ets:select(?ETS_TABLE_NAME, get_select_matchspec(InCommands, true));
239248
Else ->
240249
Else
241250
end.
@@ -267,9 +276,15 @@ format_command({Commands, Desc, Args}) ->
267276
cli_console_formatter:text("~nArguments: ") |
268277
[format_arg(Arg) || Arg <- Args]];
269278
format_command({Commands, Desc}) ->
270-
CommandStr = string:pad(string:join(Commands, " "), 32),
279+
NewCommands = lists:map(fun format_command_string/1, Commands),
280+
CommandStr = string:pad(string:join(NewCommands, " "), 32),
271281
cli_console_formatter:text("~ts ~ts", [CommandStr, Desc]).
272282

283+
format_command_string(#argument{name = Name}) ->
284+
"<" ++ Name ++">";
285+
format_command_string(Command) ->
286+
Command.
287+
273288
format_arg(#argument{name = Name, optional = Optional,
274289
default = Default, description = Desc}) ->
275290

@@ -303,14 +318,31 @@ is_argument_defined(Arg, ArgsDef) ->
303318
end.
304319

305320
register_command(Command, ArgsDef, Fun, Description) ->
306-
case check_multiple_argdefs(ArgsDef) of
321+
CommandArguments = get_command_arguments(Command),
322+
case check_multiple_argdefs(ArgsDef ++ CommandArguments) of
307323
ok ->
324+
ets:match_delete(?ETS_TABLE_NAME, {generate_match_head(Command),
325+
generate_filter(Command), []}),
308326
true = ets:insert(?ETS_TABLE_NAME, {Command, ArgsDef, Fun, Description}),
309327
ok;
310328
Else ->
311329
Else
312330
end.
313331

332+
get_command_arguments(Command) ->
333+
lists:filter(fun(#argument{}) -> true;
334+
(_) -> false
335+
end, Command).
336+
337+
get_command_arguments_with_index(Command) ->
338+
{_, Result} =
339+
lists:foldl(fun(#argument{} = Item, {ItemSeq, Acc}) ->
340+
{ItemSeq+1, [{ItemSeq, Item} | Acc]};
341+
(_, {ItemSeq, Acc}) ->
342+
{ItemSeq+1, Acc}
343+
end, {1, []}, Command),
344+
Result.
345+
314346
check_multiple_argdefs(Argdefs) ->
315347
check_multiple_argdefs(Argdefs, []).
316348

@@ -333,4 +365,40 @@ format_help(HelpCommand, Acc) ->
333365
lists:foldr(fun(Item, Acc2) -> [Item | Acc2] end, Acc, Result);
334366
Item ->
335367
[Item | Acc]
336-
end.
368+
end.
369+
370+
get_wildcard_command(Command) ->
371+
case ets:select(?ETS_TABLE_NAME, [{generate_match_head(Command),
372+
generate_filter(Command), ['$_']}]) of
373+
[] ->
374+
[];
375+
[{Commands, ArgsDef, Fun, Description}] ->
376+
CommandArguments = get_command_arguments(Commands),
377+
[{Commands, ArgsDef ++ CommandArguments, Fun, Description}]
378+
end.
379+
380+
generate_match_head(Command) ->
381+
CommandHead = [item_num(ItemNum) || ItemNum <- lists:seq(1, length(Command))],
382+
{CommandHead, '_', '_', '_'}.
383+
384+
generate_filter(Command) ->
385+
generate_filter(Command, 1, []).
386+
387+
generate_filter([], _ItemCount, Where) ->
388+
lists:reverse(Where);
389+
generate_filter([Command | Rest], ItemCount, Where) ->
390+
Item = item_num(ItemCount),
391+
Filter = {'orelse',
392+
{'==', Command, Item},
393+
{'==', argument, {element, 1, Item}}
394+
},
395+
generate_filter(Rest, ItemCount+1, [Filter | Where]).
396+
397+
item_num(ItemNum) ->
398+
list_to_atom("$" ++ integer_to_list(ItemNum)).
399+
400+
extract_command_args(CommandSpec, Command, Args) ->
401+
CommandArgWithIndex = get_command_arguments_with_index(CommandSpec),
402+
lists:foldl(fun({Index, #argument{name = Name}}, Acc) ->
403+
[{Name, lists:nth(Index, Command)} | Acc]
404+
end, Args, CommandArgWithIndex).

src/cli_console_command.hrl

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020

2121
-type command_fun() :: fun((proplists:proplist()) -> [output_format()] | output_format() ).
2222

23-
-type command() :: string().
23+
-type command() :: string() | command_argument().
2424

2525
-export_type([command_argument/0]).

test/cli_console_SUITE.erl

+9-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ all() ->
119119
handle_errors,
120120
crash_in_function,
121121
default_value_flag,
122-
pid_not_in_state
122+
pid_not_in_state,
123+
wild_card_argument_help
123124
].
124125

125126
%%--------------------------------------------------------------------
@@ -375,6 +376,13 @@ default_value_flag(_Config) ->
375376
?assertEqual("\e[37;1mHelp\n\n\e[0mconnections List connections status\n\nArguments: \n -all desc\n Default value: false\n -limit desc\n Default value: 10\n -skip_echo desc\n \n",
376377
Output).
377378

379+
wild_card_argument_help(_Config) ->
380+
Fun = fun(_Args) -> [{text, "ok"}] end,
381+
Arg1 = cli_console_command_arg:argument("table", string, "Table name"),
382+
Arg2 = cli_console_command_arg:argument("type", string, "Table type"),
383+
ok = cli_console_command:register(["wild", Arg1, Arg2], [], Fun, "Wild help"),
384+
?assertEqual("Command not found\n\n\e[37;1mHelp\n\n\e[0mwild <table> <type> Wild help\n",
385+
catch_output(fun() -> cli_console:run(["wild"]) end)).
378386

379387
list_partitions(Args) ->
380388
[cli_console_formatter:title("List of partions"),

test/cli_console_command_SUITE.erl

+31
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ groups() ->
107107
all() ->
108108
[ register_no_argument,
109109
register_the_twice,
110+
register_wildcard,
111+
register_multiple_wildcard,
112+
register_wildcard_same_arg,
110113
command_not_found,
111114
mandatory_arg_not_set_found,
112115
mandatory_arg_not_set_but_has_default,
@@ -134,6 +137,34 @@ register_the_twice(_Config) ->
134137
ok = cli_console_command:register(["foo"], [], ?DUMMY_FUN(ok2), ""),
135138
?assertEqual({ok, ok2}, cli_console_command:run(["foo"], [])).
136139

140+
register_wildcard(_Config) ->
141+
Fun = fun(Args) -> Args end,
142+
WildcardArgument = cli_console_command_arg:argument("wildcard", string,
143+
"Desc of wildcard"),
144+
ok = cli_console_command:register(["wild", WildcardArgument], [], Fun,
145+
"Wild help"),
146+
?assertEqual({ok, [{"wildcard", "things"}]},
147+
cli_console_command:run(["wild", "things"], [])).
148+
149+
register_multiple_wildcard(_Config) ->
150+
Fun = fun(Args) -> Args end,
151+
WildcardArgument = cli_console_command_arg:argument("wildcard", string,
152+
"Desc of wildcard"),
153+
WildcardArgument2 = cli_console_command_arg:argument("wildcard2", string,
154+
"Desc of wildcard2"),
155+
ok = cli_console_command:register(["wild", WildcardArgument, WildcardArgument2],
156+
[], Fun, "Wild help"),
157+
?assertEqual({ok, [{"wildcard2", "wild"}, {"wildcard", "things"}]},
158+
cli_console_command:run(["wild", "things", "wild"], [])).
159+
160+
register_wildcard_same_arg(_Config) ->
161+
Fun = fun(Args) -> {ok, Args} end,
162+
WildcardArgument = cli_console_command_arg:argument("wildcard", string,
163+
"Desc of wildcard"),
164+
Result = cli_console_command:register(["wild", WildcardArgument],
165+
[WildcardArgument], Fun, "Wild help"),
166+
?assertEqual({error, {multiple_argument_definitions, "wildcard"}}, Result).
167+
137168
command_not_found(_Config) ->
138169
?assertEqual({error, command_not_found},
139170
cli_console_command:run(["not_found_command"], [])).

test/prop_command.erl

+14-3
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
%%%%%%%%%%%%%%%%%%
88
prop_test() ->
99
application:ensure_all_started(cli_console),
10-
?FORALL({ArgsDef, Args, Description}, {arguments_defs(), arguemnts(), string()},
10+
?FORALL({Command, ArgsDef, Args, Description},
11+
{test_command(), arguments_defs(), arguemnts(), string()},
1112
begin
12-
Command = ["cmd", "run"],
13+
%Command = ["cmd", "run"],
1314
case cli_console_command:register(Command, ArgsDef, fun(A) -> A
1415
end,
1516
Description) of
1617
ok ->
18+
1719
% test against crash, or unhandled stuff
18-
case cli_console_command:run(Command, Args) of
20+
case cli_console_command:run(normalize_command(Command), Args) of
1921
{ok, ArgsResult} ->
2022
assert_ok(ArgsDef, ArgsResult, Args);
2123
{error, Error} ->
@@ -60,6 +62,11 @@ assert_ok(_ArgsDef, [{format, _, _} | _], Args) ->
6062
true
6163
end.
6264

65+
normalize_command([_, _] = Command) ->
66+
Command;
67+
normalize_command([A, B, _C]) ->
68+
[A, B, "1"].
69+
6370
%%%%%%%%%%%%%%%%%%
6471
%%% Generators %%%
6572
%%%%%%%%%%%%%%%%%%
@@ -89,6 +96,10 @@ arguments_def() ->
8996
maybe_set_default(Default, Arg1)
9097
end).
9198

99+
test_command() ->
100+
frequency([{20, ["cmd", "run"]},
101+
{1, ["cmd", "run", arguments_def()]}]).
102+
92103
maybe_set_mandatory(true, Arg) ->
93104
cli_console_command_arg:mandatory(Arg);
94105
maybe_set_mandatory(false, Arg) ->

0 commit comments

Comments
 (0)