Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Extended {%ifchanged %} syntax to accept parameters #24

Merged
merged 1 commit into from

2 participants

@thge

Django ifchanged can accept parameters, so it is possible to check for a value
which does not appear in output. Django documentation says to support one or
more parameters, this implementation supports up to 4 paramters (only limited
by the parser expression, this can be improved to support even more
parameters when needed).

{% for date in list %}
{% ifchanged date.month %}
{{ date.month }}:{{ date.day }}
{% else %}
{{ date.day }}
{% endifchanged %}
{% endfor %}

See erlydtl_unittests for some other examples.

The other (Django) syntax to use {% ifchanged %} without parameter works like before.

@thge thge Support {% ifchanged Expression(s) %} like Django does
Django ifchanged can accept parameters, so it is possible to check for a value
which does not appear in output. Django documentation says to support one or
more parameters, this implementation supports up to 4 paramters (only limited
by the parser expression, this can be improved to support even more
parameters when needed).

{% for date in list %}
  {% ifchanged date.month %}
     {{ date.month }}:{{ date.day }}
  {% else %}
     {{ date.day }}
  {% endifchanged %}
{% endfor %}

See erlydtl_unittests for some other examples.

The other (Django) syntax to use {% ifchanged %} without parameter works like before.
11b4380
@evanmiller
Owner

Great work! The only thing that raises an eyebrow is the limit on parameters. I think we can make the syntax more general to support an arbitrary number, but for now, four parameters ought to be enough for anyone.

@evanmiller evanmiller merged commit caa53a0 into erlydtl:master
@thge
@evanmiller
Owner

Here's a patch that supports unlimited parameters in the parser, and makes a few stylistic changes to the Erlang part:

165b1bf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 7, 2012
  1. @thge

    Support {% ifchanged Expression(s) %} like Django does

    thge authored
    Django ifchanged can accept parameters, so it is possible to check for a value
    which does not appear in output. Django documentation says to support one or
    more parameters, this implementation supports up to 4 paramters (only limited
    by the parser expression, this can be improved to support even more
    parameters when needed).
    
    {% for date in list %}
      {% ifchanged date.month %}
         {{ date.month }}:{{ date.day }}
      {% else %}
         {{ date.day }}
      {% endifchanged %}
    {% endfor %}
    
    See erlydtl_unittests for some other examples.
    
    The other (Django) syntax to use {% ifchanged %} without parameter works like before.
This page is out of date. Refresh to see the latest.
View
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
-----
View
45 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
View
18 src/erlydtl_parser.yrl 100755 → 100644
@@ -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'}.
View
23 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) ->
View
12 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),
View
21 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",
Something went wrong with that request. Please try again.