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

Merge pull request #24 from thge/master

Extended {%ifchanged %} syntax to accept parameters
Evan Miller 13 лет назад
Родитель
Сommit
caa53a00db

+ 0 - 2
README.markdown

@@ -175,8 +175,6 @@ Differences from standard Django Template Language
 
 The "regroup" tag must have an ending "endregroup" tag.
 
-The "ifchanged" tag cannot take arguments.
-
 
 Tests
 -----

+ 36 - 9
src/erlydtl_compiler.erl

@@ -545,14 +545,24 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
                 {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
                 ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
-            ({'ifchanged', Contents}, TreeWalkerAcc) ->
+            ({'ifchanged', Expression, Contents}, TreeWalkerAcc) ->
                 {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
-                ifchanged_ast(Contents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
-            ({'ifchangedelse', IfContents, ElseContents}, TreeWalkerAcc) ->
+                case Expression of
+                    '$undefined' ->
+                        ifchanged_ast(Contents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+                    _ ->
+                        ifchanged_ast({expr, Expression}, IfAstInfo, ElseAstInfo, Context, TreeWalker2)
+                end;
+            ({'ifchangedelse', Expression, IfContents, ElseContents}, TreeWalkerAcc) ->
                 {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
-                ifchanged_ast(IfContents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+                case Expression of
+                    '$undefined' ->
+                        ifchanged_ast(IfContents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+                    _ ->
+                        ifchanged_ast({expr, Expression}, IfAstInfo, ElseAstInfo, Context, TreeWalker2)
+                end;
             ({'ifelse', Expression, IfContents, ElseContents}, TreeWalkerAcc) ->
                 {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
                 {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
@@ -1089,11 +1099,28 @@ for_loop_ast(IteratorList, LoopValue, Contents, {EmptyContentsAst, EmptyContents
             merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)
         }, TreeWalker2}.
 
-ifchanged_ast(ParseTree, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, _Context, TreeWalker) ->
-    SourceText = lists:flatten(erlydtl_unparser:unparse(ParseTree)),
-    {{erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), 
-                [erl_syntax:string(SourceText), IfContentsAst, ElseContentsAst]),
-            merge_info(IfContentsInfo, ElseContentsInfo)}, TreeWalker}.
+ifchanged_ast({expr, Expressions}, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
+    Info = merge_info(IfContentsInfo, ElseContentsInfo),
+    ValueAstFun = fun(Expr, {LTreeWalker, LInfo, Acc}) ->
+                          {{EAst, EInfo}, ETw} = value_ast(Expr, false, Context, LTreeWalker),
+                          {ETw, merge_info(LInfo, EInfo), [erl_syntax:tuple([erl_syntax:integer(erlang:phash2(Expr)), EAst])|Acc]} end,
+    {TreeWalker1, MergedInfo, Changed} = lists:foldl(ValueAstFun, {TreeWalker, Info,  []}, Expressions),
+    {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), [erl_syntax:list(Changed)]),
+        [erl_syntax:clause([erl_syntax:atom(true)], none,
+                [IfContentsAst]),
+            erl_syntax:clause([erl_syntax:underscore()], none,
+                [ElseContentsAst])
+        ]), MergedInfo}, TreeWalker1};
+ifchanged_ast(Contents, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, _Context, TreeWalker) ->
+    Info = merge_info(IfContentsInfo, ElseContentsInfo),
+    Key = erl_syntax:integer(erlang:phash2(Contents)),
+    {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), [erl_syntax:list([erl_syntax:tuple([Key, IfContentsAst])])]),
+        [erl_syntax:clause([erl_syntax:atom(true)], none,
+                [IfContentsAst]),
+            erl_syntax:clause([erl_syntax:underscore()], none,
+                [ElseContentsAst])
+        ]), Info}, TreeWalker}.
+
 
 cycle_ast(Names, Context, TreeWalker) ->
     {NamesTuple, VarNames} = lists:mapfoldl(fun

+ 16 - 2
src/erlydtl_parser.yrl

@@ -85,6 +85,11 @@ Nonterminals
     
     IfChangedBlock
     IfChangedBraced
+    IfChangedExpression
+    IfChangedExpression1
+    IfChangedExpression2
+    IfChangedExpression3
+    IfChangedExpression4
     EndIfChangedBraced
 
     IfEqualBlock
@@ -317,9 +322,18 @@ Unot -> not_keyword IfExpression : {expr, "not", '$2'}.
 ElseBraced -> open_tag else_keyword close_tag.
 EndIfBraced -> open_tag endif_keyword close_tag.
 
-IfChangedBlock -> IfChangedBraced Elements ElseBraced Elements EndIfChangedBraced : {ifchangedelse, '$2', '$4'}.
-IfChangedBlock -> IfChangedBraced Elements EndIfChangedBraced : {ifchanged, '$2'}.
+IfChangedBlock -> IfChangedBraced Elements ElseBraced Elements EndIfChangedBraced : {ifchangedelse, '$1', '$2', '$4'}.
+IfChangedBlock -> IfChangedBraced Elements EndIfChangedBraced : {ifchanged, '$1', '$2'}.
 IfChangedBraced -> open_tag ifchanged_keyword close_tag.
+IfChangedBraced -> open_tag ifchanged_keyword IfChangedExpression close_tag : '$3'.
+IfChangedExpression -> IfChangedExpression1 IfChangedExpression2 : ['$1', '$2'].
+IfChangedExpression -> IfChangedExpression1 IfChangedExpression2 IfChangedExpression3 : ['$1', '$2', '$3'].
+IfChangedExpression -> IfChangedExpression1 IfChangedExpression2 IfChangedExpression3 IfChangedExpression4: ['$1', '$2', '$3', '$4'].
+IfChangedExpression -> Value : ['$1'].
+IfChangedExpression1 -> Value : '$1'.
+IfChangedExpression2 -> Value : '$1'.
+IfChangedExpression3 -> Value : '$1'.
+IfChangedExpression4 -> Value : '$1'.
 EndIfChangedBraced -> open_tag endifchanged_keyword close_tag.
 
 IfEqualBlock -> IfEqualBraced Elements ElseBraced Elements EndIfEqualBraced : {ifequalelse, '$1', '$2', '$4'}.

+ 16 - 7
src/erlydtl_runtime.erl

@@ -228,16 +228,25 @@ pop_ifchanged_context() ->
     [_|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE),
     put(?IFCHANGED_CONTEXT_VARIABLE, Rest).
 
-ifchanged(SourceText, EvaluatedText, AlternativeText) ->
+ifchanged(Expressions) ->
     [IfChangedContext|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE),
-    PreviousText = proplists:get_value(SourceText, IfChangedContext),
+    {Result, NewContext} = lists:foldl(fun (Expr, {ProvResult, Context}) when ProvResult == true ->
+                                               {_, NContext} = ifchanged2(Expr, Context),
+                                               {true, NContext};
+                                           (Expr, {_ProvResult, Context}) ->
+                                               ifchanged2(Expr, Context)
+                                       end, {false, IfChangedContext}, Expressions),
+    put(?IFCHANGED_CONTEXT_VARIABLE, [NewContext|Rest]),
+    Result.
+
+ifchanged2({Key, Value}, IfChangedContext) ->
+    PreviousValue = proplists:get_value(Key, IfChangedContext),
     if
-        PreviousText =:= EvaluatedText ->
-            AlternativeText;
+        PreviousValue =:= Value ->
+            {false, IfChangedContext};
         true ->
-            NewContext = [{SourceText, EvaluatedText}|proplists:delete(SourceText, IfChangedContext)],
-            put(?IFCHANGED_CONTEXT_VARIABLE, [NewContext|Rest]),
-            EvaluatedText
+            NewContext = [{Key, Value}|proplists:delete(Key, IfChangedContext)],
+            {true, NewContext}
     end.
 
 cycle(NamesTuple, Counters) when is_tuple(NamesTuple) ->

+ 6 - 6
src/erlydtl_unparser.erl

@@ -46,16 +46,16 @@ unparse([{'if', Expression, Contents}|Rest], Acc) ->
     unparse(Rest, [["{% if ", unparse_expression(Expression), " %}",
                 unparse(Contents),
                 "{% endif %}"]|Acc]);
-unparse([{'ifchanged', IfContents}|Rest], Acc) ->
-    unparse(Rest, [["{% ifchanged %}",
+unparse([{'ifchanged', Expression, IfContents}|Rest], Acc) ->
+    unparse(Rest, [["{% ifchanged ", unparse_expression(Expression), " %}",
                 unparse(IfContents),
-                "{% endif %}"]|Acc]);
-unparse([{'ifchangedelse', IfContents, ElseContents}|Rest], Acc) ->
-    unparse(Rest, [["{% ifchanged %}",
+                "{% endifchanged %}"]|Acc]);
+unparse([{'ifchangedelse', Expression, IfContents, ElseContents}|Rest], Acc) ->
+    unparse(Rest, [["{% ifchanged ", unparse_expression(Expression), " %}",
                 unparse(IfContents),
                 "{% else %}",
                 unparse(ElseContents),
-                "{% endif %}"]|Acc]);
+                "{% endifchanged %}"]|Acc]);
 unparse([{'ifelse', Expression, IfContents, ElseContents}|Rest], Acc) ->
     unparse(Rest, [["{% if ", unparse_expression(Expression), " %}",
                 unparse(IfContents),

+ 20 - 1
tests/src/erlydtl_unittests.erl

@@ -241,9 +241,28 @@ tests() ->
                 {"If changed",
                     <<"{% for x in list %}{% ifchanged %}{{ x }}\n{% endifchanged %}{% endfor %}">>,
                     [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nthree\n">>},
+                {"If changed/2",
+                    <<"{% for x, y in list %}{% ifchanged %}{{ x|upper }}{% endifchanged %}{% ifchanged %}{{ y|lower }}{% endifchanged %}\n{% endfor %}">>,
+                    [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "c"], ["Three", "b"]]}], <<"ONEa\nTWO\nb\nTHREE\nc\nb\n">>},
                 {"If changed/else",
                     <<"{% for x in list %}{% ifchanged %}{{ x }}\n{% else %}foo\n{% endifchanged %}{% endfor %}">>,
-                    [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nfoo\nthree\nfoo\nfoo\n">>}
+                    [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nfoo\nthree\nfoo\nfoo\n">>},
+                {"If changed/param",
+                    <<"{% for date in list %}{% ifchanged date.month %} {{ date.month }}:{{ date.day }}{% else %},{{ date.day }}{% endifchanged %}{% endfor %}\n">>,
+                    [{'list', [[{month,"Jan"},{day,1}],[{month,"Jan"},{day,2}],[{month,"Apr"},{day,10}],
+                               [{month,"Apr"},{day,11}],[{month,"May"},{day,4}]]}], 
+                    <<" Jan:1,2 Apr:10,11 May:4\n">>},
+                {"If changed/param2",
+                    <<"{% for x, y in list %}{% ifchanged y|upper %}{{ x|upper }}{% endifchanged %}\n{% endfor %}">>,
+                    [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "c"], ["Three", "b"]]}], <<"ONE\n\nTWO\n\nTHREE\nTHREE\n">>},
+                {"If changed/param2 combined",
+                    <<"{% for x, y in list %}{% ifchanged x y|upper %}{{ x }}{% endifchanged %}\n{% endfor %}">>,
+                    [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "B"], ["three", "c"]]}], <<"one\ntwo\ntwo\nthree\n\nthree\n">>},
+                {"If changed/resolve",
+                    <<"{% for x in list %}{% ifchanged x.name|first %}{{ x.value }}{% endifchanged %}\n{% endfor %}">>,
+                    [{'list', [[{"name", ["nA","nB"]},{"value","1"}],[{"name", ["nA","nC"]},{"value","2"}],
+                               [{"name", ["nB","nC"]},{"value","3"}],[{"name", ["nB","nA"]},{"value","4"}]]}],
+                    <<"1\n\n3\n\n">>}
             ]},
         {"for/empty", [
                 {"Simple loop",