Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from May 7, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.markdown
Expand Up @@ -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
-----
Expand Down
45 changes: 36 additions & 9 deletions src/erlydtl_compiler.erl
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
18 changes: 16 additions & 2 deletions src/erlydtl_parser.yrl 100755 → 100644
Expand Up @@ -85,6 +85,11 @@ Nonterminals

IfChangedBlock
IfChangedBraced
IfChangedExpression
IfChangedExpression1
IfChangedExpression2
IfChangedExpression3
IfChangedExpression4
EndIfChangedBraced

IfEqualBlock
Expand Down Expand Up @@ -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'}.
Expand Down
23 changes: 16 additions & 7 deletions src/erlydtl_runtime.erl
Expand Up @@ -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) ->
Expand Down
12 changes: 6 additions & 6 deletions src/erlydtl_unparser.erl
Expand Up @@ -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),
Expand Down
21 changes: 20 additions & 1 deletion tests/src/erlydtl_unittests.erl
Expand Up @@ -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",
Expand Down