Skip to content

Commit 1e1a969

Browse files
committed
Merge pull request #42 from basho/feature/csv_formatter
Add CSV formatter and support for other custom formats in general Reviewed-by: macintux
2 parents 3dabe01 + d20f641 commit 1e1a969

13 files changed

+346
-128
lines changed

README.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Clique provides the application developer with the following capabilities:
3030
configuration across one or all nodes: i.e. `riak-admin set anti-entropy=on --all`
3131
* Return a standard status format that allows output of a variety of content
3232
types: human-readable, csv, html, etc... (Note that currently only
33-
human-readable output is implemented)
33+
human-readable and CSV output formats are implemented)
3434

3535
### Why Not Clique ?
3636
* You aren't writing a CLI
@@ -309,6 +309,19 @@ clique:register_usage(["riak-admin", "handoff"], handoff_usage()),
309309
clique:register_usage(["riak-admin", "handoff", "limit"], fun handoff_limit_usage/0).
310310
```
311311

312+
### register_writer/2
313+
This is not something most applications will likely need to use, but the
314+
capability exists to create custom output writer modules. Currently you can
315+
specify the `--format=[human|csv]` flag on many commands to determine how the
316+
output will be written; registering a new writer "foo" allows you to use
317+
`--format=foo` to write the output using whatever corresponding writer module
318+
you've registered.
319+
320+
Writing custom output writers is relatively undocumented right now, and the
321+
values passed to the `write/1` callback may be subject to future changes. But,
322+
the `clique_*_writer` modules in the clique source tree provide good examples
323+
that can be used for reference.
324+
312325
### run/1
313326
`run/1` takes a given command as a list of strings and attempts to run the
314327
command using the registered information. If called with `set`, `show`, or

include/clique_status_types.hrl

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
%% csv and a subset of html, as well as other possible output.
66
-type text() :: {text, iolist()}.
77
-type status_list() :: {list, iolist(), [iolist()]} | {list, [iolist()]}.
8-
-type table() :: {table, [{iolist(), iolist()}]}.
8+
-type table() :: {table, [[{atom() | string(), term()}]]}.
99
-type alert() :: {alert, [status_list() | table() | text()]}.
1010
-type usage() :: usage.
1111
-type elem() :: text() | status_list() | table() | alert() | usage().

rebar.config

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
{xref_checks, []}.
77
{xref_queries, [{"(XC - UC) || (XU - X - B - \"(cluster_info|dtrace)\" : Mod)", []}]}.
88

9+
{erl_first_files, [
10+
"src/clique_writer.erl",
11+
"src/clique_handler.erl"
12+
]}.
13+
914
{deps, [
1015
{cuttlefish, ".*", {git, "git://github.com/basho/cuttlefish.git", {branch, "2.0"}}}
1116
]}.

src/clique.erl

+27-35
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
register_command/4,
2626
register_config/2,
2727
register_formatter/2,
28+
register_writer/2,
2829
register_config_whitelist/1,
2930
register_usage/2,
3031
run/1,
@@ -56,6 +57,11 @@ register_config(Key, Callback) ->
5657
register_formatter(Key, Callback) ->
5758
clique_config:register_formatter(Key, Callback).
5859

60+
%% @doc Register a module for writing output in a specific format
61+
-spec register_writer(string(), module()) -> true.
62+
register_writer(Name, Module) ->
63+
clique_writer:register(Name, Module).
64+
5965
%% @doc Register a list of configuration variables that are settable.
6066
%% Clique disallows setting of all config variables by default. They must be in
6167
%% whitelist to be settable.
@@ -75,43 +81,29 @@ register_usage(Cmd, Usage) ->
7581
clique_usage:register(Cmd, Usage).
7682

7783
%% @doc Take a list of status types and generate console output
78-
-spec print(err() | clique_status:status(), [string()]) -> ok.
79-
print(usage, Cmd) ->
84+
-spec print(err() | clique_status:status() | {clique_status:status(), string()}, [string()]) -> ok.
85+
print({error, _} = E, Cmd) ->
86+
print(E, Cmd, "human");
87+
print({Status, Format}, Cmd) ->
88+
print(Status, Cmd, Format);
89+
print(Status, Cmd) ->
90+
print(Status, Cmd, "human").
91+
92+
-spec print(usage | err() | clique_status:status(), [string()], string()) -> ok.
93+
print(usage, Cmd, _Format) ->
8094
clique_usage:print(Cmd);
81-
print({error, _}=E, Cmd) ->
95+
print({error, _}=E, Cmd, Format) ->
8296
Alert = clique_error:format(hd(Cmd), E),
83-
print(Alert, Cmd);
84-
print(Status, _Cmd) ->
85-
Output = clique_human_writer:write(Status),
86-
io:format("~ts", [Output]),
87-
ok.
97+
print(Alert, Cmd, Format);
98+
print(Status, _Cmd, Format) ->
99+
Output = clique_writer:write(Status, Format),
100+
io:format("~ts", [Output]).
88101

89102
%% @doc Run a config operation or command
90103
-spec run([string()]) -> ok | {error, term()}.
91-
run([_Script, "set" | Args] = Cmd) ->
92-
print(clique_config:set(Args), Cmd);
93-
run([_Script, "show" | Args] = Cmd) ->
94-
print(clique_config:show(Args), Cmd);
95-
run([_Script, "describe" | Args] = Cmd) ->
96-
print(clique_config:describe(Args), Cmd);
97-
run(Cmd0) ->
98-
case is_help(Cmd0) of
99-
{ok, Cmd} ->
100-
clique_usage:print(Cmd);
101-
_ ->
102-
M0 = clique_command:match(Cmd0),
103-
M1 = clique_parser:parse(M0),
104-
M2 = clique_parser:validate(M1),
105-
print(clique_command:run(M2), Cmd0)
106-
end.
107-
108-
%% @doc Help flags always comes at the end of the command
109-
-spec is_help(iolist()) -> {ok, iolist()} | false.
110-
is_help(Str) ->
111-
[H | T] = lists:reverse(Str),
112-
case H =:= "--help" orelse H =:= "-h" of
113-
true ->
114-
{ok, lists:reverse(T)};
115-
false ->
116-
false
117-
end.
104+
run(Cmd) ->
105+
M0 = clique_command:match(Cmd),
106+
M1 = clique_parser:parse(M0),
107+
M2 = clique_parser:extract_global_flags(M1),
108+
M3 = clique_parser:validate(M2),
109+
print(clique_command:run(M3), Cmd).

src/clique_command.erl

+37-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
-type proplist() :: [{atom(), term()}].
3333
-type status() :: clique_status:status().
3434

35+
-define(SET_CMD_SPEC, {["_", "set"], '_', clique_config:config_flags(), fun clique_config:set/2}).
36+
3537
init() ->
3638
_ = ets:new(?cmd_table, [public, named_table]),
3739
ok.
@@ -45,17 +47,36 @@ register(Cmd, Keys, Flags, Fun) ->
4547
({fun(), proplist(), proplist()})-> status().
4648
run({error, _}=E) ->
4749
E;
48-
run({Fun, Args, Flags}) ->
49-
Fun(Args, Flags).
50+
run({Fun, Args, Flags, GlobalFlags}) ->
51+
Format = proplists:get_value(format, GlobalFlags, "human"),
52+
case proplists:is_defined(help, GlobalFlags) of
53+
true ->
54+
{usage, Format};
55+
false ->
56+
Result = Fun(Args, Flags),
57+
{Result, Format}
58+
end.
5059

5160
-spec match([list()])-> {tuple(), list()} | {error, no_matching_spec}.
5261
match(Cmd0) ->
5362
{Cmd, Args} = split_command(Cmd0),
54-
case ets:lookup(?cmd_table, Cmd) of
55-
[Spec] ->
63+
%% Check for builtin commands first. If that fails, check our command table.
64+
case Cmd of
65+
[_Script, "set" | _] ->
66+
{?SET_CMD_SPEC, Args};
67+
[_Script, "show" | _] ->
68+
Spec = cmd_spec(Cmd, fun clique_config:show/2, clique_config:config_flags()),
69+
{Spec, Args};
70+
[_Script, "describe" | _] ->
71+
Spec = cmd_spec(Cmd, fun clique_config:describe/2, []),
5672
{Spec, Args};
57-
[] ->
58-
{error, {no_matching_spec, Cmd0}}
73+
_ ->
74+
case ets:lookup(?cmd_table, Cmd) of
75+
[Spec] ->
76+
{Spec, Args};
77+
[] ->
78+
{error, {no_matching_spec, Cmd0}}
79+
end
5980
end.
6081

6182
-spec split_command([list()]) -> {list(), list()}.
@@ -65,3 +86,13 @@ split_command(Cmd0) ->
6586
clique_parser:is_not_flag(Str)
6687
end, Cmd0).
6788

89+
%% NB This is a bit sneaky. We normally only accept key/value args like
90+
%% "handoff.inbound=off" and flag-style arguments like "--node [email protected]" or "--all",
91+
%% but the builtin "show" and "describe" commands work a bit differently.
92+
%% To handle these special cases, we dynamically build a command spec to smuggle the
93+
%% arguments through the rest of the (otherwise cleanly designed and implemented) code.
94+
cmd_spec(Cmd, CmdFun, AllowedFlags) ->
95+
[_Script, _CmdName | CfgKeys] = Cmd,
96+
%% Discard key/val args passed in since we don't need them, and inject the freeform args:
97+
SpecFun = fun([], Flags) -> CmdFun(CfgKeys, Flags) end,
98+
{Cmd, [], AllowedFlags, SpecFun}.

0 commit comments

Comments
 (0)