Skip to content

Instantly share code, notes, and snippets.

@codeholic
Created October 23, 2013 15:24
Show Gist options
  • Save codeholic/7120805 to your computer and use it in GitHub Desktop.
Save codeholic/7120805 to your computer and use it in GitHub Desktop.
% et - Erlang template engine
% Copyright (c) 2008 Ivan Fomichev
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
% in the Software without restriction, including without limitation the rights
% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
% copies of the Software, and to permit persons to whom the Software is
% furnished to do so, subject to the following conditions:
%
% The above copyright notice and this permission notice shall be included in
% all copies or substantial portions of the Software.
%
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
% THE SOFTWARE.
-module(et).
-export([from_file/3, do/4, arg/2, set_arg/3, none/2, dict/2, switch/5, foreach/4]).
from_file(Logic, Filename, Args) ->
case file:read_file(Filename) of
{ok, Binary} -> Logic(binary_to_list(Binary), Args);
Error -> throw(Error)
end.
do(Logic, Template, Section, Args) ->
validate_name(Section),
StartRx = "{{#" ++ Section ++ "}}[ \t]*(\r?\n|\n)?",
EndRx = "{{/" ++ Section ++ "}}[ \t]*(\r?\n|\n)?",
case regexp:match(Template, StartRx) of
{match, StartPos, StartLength} ->
Rest = string:substr(Template, StartPos + StartLength),
case regexp:match(Rest, EndRx) of
{match, EndPos, EndLength} ->
Template2 = lists:sublist(Rest, EndPos - 1),
Template3 = lists:sublist(Template, StartPos - 1) ++
Logic(Template2, Args) ++
string:substr(Rest, EndPos + EndLength),
dict(Template3, Args);
nomatch -> throw({template, "No such section " ++ Section})
end;
nomatch -> throw({template, "No such section " ++ Section})
end.
validate_name(Name) ->
Allowed = lists:seq($0, $9) ++ lists:seq($A, $Z) ++ "_" ++ lists:seq($a, $z),
AllowedLen = string:span(Name, Allowed),
if
AllowedLen == length(Name) -> ok;
true -> throw({template, "Invalid identifier " ++ Name})
end.
arg(Key, Args) ->
case lists:keysearch(Key, 1, Args) of
{value, {_, Value}} -> Value;
_ -> undefined
end.
set_arg(Key, Value, Args) ->
lists:keystore(Key, 1, Args, {Key, Value}).
arg_sort(A, B) ->
KeyA = element(1, A),
KeyB = element(1, B),
KeyA =< KeyB.
none(_, _) -> "".
dict("", _) -> "";
dict(Template, []) -> Template;
dict(Template, [{Name, Value} | T]) ->
validate_name(Name),
Rx = "{{" ++ Name ++ "}}",
Replacement = if
is_list(Value) -> Value;
is_float(Value) -> hd(io_lib:format("~g", [Value])); % TODO: choose precision
is_integer(Value) -> integer_to_list(Value);
true -> ""
end,
case regexp:gsub(Template, Rx, Replacement) of
{ok, Result, _} -> dict(Result, T)
end.
switch(_, Template, Section, Args, false) -> do(fun none/2, Template, Section, Args);
switch(Logic, Template, Section, Args, true) -> do(Logic, Template, Section, Args).
foreach_fun(_, "", _, _, _) -> "";
foreach_fun(_, _, [], _, Acc) -> lists:flatten(lists:reverse(Acc));
foreach_fun(Logic, Template, [Args | T], I, Acc) ->
Args2 = lists:umerge(fun arg_sort/2,
lists:usort(fun arg_sort/2, [
{"__i", I},
{"__first", I =:= 1},
{"__last", T =:= []},
{"__odd", I rem 2 =:= 1},
{"__even", I rem 2 =:= 0}
]),
lists:usort(fun arg_sort/2, Args)),
Result = Logic(Template, Args2),
foreach_fun(Logic, Template, T, I + 1, [Result | Acc]).
foreach(Logic, Template, Section, Args) ->
do(fun(Template2, Args2) ->
Data = arg(Section, Args2),
foreach_fun(Logic, Template2, Data, 1, [])
end,
Template, Section, Args).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment