summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rebar_templater.erl194
1 files changed, 186 insertions, 8 deletions
diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl
index 3c7c05f..329df7c 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -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).
+