diff --git a/README.markdown b/README.markdown index cf867aa..735bc57 100644 --- a/README.markdown +++ b/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 ----- diff --git a/src/erlydtl_compiler.erl b/src/erlydtl_compiler.erl index a8b9363..8643e2e 100755 --- a/src/erlydtl_compiler.erl +++ b/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 diff --git a/src/erlydtl_parser.yrl b/src/erlydtl_parser.yrl old mode 100755 new mode 100644 index 2eecaab..06b60cf --- a/src/erlydtl_parser.yrl +++ b/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'}. diff --git a/src/erlydtl_runtime.erl b/src/erlydtl_runtime.erl index 9b0fc7a..a1693bd 100644 --- a/src/erlydtl_runtime.erl +++ b/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) -> diff --git a/src/erlydtl_unparser.erl b/src/erlydtl_unparser.erl index afe306b..af7a1fc 100644 --- a/src/erlydtl_unparser.erl +++ b/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), diff --git a/tests/src/erlydtl_unittests.erl b/tests/src/erlydtl_unittests.erl index 063965e..ac2a79b 100644 --- a/tests/src/erlydtl_unittests.erl +++ b/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",