Skip to content

Commit

Permalink
Merge pull request mojombo#14 from fabiankrol/basic_eunit
Browse files Browse the repository at this point in the history
few basic eunit tests + {{^section}} + {{&tag}}
  • Loading branch information
pusewicz committed Apr 6, 2013
2 parents 869040d + 134db89 commit d580534
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 16 deletions.
49 changes: 33 additions & 16 deletions src/mustache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ render(Mod, CompiledTemplate, Ctx) ->
lists:flatten(CompiledTemplate(Ctx2)).

pre_compile(T, State) ->
SectionRE = "\{\{\#([^\}]*)}}\s*(.+?){{\/\\1\}\}\s*",
SectionRE = "\{\{(\#|\\^)([^\}]*)}}\s*(.+?){{\/\\2\}\}\s*",
{ok, CompiledSectionRE} = re:compile(SectionRE, [dotall]),
TagRE = "\{\{(#|=|!|<|>|\{)?(.+?)\\1?\}\}+",
TagRE = "\{\{(#|=|!|<|>|\{|&)?(.+?)\\1?\}\}+",
{ok, CompiledTagRE} = re:compile(TagRE, [dotall]),
State2 = State#mstate{section_re = CompiledSectionRE, tag_re = CompiledTagRE},
"fun(Ctx) -> " ++
Expand All @@ -92,32 +92,41 @@ pre_compile(T, State) ->
compiler(T, State) ->
Res = re:run(T, State#mstate.section_re),
case Res of
{match, [{M0, M1}, {N0, N1}, {C0, C1}]} ->
{match, [{M0, M1}, {K0, K1}, {N0, N1}, {C0, C1}]} ->
Front = string:substr(T, 1, M0),
Back = string:substr(T, M0 + M1 + 1),
Kind = string:substr(T, K0 + 1, K1),
Name = string:substr(T, N0 + 1, N1),
Content = string:substr(T, C0 + 1, C1),
"[" ++ compile_tags(Front, State) ++
" | [" ++ compile_section(Name, Content, State) ++
" | [" ++ compile_section(Kind, Name, Content, State) ++
" | [" ++ compiler(Back, State) ++ "]]]";
nomatch ->
compile_tags(T, State)
end.

compile_section(Name, Content, State) ->
compile_section("#", Name, Content, State) ->
Mod = State#mstate.mod,
Result = compiler(Content, State),
"fun() -> " ++
"case mustache:get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++
"\"true\" -> " ++
Result ++ "; " ++
"\"false\" -> " ++
"[]; " ++
"\"true\" -> " ++ Result ++ "; " ++
"\"false\" -> []; " ++
"List when is_list(List) -> " ++
"[fun(Ctx) -> " ++ Result ++ " end(dict:merge(CFun, SubCtx, Ctx)) || SubCtx <- List]; " ++
"Else -> " ++
"throw({template, io_lib:format(\"Bad context for ~p: ~p\", [" ++ Name ++ ", Else])}) " ++
"end " ++
"end()";
compile_section("^", Name, Content, State) ->
Mod = State#mstate.mod,
Result = compiler(Content, State),
"fun() -> " ++
"case mustache:get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++
"\"false\" -> " ++ Result ++ "; " ++
"[] -> " ++ Result ++ "; " ++
"_ -> [] "
"end " ++
"end()".

compile_tags(T, State) ->
Expand All @@ -142,14 +151,22 @@ tag_kind(T, {K0, K1}) ->
string:substr(T, K0 + 1, K1).

compile_tag(none, Content, State) ->
Mod = State#mstate.mod,
"mustache:escape(mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))";
compile_escaped_tag(Content, State);
compile_tag("&", Content, State) ->
compile_unescaped_tag(Content, State);
compile_tag("{", Content, State) ->
Mod = State#mstate.mod,
"mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")";
compile_unescaped_tag(Content, State);
compile_tag("!", _Content, _State) ->
"[]".

compile_escaped_tag(Content, State) ->
Mod = State#mstate.mod,
"mustache:escape(mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))".

compile_unescaped_tag(Content, State) ->
Mod = State#mstate.mod,
"mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")".

template_dir(Mod) ->
DefaultDirPath = filename:dirname(code:which(Mod)),
case application:get_env(mustache, templates_dir) of
Expand Down Expand Up @@ -212,11 +229,11 @@ escape(HTML) ->

escape([], Acc) ->
lists:reverse(Acc);
escape(["<" | Rest], Acc) ->
escape([$< | Rest], Acc) ->
escape(Rest, lists:reverse("&lt;", Acc));
escape([">" | Rest], Acc) ->
escape([$> | Rest], Acc) ->
escape(Rest, lists:reverse("&gt;", Acc));
escape(["&" | Rest], Acc) ->
escape([$& | Rest], Acc) ->
escape(Rest, lists:reverse("&amp;", Acc));
escape([X | Rest], Acc) ->
escape(Rest, [X | Acc]).
Expand Down
75 changes: 75 additions & 0 deletions test/mustache_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,78 @@ specials_test() ->
Ctx = dict:from_list([{name, "Chris"}, {value, 10000}]),
Result = mustache:render("\'Hello\n\"{{name}}\"~nYou \"have\" ju\0st\\ won\b\r\"${{value}}!\"\t", Ctx),
?assertEqual("\'Hello\n\"Chris\"~nYou \"have\" ju\0st\\ won\b\r\"$10000!\"\t", Result).

%% ===================================================================
%% basic tag types
%% ===================================================================

tag_type_variable_empty_test() ->
test_helper("{{name}}", "", []).

tag_type_variable_string_test() ->
test_helper("{{name}}", "NAME", [{name, "NAME"}]).

tag_type_variable_integer_test() ->
test_helper("{{name}}", "1", [{name, 1}]).

tag_type_variable_atom_test() ->
test_helper("{{name}}", "atom", [{name, atom}]).

tag_type_varibale_escaped_test() ->
test_helper("{{name}}", "&gt;&amp;do&lt;it&gt;", [{name, ">&do<it>"}]).

tag_type_variabel_unescaped_test() ->
test_helper("{{{name}}}", ">dont&do<it>", [{name, ">dont&do<it>"}]).

tag_type_variable_unescaped_with_ampersand_test() ->
test_helper("{{&name}}", ">dont&do<it>", [{name, ">dont&do<it>"}]).


tag_type_section_empty_test() ->
test_helper("{{#name}}section{{/name}}", "", []).

tag_type_section_false_test() ->
test_helper("{{#name}}section{{/name}}", "", [{name, false}]).

tag_type_section_true_test() ->
test_helper("{{#name}}section{{/name}}", "section", [{name, true}]).

tag_type_section_empty_list_test() ->
test_helper("{{#name}}section{{/name}}", "", [{name, []}]).

tag_type_section_nonempty_list_test() ->
CtxList = [{name, [ dict:new() || _ <- lists:seq(1,3) ]}],
test_helper("{{#name}}section{{/name}}", "sectionsectionsection", CtxList).


tag_type_inverted_section_empty_test() ->
test_helper("{{^name}}section{{/name}}", "section", []).

tag_type_inverted_section_false_test() ->
test_helper("{{^name}}section{{/name}}", "section", [{name, false}]).

tag_type_inverted_section_true_test() ->
test_helper("{{^name}}section{{/name}}", "", [{name, true}]).

tag_type_inverted_section_empty_list_test() ->
test_helper("{{^name}}section{{/name}}", "section", [{name, []}]).

tag_type_inverted_section_nonempty_list_test() ->
CtxList = [{name, [ dict:new() || _ <- lists:seq(1,3) ]}],
test_helper("{{^name}}section{{/name}}", "", CtxList).


tag_type_comment_test() ->
test_helper("{{!comment}}", "", []).

tag_type_comment_empty_test() ->
test_helper("{{! }}", "", []).

tag_type_comment_multiline_test() ->
test_helper("{{!\ncomment\ncomment\ncomment\n\n}}", "", []).


test_helper(Template, Expected, CtxList) ->
Ctx = dict:from_list(CtxList),
?assertEqual(Expected, mustache:render(Template, Ctx)).

0 comments on commit d580534

Please sign in to comment.