Permalink
Browse files

Support for "cycle" tag. Patch from Hunter Morris.

  • Loading branch information...
1 parent e394033 commit b67188ef639cf7dd03e5bb1ccc7ec0b37b0cf043 @evanmiller evanmiller committed Jun 28, 2008
View
@@ -0,0 +1,9 @@
+before
+
+<ul>
+{% for i in test %}
+<li>{{ forloop.counter }}. {{ i }} - {% cycle a b c %}</li>
+{% endfor %}
+</ul>
+
+after
@@ -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}) ->
@@ -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).
@@ -50,6 +50,10 @@ Nonterminals
CommentBraced
EndCommentBraced
+ CycleTag
+ CycleNames
+ CycleNamesCompat
+
ForBlock
ForBraced
EndForBraced
@@ -98,6 +102,7 @@ Terminals
comment_keyword
colon
comma
+ cycle_keyword
dot
else_keyword
endautoescape_keyword
@@ -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'].
@@ -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.
@@ -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).
@@ -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});
@@ -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
@@ -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
%%--------------------------------------------------------------------
@@ -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">>}

0 comments on commit b67188e

Please sign in to comment.