Browse Source

1. Support variable names in for expressions (e.g., for x in foo.bar)
2. New filter: urlencode

Evan Miller 17 years ago
parent
commit
79c30e5cfb

+ 2 - 0
demo/out/test_filters.html

@@ -36,3 +36,5 @@ Right adjust:
 </pre>
 
 Uppercase: UPPERCASE
+
+URL Encode: Let%27s+go%21

+ 2 - 0
demo/templates/test_filters.html

@@ -36,3 +36,5 @@ Right adjust:
 </pre>
 
 Uppercase: {{ "uppercase"|upper }}
+
+URL Encode: {{ "Let's go!"|urlencode }}

+ 18 - 15
src/erlydtl/erlydtl_compiler.erl

@@ -215,7 +215,7 @@ body_ast(DjangoParseTree, Context) ->
                 string_ast(Number);
             ({'variable', Variable}) ->
                 {Ast, VarName} = resolve_variable_ast(Variable, Context),
-                {Ast, #ast_info{var_names = [VarName]}};
+                {format(Ast, Context), #ast_info{var_names = [VarName]}};
             ({'tag', {identifier, _, Name}, Args}) ->
                 tag_ast(Name, Args, Context);
             ({'include', {string_literal, _, File}}) ->
@@ -242,8 +242,8 @@ body_ast(DjangoParseTree, Context) ->
                     body_ast(IfContents, Context), Context);                    
             ({'apply_filter', Variable, Filter}) ->
                 filter_ast(Variable, Filter, Context);
-            ({'for', {'in', IteratorList, {identifier, _, List}}, Contents}) ->
-                for_loop_ast(IteratorList, List, Contents, Context)
+            ({'for', {'in', IteratorList, Variable}, Contents}) ->
+                for_loop_ast(IteratorList, Variable, Contents, Context)
         end, DjangoParseTree),
     {AstList, Info} = lists:mapfoldl(
         fun({Ast, Info}, InfoAcc) -> 
@@ -357,14 +357,12 @@ resolve_ifvariable_ast(VarTuple, Context) ->
     resolve_variable_ast(VarTuple, Context, erl_syntax:atom(proplists)).
            
 resolve_variable_ast({{identifier, _, VarName}}, Context, ModuleAst) ->
-    {auto_escape(format_integer_ast(resolve_variable_name_ast(VarName, Context, ModuleAst)), Context), VarName};
+    {resolve_variable_name_ast(VarName, Context, ModuleAst), VarName};
 
 resolve_variable_ast({{identifier, _, VarName}, {identifier, _, AttrName}}, Context, ModuleAst) ->
-    {auto_escape(format_integer_ast(erl_syntax:application(
-                    ModuleAst, erl_syntax:atom(get_value),
-                    [erl_syntax:atom(AttrName), resolve_variable_name_ast(VarName, Context)])), 
-                Context), []}.
-                
+    {erl_syntax:application(ModuleAst, erl_syntax:atom(get_value),
+                    [erl_syntax:atom(AttrName), resolve_variable_name_ast(VarName, Context)]), VarName}.
+
 resolve_variable_name_ast(VarName, Context) ->
     resolve_variable_name_ast(VarName, Context, none).
     
@@ -385,6 +383,9 @@ resolve_variable_name_ast(VarName, Context, ModuleAst) ->
             VarValue
     end.
 
+format(Ast, Context) ->
+    auto_escape(format_integer_ast(Ast), Context).
+
 format_integer_ast(Ast) ->
     erl_syntax:application(erl_syntax:atom(erlydtl_filters), erl_syntax:atom(format_integer),
         [Ast]).
@@ -423,8 +424,10 @@ ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCon
                     {variable, Var} ->
                         {Ast, VarName} = resolve_ifvariable_ast(Var, Context),
                         {[Ast | Asts], [VarName | AccVarNames]};
-                    {_, _, Literal} ->
-                        {[erl_syntax:string(unescape_string_literal(Literal)) | Asts], AccVarNames}                        
+                    {string_literal, _, Literal} ->
+                        {[erl_syntax:string(unescape_string_literal(Literal)) | Asts], AccVarNames};
+                    {number_literal, _, Literal} ->
+                        {[erl_syntax:integer(list_to_integer(Literal)) | Asts], AccVarNames}
                 end                
         end,
         {[], Info#ast_info.var_names},
@@ -437,7 +440,7 @@ ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCon
                         erl_syntax:list([Arg1Ast, Arg2Ast])]),    
     {Ast, Info#ast_info{var_names = VarNames}}.         
 
-for_loop_ast(IteratorList, List, Contents, Context) ->
+for_loop_ast(IteratorList, {variable, Variable}, Contents, Context) ->
     Vars = lists:map(fun({identifier, _, Iterator}) -> 
                     erl_syntax:variable("Var_" ++ Iterator) 
             end, IteratorList),
@@ -457,7 +460,7 @@ for_loop_ast(IteratorList, List, Contents, Context) ->
             erl_syntax:tuple([erl_syntax:atom('counter0'),
                     erl_syntax:infix_expr(erl_syntax:variable("Counter0"), erl_syntax:operator("+"), erl_syntax:integer(1))])
         ]),
-    ListAst = resolve_variable_name_ast(List, Context),
+    {ListAst, VarName} = resolve_ifvariable_ast(Variable, Context),
     CounterVars0 = erl_syntax:list([
             erl_syntax:tuple([erl_syntax:atom('counter'), erl_syntax:integer(1)]),
             erl_syntax:tuple([erl_syntax:atom('counter0'), erl_syntax:integer(0)])
@@ -474,7 +477,7 @@ for_loop_ast(IteratorList, List, Contents, Context) ->
                                     [erl_syntax:tuple([InnerAst, CounterAst])])
                             ]),
                         CounterVars0, ListAst])]),
-                Info#ast_info{var_names = [List]}}.
+                Info#ast_info{var_names = [VarName]}}.
 
 %% TODO: implement "load" tag to make custom tags work like in original django
 tag_ast(Name, Args, Context) ->
@@ -482,7 +485,7 @@ tag_ast(Name, Args, Context) ->
             ({{identifier, _, Key}, {string_literal, _, Value}}) ->
                 {list_to_atom(Key), erl_syntax:string(unescape_string_literal(Value))};
             ({{identifier, _, Key}, {variable, Value}}) ->
-                {list_to_atom(Key), resolve_variable_ast(Value, Context)}
+                {list_to_atom(Key), format(resolve_variable_ast(Value, Context), Context)}
         end, Args),
     Source = filename:join([erlydtl_deps:get_base_dir(), "priv", "tags", Name]),
     case parse(Source, Context#dtl_context.reader) of

+ 21 - 0
src/erlydtl/erlydtl_filters.erl

@@ -101,6 +101,9 @@ plus(Input, Number) when is_integer(Input) ->
 upper(Input) ->
     string:to_upper(lists:flatten(Input)).
 
+urlencode(Input) ->
+    urlencode(lists:flatten(Input), []).
+
 % internal
 
 escape([], Acc) ->
@@ -133,3 +136,21 @@ linebreaksbr("\n" ++ Rest, Acc) ->
     linebreaksbr(Rest, lists:reverse("<br />", Acc));
 linebreaksbr([C | Rest], Acc) ->
     linebreaksbr(Rest, [C | Acc]).
+
+% Taken from quote_plus of mochiweb_util
+urlencode([], Acc) ->
+    lists:reverse(Acc);
+urlencode([C | Rest], Acc) when ((C >= $a andalso C =< $z) orelse
+                                  (C >= $A andalso C =< $Z) orelse
+                                  (C >= $0 andalso C =< $9) orelse
+                                  (C =:= $\. orelse C =:= $- 
+                                      orelse C =:= $~ orelse C =:= $_)) ->
+    urlencode(Rest, [C | Acc]);
+urlencode([$\s | Rest], Acc) ->
+    urlencode(Rest, [$+ | Acc]);
+urlencode([C | Rest], Acc) ->
+    <<Hi:4, Lo:4>> = <<C>>,
+    urlencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]).
+
+hexdigit(C) when C < 10 -> $0 + C;
+hexdigit(C) when C < 16 -> $A + (C - 10).

+ 2 - 2
src/erlydtl/erlydtl_parser.yrl

@@ -159,8 +159,8 @@ EndCommentBraced -> open_tag endcomment_keyword close_tag.
 ForBlock -> ForBraced Elements EndForBraced : {for, '$1', '$2'}.
 ForBraced -> open_tag for_keyword ForExpression close_tag : '$3'.
 EndForBraced -> open_tag endfor_keyword close_tag.
-ForExpression -> identifier : '$1'.
-ForExpression -> ForGroup in_keyword identifier : {'in', '$1', '$3'}.
+ForExpression -> Variable : '$1'.
+ForExpression -> ForGroup in_keyword Variable : {'in', '$1', '$3'}.
 ForGroup -> identifier : ['$1'].
 ForGroup -> ForGroup comma identifier : '$1' ++ ['$3'].