Permalink
Browse files

New tags: ssi, filter, templatetag, and widthratio

  • Loading branch information...
1 parent 7000228 commit 7bd6d356f456314dc5b5bc63bd7fce1c713cdcaf @evanmiller evanmiller committed Mar 19, 2011
View
@@ -3,9 +3,9 @@ ErlyDTL
ErlyDTL implements most but not all of the Django Template Language.
-*Supported tags*: autoescape, block, comment, cycle, extends, firstof, for, if, ifchanged, ifequal, ifnotequal, include, now, trans
+*Supported tags*: autoescape, block, comment, cycle, extends, filter, firstof, for, if, ifequal, ifnotequal, include, now, ssi, templatetag, trans, widthratio, with
-_Unsupported tags_: csrf_token, filter, regroup, spaceless, ssi, templatetag, url, widthratio
+_Unsupported tags_: csrf_token, ifchanged, regroup, spaceless, url
*Supported filters*: add, addslashes, capfirst, center, cut, date, default, default_if_none, dictsort, dictsortreversed, divisibleby, escape, escapejs, filesizeformat, first, fix_ampersands, floatformat, force_escape, format_integer, format_number, get_digit, join, last, length, length_is, linebreaks, linebreaksbr, linenumbers, ljust, lower, make_list, phonenumeric, pluralize, pprint, random, random_num, random_range, removetags, rjust, safe, safeseq, slice, slugify, stringformat, striptags, time, timesince, timeuntil, title, truncatewords, truncatewords_html, unordered_list, upper, urlencode, urlize, urlizetrunc, wordcount, wordwrap, yesno
View
@@ -456,27 +456,37 @@ body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, T
body_ast(DjangoParseTree, Context, TreeWalker) ->
{AstInfoList, TreeWalker2} = lists:mapfoldl(
fun
+ ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
+ body_ast(Contents, Context#dtl_context{auto_escape = OnOrOff},
+ TreeWalkerAcc);
({'block', {identifier, _, Name}, Contents}, TreeWalkerAcc) ->
Block = case dict:find(Name, Context#dtl_context.block_dict) of
- {ok, ChildBlock} ->
- ChildBlock;
- _ ->
- Contents
+ {ok, ChildBlock} -> ChildBlock;
+ _ -> Contents
end,
body_ast(Block, Context, TreeWalkerAcc);
+ ({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
+ call_ast(Name, TreeWalkerAcc);
+ ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
+ call_with_ast(Name, With, Context, TreeWalkerAcc);
({'comment', _Contents}, TreeWalkerAcc) ->
empty_ast(TreeWalkerAcc);
+ ({'cycle', Names}, TreeWalkerAcc) ->
+ cycle_ast(Names, Context, TreeWalkerAcc);
+ ({'cycle_compat', Names}, TreeWalkerAcc) ->
+ cycle_compat_ast(Names, Context, TreeWalkerAcc);
({'date', 'now', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
now_ast(FormatString, Context, TreeWalkerAcc);
- ({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
- body_ast(Contents, Context#dtl_context{auto_escape = OnOrOff},
- TreeWalkerAcc);
- ({'string', _Pos, String}, TreeWalkerAcc) ->
- string_ast(String, TreeWalkerAcc);
- ({'trans', Value}, TreeWalkerAcc) ->
- translated_ast(Value, Context, TreeWalkerAcc);
- ({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
- include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
+ ({'filter', FilterList, Contents}, TreeWalkerAcc) ->
+ filter_tag_ast(FilterList, Contents, Context, TreeWalkerAcc);
+ ({'firstof', Vars}, TreeWalkerAcc) ->
+ firstof_ast(Vars, Context, TreeWalkerAcc);
+ ({'for', {'in', IteratorList, Variable}, Contents}, TreeWalkerAcc) ->
+ {EmptyAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
+ for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
+ ({'for', {'in', IteratorList, Variable}, Contents, EmptyPartContents}, TreeWalkerAcc) ->
+ {EmptyAstInfo, TreeWalker1} = body_ast(EmptyPartContents, Context, TreeWalkerAcc),
+ for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
({'if', Expression, Contents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
@@ -501,24 +511,22 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
{IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'for', {'in', IteratorList, Variable}, Contents}, TreeWalkerAcc) ->
- {EmptyAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
- for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
- ({'for', {'in', IteratorList, Variable}, Contents, EmptyPartContents}, TreeWalkerAcc) ->
- {EmptyAstInfo, TreeWalker1} = body_ast(EmptyPartContents, Context, TreeWalkerAcc),
- for_loop_ast(IteratorList, Variable, Contents, EmptyAstInfo, Context, TreeWalker1);
+ ({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
+ include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
+ ({'ssi', Arg}, TreeWalkerAcc) ->
+ ssi_ast(Arg, Context, TreeWalkerAcc);
+ ({'string', _Pos, String}, TreeWalkerAcc) ->
+ string_ast(String, TreeWalkerAcc);
({'tag', {'identifier', _, Name}, Args}, TreeWalkerAcc) ->
tag_ast(Name, Args, Context, TreeWalkerAcc);
- ({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
- call_ast(Name, TreeWalkerAcc);
- ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
- call_with_ast(Name, With, Context, TreeWalkerAcc);
- ({'firstof', Vars}, TreeWalkerAcc) ->
- firstof_ast(Vars, Context, TreeWalkerAcc);
- ({'cycle', Names}, TreeWalkerAcc) ->
- cycle_ast(Names, Context, TreeWalkerAcc);
- ({'cycle_compat', Names}, TreeWalkerAcc) ->
- cycle_compat_ast(Names, Context, TreeWalkerAcc);
+ ({'templatetag', {_, _, TagName}}, TreeWalkerAcc) ->
+ templatetag_ast(TagName, Context, TreeWalkerAcc);
+ ({'trans', Value}, TreeWalkerAcc) ->
+ translated_ast(Value, Context, TreeWalkerAcc);
+ ({'widthratio', Numerator, Denominator, Scale}, TreeWalkerAcc) ->
+ widthratio_ast(Numerator, Denominator, Scale, Context, TreeWalkerAcc);
+ ({'with', Args, Contents}, TreeWalkerAcc) ->
+ with_ast(Args, Contents, Context, TreeWalkerAcc);
(ValueToken, TreeWalkerAcc) ->
{{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, Context, TreeWalkerAcc),
{{format(ValueAst, Context, ValueTreeWalker),ValueInfo},ValueTreeWalker}
@@ -640,6 +648,36 @@ translated_ast2(NewStrAst, DefaultStringAst, AstInfo, TreeWalker) ->
[NewStrAst, erl_syntax:variable("TranslationFun"), DefaultStringAst]),
{{StringLookupAst, AstInfo}, TreeWalker}.
+% Completely unnecessary in ErlyDTL (use {{ "{%" }} etc), but implemented for compatibility.
+templatetag_ast('openblock', _Context, TreeWalker) ->
+ {{erl_syntax:string("{%"), #ast_info{}}, TreeWalker};
+templatetag_ast('closeblock', _Context, TreeWalker) ->
+ {{erl_syntax:string("%}"), #ast_info{}}, TreeWalker};
+templatetag_ast('openvariable', _Context, TreeWalker) ->
+ {{erl_syntax:string("{{"), #ast_info{}}, TreeWalker};
+templatetag_ast('closevariable', _Context, TreeWalker) ->
+ {{erl_syntax:string("}}"), #ast_info{}}, TreeWalker};
+templatetag_ast('openbrace', _Context, TreeWalker) ->
+ {{erl_syntax:string("{"), #ast_info{}}, TreeWalker};
+templatetag_ast('closebrace', _Context, TreeWalker) ->
+ {{erl_syntax:string("}"), #ast_info{}}, TreeWalker};
+templatetag_ast('opencomment', _Context, TreeWalker) ->
+ {{erl_syntax:string("{#"), #ast_info{}}, TreeWalker};
+templatetag_ast('closecomment', _Context, TreeWalker) ->
+ {{erl_syntax:string("#}"), #ast_info{}}, TreeWalker}.
+
+
+widthratio_ast(Numerator, Denominator, Scale, Context, TreeWalker) ->
+ {{NumAst, NumInfo}, TreeWalker1} = value_ast(Numerator, false, Context, TreeWalker),
+ {{DenAst, DenInfo}, TreeWalker2} = value_ast(Denominator, false, Context, TreeWalker1),
+ {{ScaleAst, ScaleInfo}, TreeWalker3} = value_ast(Scale, false, Context, TreeWalker2),
+ {{format_number_ast(erl_syntax:application(
+ erl_syntax:atom(erlydtl_runtime),
+ erl_syntax:atom(widthratio),
+ [NumAst, DenAst, ScaleAst])), merge_info(ScaleInfo, merge_info(NumInfo, DenInfo))},
+ TreeWalker3}.
+
+
string_ast(String, TreeWalker) ->
{{erl_syntax:string(String), #ast_info{}}, TreeWalker}. %% less verbose AST, better for development and debugging
% {{erl_syntax:binary([erl_syntax:binary_field(erl_syntax:integer(X)) || X <- String]), #ast_info{}}, TreeWalker}.
@@ -656,23 +694,71 @@ include_ast(File, Context, TreeWalker) ->
end.
+filter_tag_ast(FilterList, Contents, Context, TreeWalker) ->
+ {{InnerAst, Info}, TreeWalker1} = body_ast(Contents, Context#dtl_context{auto_escape = did}, TreeWalker),
+ {{FilteredAst, FilteredInfo}, TreeWalker2} = lists:foldl(fun
+ ([{identifier, _, 'escape'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
+ {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
+ ([{identifier, _, 'safe'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
+ {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
+ ([{identifier, _, 'safeseq'}], {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
+ {{AstAcc, InfoAcc}, TreeWalkerAcc#treewalker{safe = true}};
+ (Filter, {{AstAcc, InfoAcc}, TreeWalkerAcc}) ->
+ {Ast, AstInfo} = filter_ast1(Filter, AstAcc, Context),
+ {{Ast, merge_info(InfoAcc, AstInfo)}, TreeWalkerAcc}
+ end, {{erl_syntax:application(
+ erl_syntax:atom(lists),
+ erl_syntax:atom(flatten),
+ [InnerAst]), Info}, TreeWalker1}, FilterList),
+
+ EscapedAst = case search_for_escape_filter(lists:reverse(FilterList), Context) of
+ on ->
+ erl_syntax:application(
+ erl_syntax:atom(erlydtl_filters),
+ erl_syntax:atom(force_escape),
+ [FilteredAst]);
+ _ ->
+ FilteredAst
+ end,
+ {{EscapedAst, FilteredInfo}, TreeWalker2}.
+
+search_for_escape_filter(FilterList, #dtl_context{auto_escape = on}) ->
+ search_for_safe_filter(FilterList);
+search_for_escape_filter(_, #dtl_context{auto_escape = did}) ->
+ off;
+search_for_escape_filter([[{identifier, _, 'escape'}]|Rest], _Context) ->
+ search_for_safe_filter(Rest);
+search_for_escape_filter([_|Rest], Context) ->
+ search_for_escape_filter(Rest, Context);
+search_for_escape_filter([], _Context) ->
+ off.
+
+search_for_safe_filter([[{identifier, _, 'safe'}]|_]) ->
+ off;
+search_for_safe_filter([[{identifier, _, 'safeseq'}]|_]) ->
+ off;
+search_for_safe_filter([_|Rest]) ->
+ search_for_safe_filter(Rest);
+search_for_safe_filter([]) ->
+ on.
+
filter_ast(Variable, Filter, Context, TreeWalker) ->
% the escape filter is special; it is always applied last, so we have to go digging for it
% AutoEscape = 'did' means we (will have) decided whether to escape the current variable,
% so don't do any more escaping
{{UnescapedAst, Info}, TreeWalker2} = filter_ast_noescape(Variable, Filter,
Context#dtl_context{auto_escape = did}, TreeWalker),
- case search_for_escape_filter(Variable, Filter, Context) of
+ EscapedAst = case search_for_escape_filter(Variable, Filter, Context) of
on ->
- {{erl_syntax:application(
- erl_syntax:atom(erlydtl_filters),
- erl_syntax:atom(force_escape),
- [UnescapedAst]),
- Info}, TreeWalker2};
- _ ->
- {{UnescapedAst, Info}, TreeWalker2}
- end.
+ erl_syntax:application(
+ erl_syntax:atom(erlydtl_filters),
+ erl_syntax:atom(force_escape),
+ [UnescapedAst]);
+ _ ->
+ UnescapedAst
+ end,
+ {{EscapedAst, Info}, TreeWalker2}.
filter_ast_noescape(Variable, [{identifier, _, 'escape'}], Context, TreeWalker) ->
value_ast(Variable, true, Context, TreeWalker#treewalker{safe = true});
@@ -785,6 +871,24 @@ ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCo
[ElseContentsAst])
]), merge_info(ExpressionInfo, Info)}, TreeWalker1}.
+with_ast(ArgList, Contents, Context, TreeWalker) ->
+ {ArgAstList, {ArgInfo, TreeWalker1}} = lists:mapfoldl(fun
+ ({{identifier, _, _LocalVarName}, Value}, {AstInfo1, TreeWalker1}) ->
+ {{Ast, Info}, TreeWalker2} = value_ast(Value, false, Context, TreeWalker1),
+ {Ast, {merge_info(AstInfo1, Info), TreeWalker2}}
+ end, {#ast_info{}, TreeWalker}, ArgList),
+
+ NewScope = lists:map(fun({{identifier, _, LocalVarName}, _Value}) ->
+ {LocalVarName, erl_syntax:variable(lists:concat(["Var_", LocalVarName]))}
+ end, ArgList),
+
+ {{InnerAst, InnerInfo}, TreeWalker2} = body_ast(Contents,
+ Context#dtl_context{local_scopes = [NewScope|Context#dtl_context.local_scopes]}, TreeWalker1),
+
+ {{erl_syntax:application(
+ erl_syntax:fun_expr([
+ erl_syntax:clause(lists:map(fun({_, Var}) -> Var end, NewScope), none,
+ [InnerAst])]), ArgAstList), merge_info(ArgInfo, InnerInfo)}, TreeWalker2}.
for_loop_ast(IteratorList, LoopValue, Contents, {EmptyContentsAst, EmptyContentsInfo}, Context, TreeWalker) ->
Vars = lists:map(fun({identifier, _, Iterator}) ->
@@ -864,6 +968,14 @@ now_ast(FormatString, _Context, TreeWalker) ->
[erl_syntax:string(UnescapeOuter)]),
#ast_info{}}, TreeWalker}.
+ssi_ast(FileName, Context, TreeWalker) ->
+ {{Ast, Info}, TreeWalker1} = value_ast(FileName, true, Context, TreeWalker),
+ {Mod, Fun} = Context#dtl_context.reader,
+ {{erl_syntax:application(
+ erl_syntax:atom(erlydtl_runtime),
+ erl_syntax:atom(read_file),
+ [erl_syntax:atom(Mod), erl_syntax:atom(Fun), Ast]), Info}, TreeWalker1}.
+
unescape_string_literal(String) ->
unescape_string_literal(string:strip(String, both, 34), [], noslash).
Oops, something went wrong.

0 comments on commit 7bd6d35

Please sign in to comment.