Skip to content

Commit

Permalink
Support {{ block.super }} in block tags (fixes #18)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaos committed Feb 27, 2014
1 parent c1eebb2 commit d572bfc
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 41 deletions.
69 changes: 49 additions & 20 deletions src/erlydtl_beam_compiler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@

-import(erlydtl_compiler_utils,
[unescape_string_literal/1, full_path/2, push_scope/2,
restore_scope/2, begin_scope/1, begin_scope/3, end_scope/4,
print/3, get_current_file/1, add_errors/2, add_warnings/2,
merge_info/2, call_extension/3, init_treewalker/1,
resolve_variable/2, resolve_variable/3,
restore_scope/2, begin_scope/1, begin_scope/2, end_scope/4,
empty_scope/0, print/3, get_current_file/1, add_errors/2,
add_warnings/2, merge_info/2, call_extension/3,
init_treewalker/1, resolve_variable/2, resolve_variable/3,
reset_parse_trail/2]).

-include_lib("merl/include/merl.hrl").
Expand Down Expand Up @@ -502,9 +502,10 @@ body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], #treewal
BlockDict = lists:foldl(
fun ({block, {identifier, _, Name}, Contents}, Dict) ->
dict:store(Name, Contents, Dict);
(_, Dict) ->
Dict
end, dict:new(), ThisParseTree),
(_, Dict) -> Dict
end,
dict:new(),
ThisParseTree),
{Info, TreeWalker1} = with_dependency(
{File, CheckSum},
body_ast(
Expand All @@ -525,17 +526,28 @@ body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], #treewal


body_ast(DjangoParseTree, TreeWalker) ->
{ScopeId, TreeWalkerScope} = begin_scope(TreeWalker),
body_ast(DjangoParseTree, empty_scope(), TreeWalker).

body_ast(DjangoParseTree, BodyScope, TreeWalker) ->
{ScopeId, TreeWalkerScope} = begin_scope(BodyScope, TreeWalker),
{AstInfoList, TreeWalker1} =
lists:mapfoldl(
fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, #treewalker{ context=Context }=TW) ->
body_ast(Contents, TW#treewalker{ context=Context#dtl_context{auto_escape = OnOrOff} });
({'block', {identifier, _, Name}, Contents}, #treewalker{ context=Context }=TW) ->
Block = case dict:find(Name, Context#dtl_context.block_dict) of
{ok, ChildBlock} -> ChildBlock;
_ -> Contents
end,
body_ast(Block, TW);
({'block', {identifier, Pos, Name}, Contents}, #treewalker{ context=Context }=TW) ->
{Block, BlockScope} =
case dict:find(Name, Context#dtl_context.block_dict) of
{ok, ChildBlock} ->
{{ContentsAst, _ContentsInfo}, _ContentsTW} = body_ast(Contents, TW),
{ChildBlock,
create_scope(
[{block, ?Q("[{super, _@ContentsAst}]")}],
Pos, TW)
};
_ ->
{Contents, empty_scope()}
end,
body_ast(Block, BlockScope, TW);
({'blocktrans', Args, Contents}, TW) ->
blocktrans_ast(Args, Contents, TW);
({'call', {identifier, _, Name}}, TW) ->
Expand Down Expand Up @@ -1144,8 +1156,8 @@ scope_as(VarName, Contents, TreeWalker) ->
{{ContentsAst, ContentsInfo}, TreeWalker1} = body_ast(Contents, TreeWalker),
VarAst = merl:var(lists:concat(["Var_", VarName])),
{Id, TreeWalker2} = begin_scope(
[{VarName, VarAst}],
[?Q("_@VarAst = _@ContentsAst")],
{[{VarName, VarAst}],
[?Q("_@VarAst = _@ContentsAst")]},
TreeWalker1),
{{Id, ContentsInfo}, TreeWalker2}.

Expand All @@ -1154,10 +1166,10 @@ regroup_ast(ListVariable, GrouperVariable, LocalVarName, TreeWalker) ->
LocalVarAst = merl:var(lists:concat(["Var_", LocalVarName])),

{Id, TreeWalker2} = begin_scope(
[{LocalVarName, LocalVarAst}],
[?Q("_@LocalVarAst = erlydtl_runtime:regroup(_@ListAst, _@regroup)",
[{regroup, regroup_filter(GrouperVariable, [])}])
],
{[{LocalVarName, LocalVarAst}],
[?Q("_@LocalVarAst = erlydtl_runtime:regroup(_@ListAst, _@regroup)",
[{regroup, regroup_filter(GrouperVariable, [])}])
]},
TreeWalker1),

{{Id, ListInfo}, TreeWalker2}.
Expand Down Expand Up @@ -1371,3 +1383,20 @@ call_ast(Module, Variable, AstInfo, TreeWalker) ->
" {error, Reason} -> io_lib:format(\"error: ~p\", [Reason])",
"end"]),
with_dependencies(Module:dependencies(), {{Ast, AstInfo}, TreeWalker}).

create_scope(Vars, VarScope) ->
{Scope, Values} =
lists:foldl(
fun ({Name, Value}, {VarAcc, ValueAcc}) ->
NameAst = merl:var(lists:concat(["_Var_", Name, VarScope])),
{[{Name, NameAst}|VarAcc],
[?Q("_@NameAst = _@Value")|ValueAcc]
}
end,
empty_scope(),
Vars),
{Scope, [Values]}.

create_scope(Vars, {Row, Col}, #treewalker{ context=Context }) ->
Level = length(Context#dtl_context.local_scopes),
create_scope(Vars, lists:concat(["/", Level, "_", Row, ":", Col])).
20 changes: 15 additions & 5 deletions src/erlydtl_compiler_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@
push_scope/2,
restore_scope/2,
begin_scope/1,
begin_scope/3,
end_scope/4
begin_scope/2,
end_scope/4,
empty_scope/0
]).

-include("erlydtl_ext.hrl").
Expand Down Expand Up @@ -208,15 +209,17 @@ restore_scope(#treewalker{ context=Target }, Context) ->
restore_scope(#dtl_context{ local_scopes=Scopes }, Context) ->
Context#dtl_context{ local_scopes=Scopes }.

begin_scope(TreeWalker) -> begin_scope([], [], TreeWalker).
begin_scope(TreeWalker) -> begin_scope(empty_scope(), TreeWalker).

begin_scope(Scope, Values, TreeWalker) ->
begin_scope({Scope, Values}, TreeWalker) ->
Id = make_ref(),
{Id, push_scope({Id, Scope, Values}, TreeWalker)}.

end_scope(Fun, Id, AstList, TreeWalker) ->
close_scope(Fun, Id, AstList, TreeWalker).

empty_scope() -> {[], []}.

reset_parse_trail(ParseTrail, #treewalker{ context=Context }=TreeWalker) ->
TreeWalker#treewalker{ context=reset_parse_trail(ParseTrail, Context) };
reset_parse_trail(ParseTrail, Context) ->
Expand Down Expand Up @@ -323,9 +326,14 @@ close_scope(Fun, Id, AstList, TreeWalker) ->

merge_scopes(Id, #treewalker{ context=Context }=TreeWalker) ->
{Values, Scopes} = merge_scopes(Id, Context#dtl_context.local_scopes, []),
{Values, TreeWalker#treewalker{ context=Context#dtl_context{ local_scopes = Scopes } }}.
{lists:reverse(Values),
TreeWalker#treewalker{
context=Context#dtl_context{
local_scopes = Scopes
} }}.

merge_scopes(Id, [{Id, _Scope, []}|Scopes], Acc) -> {Acc, Scopes};
merge_scopes(Id, [{Id, _Scope, Values}|Scopes], Acc) -> {[{Id, Values}|Acc], Scopes};
merge_scopes(Id, [{_ScopeId, _Scope, []}|Scopes], Acc) ->
merge_scopes(Id, Scopes, Acc);
merge_scopes(Id, [{ScopeId, _Scope, Values}|Scopes], Acc) ->
Expand All @@ -339,6 +347,8 @@ split_ast(Id, AstList) ->

split_ast(_Split, [], {Pre, Acc}) ->
{Pre, lists:reverse(Acc), []};
split_ast(_Split, [], Acc) ->
{[], lists:reverse(Acc), []};
split_ast(Split, [Split|Rest], {Pre, Acc}) ->
{Pre, lists:reverse(Acc), Rest};
split_ast(Split, [Split|Rest], Acc) ->
Expand Down
11 changes: 11 additions & 0 deletions tests/expect/block_super
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
base-barstring

base template

extending title: "base title"

more of base template

replacing the base content - variable: test-barstring after variable. Was: base content

end of base template
3 changes: 3 additions & 0 deletions tests/input/block_super
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% extends "base" %}
{% block title %}extending title: "{{ block.super }}"{% endblock %}
{% block content %}replacing the base content - variable: {{ test_var }} after variable. Was: {{ block.super }}{% endblock %}
28 changes: 12 additions & 16 deletions tests/src/erlydtl_functional_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ test_list() ->
"custom_tag2", "custom_tag3", "custom_tag4", "custom_call",
"include_template", "include_path", "ssi", "extends_path",
"extends_path2", "trans", "extends2", "extends3",
"recursive_block", "extend_recursive_block", "missing"
"recursive_block", "extend_recursive_block", "missing",
"block_super"
].

setup_compile("for_list_preset") ->
Expand Down Expand Up @@ -102,6 +103,11 @@ setup("autoescape") ->
setup("extends") ->
RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
{ok, RenderVars};
setup("include_template") -> setup("extends");
setup("include_path") -> setup("extends");
setup("extends_path") -> setup("extends");
setup("extends_path2") -> setup("extends");
setup("block_super") -> setup("extends");
setup("filters") ->
RenderVars = [
{date_var1, {1975,7,24}},
Expand Down Expand Up @@ -157,18 +163,6 @@ setup("cycle") ->
RenderVars = [{test, [integer_to_list(X) || X <- lists:seq(1, 20)]},
{a, "Apple"}, {b, "Banana"}, {c, "Cherry"}],
{ok, RenderVars};
setup("include_template") ->
RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
{ok, RenderVars};
setup("include_path") ->
RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
{ok, RenderVars};
setup("extends_path") ->
RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
{ok, RenderVars};
setup("extends_path2") ->
RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
{ok, RenderVars};
setup("trans") ->
RenderVars = [{locale, "reverse"}],
{ok, RenderVars};
Expand All @@ -185,8 +179,6 @@ setup("custom_tag4") ->
setup("ssi") ->
RenderVars = [{path, filename:absname(filename:join(["tests", "input", "ssi_include.html"]))}],
{ok, RenderVars};


%%--------------------------------------------------------------------
%% Custom tags
%%--------------------------------------------------------------------
Expand Down Expand Up @@ -311,7 +303,11 @@ test_render(Name, Module) ->
fun(F) -> file:write_file(F, Data) end),
{error, io_lib:format(
"Expected output does not match rendered output~n"
"==Expected==~n~p~n--Actual--~n~p~n==End==~n",
" ==Expected==~n"
"~s~n"
" --Actual--~n"
"~s~n"
" ==End==~n",
[RenderResult, Data])}
end;
true ->
Expand Down

0 comments on commit d572bfc

Please sign in to comment.