Skip to content

Commit

Permalink
Support for "cycle" tag. Patch from Hunter Morris.
Browse files Browse the repository at this point in the history
git-svn-id: http://erlydtl.googlecode.com/svn/trunk@135 a5195066-8e3e-0410-a82a-05b01b1b9875
  • Loading branch information
emmiller committed Jun 28, 2008
1 parent b4f92e5 commit 26f41f0
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 3 deletions.
9 changes: 9 additions & 0 deletions examples/docroot/cycle
@@ -0,0 +1,9 @@
before

<ul>
{% for i in test %}
<li>{{ forloop.counter }}. {{ i }} - {% cycle a b c %}</li>
{% endfor %}
</ul>

after
27 changes: 26 additions & 1 deletion src/erlydtl/erlydtl_compiler.erl
Expand Up @@ -365,7 +365,11 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
call_ast(Name, TreeWalkerAcc);
({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
call_with_ast(Name, With, Context, TreeWalkerAcc)
call_with_ast(Name, With, Context, TreeWalkerAcc);
({'cycle', Names}, TreeWalkerAcc) ->
cycle_ast(Names, Context, TreeWalkerAcc);
({'cycle_compat', Names}, TreeWalkerAcc) ->
cycle_compat_ast(Names, Context, TreeWalkerAcc)
end, TreeWalker, DjangoParseTree),
{AstList, {Info, TreeWalker3}} = lists:mapfoldl(
fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
Expand Down Expand Up @@ -608,6 +612,27 @@ load_ast(Names, _Context, TreeWalker) ->
CustomTags = lists:merge([X || {identifier, _ , X} <- Names], TreeWalker#treewalker.custom_tags),
{{erl_syntax:list([]), #ast_info{}}, TreeWalker#treewalker{custom_tags = CustomTags}}.

cycle_ast(Names, Context, TreeWalker) ->
NamesTuple = lists:map(fun({string_literal, _, Str}) ->
erl_syntax:string(unescape_string_literal(Str));
({variable, _}=Var) ->
{V, _} = resolve_variable_ast(Var, Context),
V;
({number_literal, _, Num}) ->
format(erl_syntax:integer(Num), Context);
(_) ->
[]
end, Names),
{{erl_syntax:application(
erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
[erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters")]), #ast_info{}}, TreeWalker}.

%% Older Django templates treat cycle with comma-delimited elements as strings
cycle_compat_ast(Names, _Context, TreeWalker) ->
NamesTuple = [erl_syntax:string(X) || {identifier, _, X} <- Names],
{{erl_syntax:application(
erl_syntax:atom('erlydtl_runtime'), erl_syntax:atom('cycle'),
[erl_syntax:tuple(NamesTuple), erl_syntax:variable("Counters")]), #ast_info{}}, TreeWalker}.

unescape_string_literal(String) ->
unescape_string_literal(string:strip(String, both, 34), [], noslash).
Expand Down
16 changes: 16 additions & 0 deletions src/erlydtl/erlydtl_parser.yrl
Expand Up @@ -50,6 +50,10 @@ Nonterminals
CommentBraced
EndCommentBraced

CycleTag
CycleNames
CycleNamesCompat

ForBlock
ForBraced
EndForBraced
Expand Down Expand Up @@ -98,6 +102,7 @@ Terminals
comment_keyword
colon
comma
cycle_keyword
dot
else_keyword
endautoescape_keyword
Expand Down Expand Up @@ -137,6 +142,7 @@ Elements -> Elements ExtendsTag : '$1' ++ ['$2'].
Elements -> Elements IncludeTag : '$1' ++ ['$2'].
Elements -> Elements NowTag : '$1' ++ ['$2'].
Elements -> Elements LoadTag : '$1' ++ ['$2'].
Elements -> Elements CycleTag : '$1' ++ ['$2'].
Elements -> Elements BlockBlock : '$1' ++ ['$2'].
Elements -> Elements ForBlock : '$1' ++ ['$2'].
Elements -> Elements IfBlock : '$1' ++ ['$2'].
Expand Down Expand Up @@ -173,6 +179,16 @@ CommentBlock -> CommentBraced Elements EndCommentBraced : {comment, '$2'}.
CommentBraced -> open_tag comment_keyword close_tag.
EndCommentBraced -> open_tag endcomment_keyword close_tag.

CycleTag -> open_tag cycle_keyword CycleNamesCompat close_tag : {cycle_compat, '$3'}.
CycleTag -> open_tag cycle_keyword CycleNames close_tag : {cycle, '$3'}.

CycleNames -> Value : ['$1'].
CycleNames -> CycleNames Value : '$1' ++ ['$2'].

CycleNamesCompat -> identifier comma : ['$1'].
CycleNamesCompat -> CycleNamesCompat identifier comma : '$1' ++ ['$2'].
CycleNamesCompat -> CycleNamesCompat identifier : '$1' ++ ['$2'].

ForBlock -> ForBraced Elements EndForBraced : {for, '$1', '$2'}.
ForBraced -> open_tag for_keyword ForExpression close_tag : '$3'.
EndForBraced -> open_tag endfor_keyword close_tag.
Expand Down
3 changes: 3 additions & 0 deletions src/erlydtl/erlydtl_runtime.erl
Expand Up @@ -84,3 +84,6 @@ increment_counter_stats([{counter, Counter}, {counter0, Counter0}, {revcounter,
{revcounter0, RevCounter0 - 1},
{first, false}, {last, RevCounter0 =:= 1},
{parentloop, Parent}].

cycle(NamesTuple, Counters) when is_tuple(NamesTuple) ->
element(fetch_value(counter0, Counters) rem size(NamesTuple) + 1, NamesTuple).
19 changes: 19 additions & 0 deletions src/erlydtl/erlydtl_scanner.erl
Expand Up @@ -121,19 +121,38 @@ scan("\"" ++ T, Scanned, {Row, Column}, {in_code, Closer}) ->
scan("\"" ++ T, Scanned, {Row, Column}, {in_identifier, Closer}) ->
scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_double_quote, Closer});

scan("\'" ++ T, Scanned, {Row, Column}, {in_code, Closer}) ->
scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_single_quote, Closer});

scan("\'" ++ T, Scanned, {Row, Column}, {in_identifier, Closer}) ->
scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_single_quote, Closer});

scan([$\\ | T], Scanned, {Row, Column}, {in_double_quote, Closer}) ->
scan(T, append_char(Scanned, $\\), {Row, Column + 1}, {in_double_quote_slash, Closer});

scan([H | T], Scanned, {Row, Column}, {in_double_quote_slash, Closer}) ->
scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_double_quote, Closer});

scan([$\\ | T], Scanned, {Row, Column}, {in_single_quote, Closer}) ->
scan(T, append_char(Scanned, $\\), {Row, Column + 1}, {in_single_quote_slash, Closer});

scan([H | T], Scanned, {Row, Column}, {in_single_quote_slash, Closer}) ->
scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_single_quote, Closer});

% end quote
scan("\"" ++ T, Scanned, {Row, Column}, {in_double_quote, Closer}) ->
scan(T, append_char(Scanned, 34), {Row, Column + 1}, {in_code, Closer});

% treat single quotes the same as double quotes
scan("\'" ++ T, Scanned, {Row, Column}, {in_single_quote, Closer}) ->
scan(T, append_char(Scanned, 34), {Row, Column + 1}, {in_code, Closer});

scan([H | T], Scanned, {Row, Column}, {in_double_quote, Closer}) ->
scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_double_quote, Closer});

scan([H | T], Scanned, {Row, Column}, {in_single_quote, Closer}) ->
scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_single_quote, Closer});


scan("," ++ T, Scanned, {Row, Column}, {_, Closer}) ->
scan(T, [{comma, {Row, Column}, ","} | Scanned], {Row, Column + 1}, {in_code, Closer});
Expand Down
9 changes: 7 additions & 2 deletions src/tests/erlydtl_functional_tests.erl
Expand Up @@ -3,7 +3,7 @@
%%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
%%% @author Evan Miller <emmiller@gmail.com>
%%% @copyright 2008 Roberto Saccon, Evan Miller
%%% @doc ErlyDTS test suite
%%% @doc ErlyDTL test suite
%%% @end
%%%
%%% The MIT License
Expand Down Expand Up @@ -143,7 +143,12 @@ setup("var_preset") ->
setup("var_error") ->
CompileVars = [],
RenderVars = [{var1, "foostring1"}],
{ok, CompileVars, error, RenderVars};
{ok, CompileVars, error, RenderVars};
setup("cycle") ->
CompileVars = [],
RenderVars = [{test, [integer_to_list(X) || X <- lists:seq(1, 20)]},
{a, "Apple"}, {b, "Banana"}, {c, "Cherry"}],
{ok, CompileVars, ok, RenderVars};
%%--------------------------------------------------------------------
%% Custom tags
%%--------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions src/tests/erlydtl_unittests.erl
Expand Up @@ -26,6 +26,15 @@ tests() ->
{"Newlines are escaped",
<<"{{ \"foo\\n\" }}">>, [], <<"foo\n">>}
]},
{"cycle", [
{"Cycling through quoted strings",
<<"{% for i in test %}{% cycle 'a' 'b' %}{{ i }},{% endfor %}">>,
[{test, ["0", "1", "2", "3", "4"]}], <<"a0,b1,a2,b3,a4,">>},
{"Cycling through normal variables",
<<"{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}">>,
[{test, ["0", "1", "2", "3", "4"]}, {aye, "a"}, {bee, "b"}],
<<"a0,b1,a2,b3,a4,">>}
]},
{"number literal", [
{"Render integer",
<<"{{ 5 }}">>, [], <<"5">>}
Expand Down

0 comments on commit 26f41f0

Please sign in to comment.