mirror of
https://github.com/correl/rebar.git
synced 2024-11-14 19:19:30 +00:00
Basic implementation of templater is complete
This commit is contained in:
parent
1ea7065b83
commit
8c4f35a294
1 changed files with 186 additions and 8 deletions
|
@ -26,16 +26,71 @@
|
|||
%% -------------------------------------------------------------------
|
||||
-module(rebar_templater).
|
||||
|
||||
-export([create/2]).
|
||||
-export(['create-app'/2,
|
||||
create/2]).
|
||||
|
||||
-include("rebar.hrl").
|
||||
|
||||
-define(TEMPLATE_RE, ".*\\.template\$").
|
||||
|
||||
%% ===================================================================
|
||||
%% Public API
|
||||
%% ===================================================================
|
||||
|
||||
'create-app'(Config, File) ->
|
||||
%% Alias for create w/ template=simpleapp
|
||||
rebar_config:set_global(template, "simpleapp"),
|
||||
create(Config, File).
|
||||
|
||||
create(_Config, _) ->
|
||||
ok.
|
||||
%% Load a list of all the files in the escript -- cache it in the pdict
|
||||
%% since we'll potentially need to walk it several times over the course
|
||||
%% of a run.
|
||||
cache_escript_files(),
|
||||
|
||||
%% Build a list of available templates
|
||||
AvailTemplates = find_disk_templates() ++ find_escript_templates(),
|
||||
?DEBUG("Available templates: ~p\n", [AvailTemplates]),
|
||||
|
||||
%% Using the specified template id, find the matching template file/type.
|
||||
%% Note that if you define the same template in both ~/.rebar/templates
|
||||
%% that is also present in the escript, the one on the file system will
|
||||
%% be preferred.
|
||||
{Type, Template} = select_template(AvailTemplates, template_id()),
|
||||
|
||||
%% Load the template definition as is and get the list of variables the
|
||||
%% template requires.
|
||||
TemplateTerms = consult(load_file(Type, Template)),
|
||||
case lists:keysearch(variables, 1, TemplateTerms) of
|
||||
{value, {variables, Vars}} ->
|
||||
case parse_vars(Vars, dict:new()) of
|
||||
{error, Entry} ->
|
||||
Context0 = undefined,
|
||||
?ABORT("Failed while processing variables from template ~p. Variable definitions "
|
||||
"must follow form of [{atom(), term()}]. Failed at: ~p\n",
|
||||
[template_id(), Entry]);
|
||||
Context0 ->
|
||||
ok
|
||||
end;
|
||||
false ->
|
||||
?WARN("No variables section found in template ~p; using empty context.\n",
|
||||
[template_id()]),
|
||||
Context0 = dict:new()
|
||||
end,
|
||||
|
||||
%% For each variable, see if it's defined in global vars -- if it is, prefer that
|
||||
%% value over the defaults
|
||||
Context = update_vars(dict:fetch_keys(Context0), Context0),
|
||||
?DEBUG("Template ~p context: ~p\n", [template_id(), dict:to_list(Context)]),
|
||||
|
||||
%% Now, use our context to process the template definition -- this permits us to
|
||||
%% use variables within the definition for filenames.
|
||||
FinalTemplate = consult(render(load_file(Type, Template), Context)),
|
||||
?DEBUG("Final template def ~p: ~p\n", [template_id(), FinalTemplate]),
|
||||
|
||||
%% Execute the instructions in the finalized template
|
||||
execute_template(FinalTemplate, Type, Template, Context).
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -43,9 +98,132 @@ create(_Config, _) ->
|
|||
%% Internal functions
|
||||
%% ===================================================================
|
||||
|
||||
% execute_template([]) ->
|
||||
% ok;
|
||||
% execute_template([{file, Input, Output} | Rest]) ->
|
||||
% ok;
|
||||
% execute_template([{dir, Name} | Rest]) ->
|
||||
% ok.
|
||||
%%
|
||||
%% Scan the current escript for available files and cache in pdict.
|
||||
%%
|
||||
cache_escript_files() ->
|
||||
{ok, Files} = escript:foldl(fun(Name, _, GetBin, Acc) -> [{Name, GetBin()} | Acc] end,
|
||||
[], escript:script_name()),
|
||||
erlang:put(escript_files, Files).
|
||||
|
||||
|
||||
template_id() ->
|
||||
case rebar_config:get_global(template, undefined) of
|
||||
undefined ->
|
||||
?ABORT("No template specified.\n", []);
|
||||
TemplateId ->
|
||||
TemplateId
|
||||
end.
|
||||
|
||||
find_escript_templates() ->
|
||||
[{escript, Name} || {Name, _Bin} <- erlang:get(escript_files),
|
||||
re:run(Name, ?TEMPLATE_RE, [{capture, none}]) == match].
|
||||
|
||||
find_disk_templates() ->
|
||||
Files = rebar_utils:find_files(filename:join(os:getenv("HOME"), ".rebar/templates"), ?TEMPLATE_RE),
|
||||
[{file, F} || F <- Files].
|
||||
|
||||
select_template([], Template) ->
|
||||
?ABORT("Template ~s not found.\n", [Template]);
|
||||
select_template([{Type, Avail} | Rest], Template) ->
|
||||
case filename:basename(Avail, ".template") == Template of
|
||||
true ->
|
||||
{Type, Avail};
|
||||
false ->
|
||||
select_template(Rest, Template)
|
||||
end.
|
||||
|
||||
%%
|
||||
%% Read the contents of a file from the appropriate source
|
||||
%%
|
||||
load_file(escript, Name) ->
|
||||
{Name, Bin} = lists:keyfind(Name, 1, erlang:get(escript_files)),
|
||||
Bin;
|
||||
load_file(file, Name) ->
|
||||
{ok, Bin} = file:read_file(Name),
|
||||
Bin.
|
||||
|
||||
%%
|
||||
%% Parse/validate variables out from the template definition
|
||||
%%
|
||||
parse_vars([], Dict) ->
|
||||
Dict;
|
||||
parse_vars([{Key, Value} | Rest], Dict) when is_atom(Key) ->
|
||||
parse_vars(Rest, dict:store(Key, Value, Dict));
|
||||
parse_vars([Other | _Rest], _Dict) ->
|
||||
{error, Other};
|
||||
parse_vars(Other, _Dict) ->
|
||||
{error, Other}.
|
||||
|
||||
%%
|
||||
%% Given a list of keys in Dict, see if there is a corresponding value defined
|
||||
%% in the global config; if there is, update the key in Dict with it
|
||||
%%
|
||||
update_vars([], Dict) ->
|
||||
Dict;
|
||||
update_vars([Key | Rest], Dict) ->
|
||||
Value = rebar_config:get_global(Key, dict:fetch(Key, Dict)),
|
||||
update_vars(Rest, dict:store(Key, Value, Dict)).
|
||||
|
||||
|
||||
%%
|
||||
%% Given a string or binary, parse it into a list of terms, ala file:consult/0
|
||||
%%
|
||||
consult(Str) when is_list(Str) ->
|
||||
consult([], Str, []);
|
||||
consult(Bin) when is_binary(Bin)->
|
||||
consult([], binary_to_list(Bin), []).
|
||||
|
||||
consult(Cont, Str, Acc) ->
|
||||
case erl_scan:tokens(Cont, Str, 0) of
|
||||
{done, Result, Remaining} ->
|
||||
case Result of
|
||||
{ok, Tokens, _} ->
|
||||
{ok, Term} = erl_parse:parse_term(Tokens),
|
||||
consult([], Remaining, [Term | Acc]);
|
||||
{eof, _Other} ->
|
||||
lists:reverse(Acc);
|
||||
{error, Info, _} ->
|
||||
{error, Info}
|
||||
end;
|
||||
{more, Cont1} ->
|
||||
consult(Cont1, eof, Acc)
|
||||
end.
|
||||
|
||||
|
||||
%%
|
||||
%% Render a binary to a string, using mustache and the specified context
|
||||
%%
|
||||
render(Bin, Context) ->
|
||||
%% Be sure to escape any double-quotes before rendering...
|
||||
Str = re:replace(Bin, "\"", "\\\\\"", [global, {return,list}]),
|
||||
mustache:render(Str, Context).
|
||||
|
||||
%%
|
||||
%% Execute each instruction in a template definition file.
|
||||
%%
|
||||
execute_template([], _TemplateType, _TemplateName, _Context) ->
|
||||
ok;
|
||||
execute_template([{file, Input, Output} | Rest], TemplateType, TemplateName, Context) ->
|
||||
InputName = filename:join(filename:dirname(TemplateName), Input),
|
||||
filelib:ensure_dir(Output),
|
||||
case file:write_file(Output, render(load_file(TemplateType, InputName), Context)) of
|
||||
ok ->
|
||||
execute_template(Rest, TemplateType, TemplateName, Context);
|
||||
{error, Reason} ->
|
||||
?ABORT("Failed to write output file ~p: ~p\n", [Output, Reason])
|
||||
end;
|
||||
execute_template([{dir, Name} | Rest], TemplateType, TemplateName, Context) ->
|
||||
case filelib:ensure_dir(filename:join(Name, "dummy")) of
|
||||
ok ->
|
||||
execute_template(Rest, TemplateType, TemplateName, Context);
|
||||
{error, Reason} ->
|
||||
?ABORT("Failed while processing template instruction {dir, ~s}: ~p\n",
|
||||
[Name, Reason])
|
||||
end;
|
||||
execute_template([{variables, _} | Rest], TemplateType, TemplateName, Context) ->
|
||||
execute_template(Rest, TemplateType, TemplateName, Context);
|
||||
execute_template([Other | Rest], TemplateType, TemplateName, Context) ->
|
||||
?WARN("Skipping unknown template instruction: ~p\n", [Other]),
|
||||
execute_template(Rest, TemplateType, TemplateName, Context).
|
||||
|
||||
|
|
Loading…
Reference in a new issue