machi/plugins/riak_pb_msgcodegen.erl

149 lines
5.1 KiB
Erlang
Raw Normal View History

%% @doc Generates a codec-mapping module from a CSV mapping of message
%% codes to messages in .proto files.
-module(riak_pb_msgcodegen).
-export([preprocess/2,
clean/2]).
%% -include_lib("rebar/include/rebar.hrl").
-define(FAIL, rebar_utils:abort()).
-define(ABORT(Str, Args), rebar_utils:abort(Str, Args)).
-define(CONSOLE(Str, Args), io:format(Str, Args)).
-define(DEBUG(Str, Args), rebar_log:log(debug, Str, Args)).
-define(INFO(Str, Args), rebar_log:log(info, Str, Args)).
-define(WARN(Str, Args), rebar_log:log(warn, Str, Args)).
-define(ERROR(Str, Args), rebar_log:log(error, Str, Args)).
-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))).
-define(MODULE_COMMENTS(CSV),
["%% @doc This module contains message code mappings generated from\n%% ",
CSV,". DO NOT EDIT OR COMMIT THIS FILE!\n"]).
%% ===================================================================
%% Public API
%% ===================================================================
preprocess(Config, _AppFile) ->
case rebar_config:get(Config, current_command, undefined) of
'compile' ->
case rebar_utils:find_files("src", ".*\\.csv") of
[] ->
ok;
FoundFiles ->
Targets = [{CSV, fq_erl_file(CSV)} || CSV <- FoundFiles ],
generate_each(Config, Targets)
end;
_Else -> ok
end,
{ok, Config, []}.
clean(_Config, _AppFile) ->
CSVs = rebar_utils:find_files("src", ".*\\.csv"),
ErlFiles = [fq_erl_file(CSV) || CSV <- CSVs],
case ErlFiles of
[] -> ok;
_ -> delete_each(ErlFiles)
end.
%% ===================================================================
%% Internal functions
%% ===================================================================
generate_each(_Config, []) ->
ok;
generate_each(Config, [{CSV, Erl}|Rest]) ->
case is_modified(CSV, Erl) of
false ->
ok;
true ->
Tuples = load_csv(CSV),
Module = generate_module(mod_name(CSV), Tuples),
Formatted = erl_prettypr:format(Module),
ok = file:write_file(Erl, [?MODULE_COMMENTS(CSV), Formatted]),
?CONSOLE("Generated ~s~n", [Erl])
end,
generate_each(Config, Rest).
is_modified(CSV, Erl) ->
not filelib:is_regular(Erl) orelse
filelib:last_modified(CSV) > filelib:last_modified(Erl).
mod_name(SourceFile) ->
filename:basename(SourceFile, ".csv").
fq_erl_file(SourceFile) ->
filename:join(["src", erl_file(SourceFile)]).
erl_file(SourceFile) ->
mod_name(SourceFile) ++ ".erl".
load_csv(SourceFile) ->
{ok, Bin} = file:read_file(SourceFile),
csv_to_tuples(unicode:characters_to_list(Bin, latin1)).
csv_to_tuples(String) ->
Lines = string:tokens(String, [$\r,$\n]),
[ begin
[Code, Message, Proto] = string:tokens(Line, ","),
{list_to_integer(Code), string:to_lower(Message), Proto ++ "_pb"}
end
|| Line <- Lines].
generate_module(Name, Tuples) ->
%% TODO: Add generated doc comment at the top
Mod = erl_syntax:attribute(erl_syntax:atom(module),
[erl_syntax:atom(Name)]),
ExportsList = [
erl_syntax:arity_qualifier(erl_syntax:atom(Fun), erl_syntax:integer(1))
|| Fun <- [msg_type, msg_code, decoder_for] ],
Exports = erl_syntax:attribute(erl_syntax:atom(export),
[erl_syntax:list(ExportsList)]),
Clauses = generate_msg_type(Tuples) ++
generate_msg_code(Tuples) ++
generate_decoder_for(Tuples),
erl_syntax:form_list([Mod, Exports|Clauses]).
generate_decoder_for(Tuples) ->
Spec = erl_syntax:text("-spec decoder_for(non_neg_integer()) -> module().\n"),
Name = erl_syntax:atom(decoder_for),
Clauses = [
erl_syntax:clause([erl_syntax:integer(Code)],
none,
[erl_syntax:atom(Mod)])
|| {Code, _, Mod} <- Tuples ],
[ Spec, erl_syntax:function(Name, Clauses) ].
generate_msg_code(Tuples) ->
Spec = erl_syntax:text("-spec msg_code(atom()) -> non_neg_integer()."),
Name = erl_syntax:atom(msg_code),
Clauses = [
erl_syntax:clause([erl_syntax:atom(Msg)], none, [erl_syntax:integer(Code)])
|| {Code, Msg, _} <- Tuples ],
[ Spec, erl_syntax:function(Name, Clauses) ].
generate_msg_type(Tuples) ->
Spec = erl_syntax:text("-spec msg_type(non_neg_integer()) -> atom()."),
Name = erl_syntax:atom(msg_type),
Clauses = [
erl_syntax:clause([erl_syntax:integer(Code)], none, [erl_syntax:atom(Msg)])
|| {Code, Msg, _} <- Tuples ],
CatchAll = erl_syntax:clause([erl_syntax:underscore()], none, [erl_syntax:atom(undefined)]),
[ Spec, erl_syntax:function(Name, Clauses ++ [CatchAll]) ].
delete_each([]) ->
ok;
delete_each([File | Rest]) ->
case file:delete(File) of
ok ->
ok;
{error, enoent} ->
ok;
{error, Reason} ->
?ERROR("Failed to delete ~s: ~p\n", [File, Reason])
end,
delete_each(Rest).