Просмотр исходного кода

Support `{{ block.super }}` in block tags (fixes #18)

Andreas Stenius 11 лет назад
Родитель
Сommit
d572bfc9bb

+ 49 - 20
src/erlydtl_beam_compiler.erl

@@ -59,10 +59,10 @@
 
 
 -import(erlydtl_compiler_utils,
 -import(erlydtl_compiler_utils,
         [unescape_string_literal/1, full_path/2, push_scope/2,
         [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]).
          reset_parse_trail/2]).
 
 
 -include_lib("merl/include/merl.hrl").
 -include_lib("merl/include/merl.hrl").
@@ -502,9 +502,10 @@ body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], #treewal
                     BlockDict = lists:foldl(
                     BlockDict = lists:foldl(
                                   fun ({block, {identifier, _, Name}, Contents}, Dict) ->
                                   fun ({block, {identifier, _, Name}, Contents}, Dict) ->
                                           dict:store(Name, Contents, Dict);
                                           dict:store(Name, Contents, Dict);
-                                      (_, Dict) ->
-                                          Dict
-                                  end, dict:new(), ThisParseTree),
+                                      (_, Dict) -> Dict
+                                  end,
+                                  dict:new(),
+                                  ThisParseTree),
                     {Info, TreeWalker1} = with_dependency(
                     {Info, TreeWalker1} = with_dependency(
                                             {File, CheckSum},
                                             {File, CheckSum},
                                             body_ast(
                                             body_ast(
@@ -525,17 +526,28 @@ body_ast([{'extends', {string_literal, _Pos, String}} | ThisParseTree], #treewal
 
 
 
 
 body_ast(DjangoParseTree, TreeWalker) ->
 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} =
     {AstInfoList, TreeWalker1} =
         lists:mapfoldl(
         lists:mapfoldl(
           fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, #treewalker{ context=Context }=TW) ->
           fun ({'autoescape', {identifier, _, OnOrOff}, Contents}, #treewalker{ context=Context }=TW) ->
                   body_ast(Contents, TW#treewalker{ context=Context#dtl_context{auto_escape = OnOrOff} });
                   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', Args, Contents}, TW) ->
                   blocktrans_ast(Args, Contents, TW);
                   blocktrans_ast(Args, Contents, TW);
               ({'call', {identifier, _, Name}}, TW) ->
               ({'call', {identifier, _, Name}}, TW) ->
@@ -1144,8 +1156,8 @@ scope_as(VarName, Contents, TreeWalker) ->
     {{ContentsAst, ContentsInfo}, TreeWalker1} = body_ast(Contents, TreeWalker),
     {{ContentsAst, ContentsInfo}, TreeWalker1} = body_ast(Contents, TreeWalker),
     VarAst = merl:var(lists:concat(["Var_", VarName])),
     VarAst = merl:var(lists:concat(["Var_", VarName])),
     {Id, TreeWalker2} = begin_scope(
     {Id, TreeWalker2} = begin_scope(
-                          [{VarName, VarAst}],
-                          [?Q("_@VarAst = _@ContentsAst")],
+                          {[{VarName, VarAst}],
+                           [?Q("_@VarAst = _@ContentsAst")]},
                           TreeWalker1),
                           TreeWalker1),
     {{Id, ContentsInfo}, TreeWalker2}.
     {{Id, ContentsInfo}, TreeWalker2}.
 
 
@@ -1154,10 +1166,10 @@ regroup_ast(ListVariable, GrouperVariable, LocalVarName, TreeWalker) ->
     LocalVarAst = merl:var(lists:concat(["Var_", LocalVarName])),
     LocalVarAst = merl:var(lists:concat(["Var_", LocalVarName])),
 
 
     {Id, TreeWalker2} = begin_scope(
     {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),
                           TreeWalker1),
 
 
     {{Id, ListInfo}, TreeWalker2}.
     {{Id, ListInfo}, TreeWalker2}.
@@ -1371,3 +1383,20 @@ call_ast(Module, Variable, AstInfo, TreeWalker) ->
               "  {error, Reason} -> io_lib:format(\"error: ~p\", [Reason])",
               "  {error, Reason} -> io_lib:format(\"error: ~p\", [Reason])",
               "end"]),
               "end"]),
     with_dependencies(Module:dependencies(), {{Ast, AstInfo}, TreeWalker}).
     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])).

+ 15 - 5
src/erlydtl_compiler_utils.erl

@@ -62,8 +62,9 @@
          push_scope/2,
          push_scope/2,
          restore_scope/2,
          restore_scope/2,
          begin_scope/1,
          begin_scope/1,
-         begin_scope/3,
-         end_scope/4
+         begin_scope/2,
+         end_scope/4,
+         empty_scope/0
         ]).
         ]).
 
 
 -include("erlydtl_ext.hrl").
 -include("erlydtl_ext.hrl").
@@ -208,15 +209,17 @@ restore_scope(#treewalker{ context=Target }, Context) ->
 restore_scope(#dtl_context{ local_scopes=Scopes }, Context) ->
 restore_scope(#dtl_context{ local_scopes=Scopes }, Context) ->
     Context#dtl_context{ local_scopes=Scopes }.
     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 = make_ref(),
     {Id, push_scope({Id, Scope, Values}, TreeWalker)}.
     {Id, push_scope({Id, Scope, Values}, TreeWalker)}.
 
 
 end_scope(Fun, Id, AstList, TreeWalker) ->
 end_scope(Fun, Id, AstList, TreeWalker) ->
     close_scope(Fun, Id, AstList, TreeWalker).
     close_scope(Fun, Id, AstList, TreeWalker).
 
 
+empty_scope() -> {[], []}.
+
 reset_parse_trail(ParseTrail, #treewalker{ context=Context }=TreeWalker) ->
 reset_parse_trail(ParseTrail, #treewalker{ context=Context }=TreeWalker) ->
     TreeWalker#treewalker{ context=reset_parse_trail(ParseTrail, Context) };
     TreeWalker#treewalker{ context=reset_parse_trail(ParseTrail, Context) };
 reset_parse_trail(ParseTrail, Context) ->
 reset_parse_trail(ParseTrail, Context) ->
@@ -323,9 +326,14 @@ close_scope(Fun, Id, AstList, TreeWalker) ->
 
 
 merge_scopes(Id, #treewalker{ context=Context }=TreeWalker) ->
 merge_scopes(Id, #treewalker{ context=Context }=TreeWalker) ->
     {Values, Scopes} = merge_scopes(Id, Context#dtl_context.local_scopes, []),
     {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, []}|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, [{_ScopeId, _Scope, []}|Scopes], Acc) ->
     merge_scopes(Id, Scopes, Acc);
     merge_scopes(Id, Scopes, Acc);
 merge_scopes(Id, [{ScopeId, _Scope, Values}|Scopes], Acc) ->
 merge_scopes(Id, [{ScopeId, _Scope, Values}|Scopes], Acc) ->
@@ -339,6 +347,8 @@ split_ast(Id, AstList) ->
 
 
 split_ast(_Split, [], {Pre, Acc}) ->
 split_ast(_Split, [], {Pre, Acc}) ->
     {Pre, lists:reverse(Acc), []};
     {Pre, lists:reverse(Acc), []};
+split_ast(_Split, [], Acc) ->
+    {[], lists:reverse(Acc), []};
 split_ast(Split, [Split|Rest], {Pre, Acc}) ->
 split_ast(Split, [Split|Rest], {Pre, Acc}) ->
     {Pre, lists:reverse(Acc), Rest};
     {Pre, lists:reverse(Acc), Rest};
 split_ast(Split, [Split|Rest], Acc) ->
 split_ast(Split, [Split|Rest], Acc) ->

+ 11 - 0
tests/expect/block_super

@@ -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 - 0
tests/input/block_super

@@ -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 %}

+ 12 - 16
tests/src/erlydtl_functional_tests.erl

@@ -48,7 +48,8 @@ test_list() ->
      "custom_tag2", "custom_tag3", "custom_tag4", "custom_call",
      "custom_tag2", "custom_tag3", "custom_tag4", "custom_call",
      "include_template", "include_path", "ssi", "extends_path",
      "include_template", "include_path", "ssi", "extends_path",
      "extends_path2", "trans", "extends2", "extends3",
      "extends_path2", "trans", "extends2", "extends3",
-     "recursive_block", "extend_recursive_block", "missing"
+     "recursive_block", "extend_recursive_block", "missing",
+     "block_super"
     ].
     ].
 
 
 setup_compile("for_list_preset") ->
 setup_compile("for_list_preset") ->
@@ -102,6 +103,11 @@ setup("autoescape") ->
 setup("extends") ->
 setup("extends") ->
     RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
     RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
     {ok, RenderVars};
     {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") ->
 setup("filters") ->
     RenderVars = [
     RenderVars = [
                   {date_var1, {1975,7,24}},
                   {date_var1, {1975,7,24}},
@@ -157,18 +163,6 @@ setup("cycle") ->
     RenderVars = [{test, [integer_to_list(X) || X <- lists:seq(1, 20)]},
     RenderVars = [{test, [integer_to_list(X) || X <- lists:seq(1, 20)]},
                   {a, "Apple"}, {b, "Banana"}, {c, "Cherry"}],
                   {a, "Apple"}, {b, "Banana"}, {c, "Cherry"}],
     {ok, RenderVars};
     {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") ->
 setup("trans") ->
     RenderVars = [{locale, "reverse"}],
     RenderVars = [{locale, "reverse"}],
     {ok, RenderVars};
     {ok, RenderVars};
@@ -185,8 +179,6 @@ setup("custom_tag4") ->
 setup("ssi") ->
 setup("ssi") ->
     RenderVars = [{path, filename:absname(filename:join(["tests", "input", "ssi_include.html"]))}],
     RenderVars = [{path, filename:absname(filename:join(["tests", "input", "ssi_include.html"]))}],
     {ok, RenderVars};
     {ok, RenderVars};
-
-
 %%--------------------------------------------------------------------
 %%--------------------------------------------------------------------
 %% Custom tags
 %% Custom tags
 %%--------------------------------------------------------------------
 %%--------------------------------------------------------------------
@@ -311,7 +303,11 @@ test_render(Name, Module) ->
                               fun(F) -> file:write_file(F, Data) end),
                               fun(F) -> file:write_file(F, Data) end),
                             {error, io_lib:format(
                             {error, io_lib:format(
                                       "Expected output does not match rendered output~n"
                                       "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])}
                                       [RenderResult, Data])}
                     end;
                     end;
                true ->
                true ->