Packages

An extremly fast and simple templating library in Erlang

Current section

Files

Jump to
templaterl src templaterl.erl
Raw

src/templaterl.erl

%%--------------------------------------------------------------------
%% @doc Templaterl is a very basic inline templating engine that
%% supports token replacement as well as custom expressions
%% on the token values.
%%
%% For example you can do the following:
%% Uppercase = "uppercase(_Token, Value) -> << <<(string:to_upper(X))>> || <<X>> <= Value >>.",
%% templaterl:register_helpers([Uppercase]),
%% templaterl:compile(<<"This bitstring has a {{{tag}}} that can be uppercase {{{uppercase tag}}} too.">>,
%% [{<<"tag">>, <<"token">>}]).
%% => <<"This bitstring has a token that can be uppercase TOKEN too.">>
%%
%%
%% @end
%%--------------------------------------------------------------------
-module(templaterl).
-compile(inline).
-export([
compile/2,
register_helpers/1
]).
-type token_list() :: [token()].
-type token() :: {bitstring(), bitstring()}.
-define(TEMPLATERL_HELPER_MODULE, tempalterl_helpers).
%%====================================================================
%% API functions
%%====================================================================
-spec compile(bitstring(), token_list()) -> bitstring().
compile(Bin, Tokens) when is_bitstring(Bin) andalso is_list(Tokens) ->
parse_and_replace(Bin, Tokens, <<>>).
%% @doc If custom helper functions are needed they have to be registered
%% before calling compile. This function creates a new inline
%% module with the given expression definitions. This makes the
%% calls faster.
%%
%% Helper function definition is in the form of a string. For example,
%% if you want a helper that concatenates the token name and value
%% you would write something like:
%%
%% DummyHelper = "dummy_helper(Token, Value) -> <<Token/binary, $:, Value/binary>>."
%%
%% Usage:
%% templaterl:register_helpers([DummyHelper]),
%% templaterl:compile(<<"test {{dummy_helper my_token}}">>, [{<<"my_token">>, value}]).
%% => <<"test my_token:value">>
%% @end
-spec register_helpers(list(string())) -> ok.
register_helpers(HelperList) ->
generate_helper_module(HelperList).
%%====================================================================
%% Internal functions
%%====================================================================
parse_and_replace(<<>>, _, Acc) ->
erlang:iolist_to_binary(Acc);
parse_and_replace(Bin, TokenList, Acc) ->
case binary:split(Bin, <<"{{{">>) of
[Before, After] ->
parse_and_replace2(After, TokenList, [Acc, Before]);
[Rest] ->
parse_and_replace(<<>>, TokenList, [Acc, Rest])
end.
parse_and_replace2(<<>>, _, _) ->
bad_tag;
parse_and_replace2(Bin, TokenList, Acc) ->
case binary:split(Bin, <<"}}}">>) of
[Token, Rest] ->
Value = convert_to_binary(apply_token_funs(Token, TokenList)),
parse_and_replace(Rest, TokenList, [Acc, Value]);
[_Rest] ->
bad_tag
end.
apply_token_funs(TokenBin, TokenList) ->
case binary:split(TokenBin, <<" ">>, [global, trim]) of
[Token] ->
token_value(Token, TokenList);
FuncList ->
[Token | Funs] = lists:map(fun(E) -> binary:replace(E, [<<"(">>, <<")">>], <<"">>, [global]) end, lists:reverse(FuncList)),
Value = token_value(Token, TokenList),
lists:foldl(
fun(Current, Prev) ->
ExpressionFun = binary_to_existing_atom(Current, utf8),
?TEMPLATERL_HELPER_MODULE:ExpressionFun(Token, Prev)
end,
convert_to_binary(Value),
Funs)
end.
token_value(Token, TokenList) ->
case lists:keyfind(Token, 1, TokenList) of
{_, Value} ->
Value;
false ->
throw({token_not_found, Token})
end.
convert_to_binary(Term) when is_binary(Term) -> Term;
convert_to_binary(Term) when is_integer(Term) -> integer_to_binary(Term);
convert_to_binary(Term) when is_float(Term) -> list_to_binary(io_lib:format("~p", [Term]));
convert_to_binary(Term) when is_list(Term) -> list_to_binary(Term);
convert_to_binary(true) -> <<"true">>;
convert_to_binary(false) -> <<"false">>;
convert_to_binary(Term) when is_atom(Term) -> atom_to_binary(Term, utf8).
generate_helper_module(HelperList) ->
Forms = helper_forms(HelperList),
{ok, Module, Bin} = compile:forms(Forms, [debug_info, inline]),
code:purge(Module),
Filename = atom_to_list(Module) ++ ".erl",
{module, Module} = code:load_binary(Module, Filename, Bin),
ok.
helper_forms(HelperList) ->
ModuleDefinition = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(?TEMPLATERL_HELPER_MODULE)]),
Functions = [helper_function_syntax(HelperBody) || HelperBody <- HelperList],
ExportsList = [erl_syntax:arity_qualifier(erl_syntax:atom(HelperName), erl_syntax:integer(2)) || {HelperName, _} <- Functions],
Exports = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportsList)]),
FunctionForms = [HelperFuncForm || {_, HelperFuncForm} <- Functions],
Module = [ModuleDefinition, Exports] ++ FunctionForms,
[erl_syntax:revert(X) || X <- Module].
helper_function_syntax(HelperBody) ->
{ok, Ts, _} = erl_scan:string(HelperBody),
{ok, Form} = erl_parse:parse_form(Ts),
Fun = element(3, Form),
{Fun, Form}.