Permalink
Browse files

Zotonic: Basic boolean operators in "if" clause

Support for "and", "or", "==", and "/=" in if statements, as well as
arbitrary nesting of expressions. Adapted from Zotonic ErlyDTL.

Includes tests.
  • Loading branch information...
1 parent 48fea22 commit b96dd0bff41e1139c025922b8f56e2f7006617b2 @evanmiller evanmiller committed with Evan Miller May 21, 2010
@@ -325,17 +325,6 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
string_ast(String, TreeWalkerAcc);
({'trans', {string_literal, _Pos, FormatString}}, TreeWalkerAcc) ->
translated_ast(FormatString, Context, TreeWalkerAcc);
- ({'string_literal', _Pos, String}, TreeWalkerAcc) ->
- {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context),
- #ast_info{}}, TreeWalkerAcc};
- ({'number_literal', _Pos, Number}, TreeWalkerAcc) ->
- string_ast(Number, TreeWalkerAcc);
- ({'attribute', _} = Variable, TreeWalkerAcc) ->
- {Ast, VarName} = resolve_variable_ast(Variable, Context),
- {{format(Ast, Context), #ast_info{var_names = [VarName]}}, TreeWalkerAcc};
- ({'variable', _} = Variable, TreeWalkerAcc) ->
- {Ast, VarName} = resolve_variable_ast(Variable, Context),
- {{format(Ast, Context), #ast_info{var_names = [VarName]}}, TreeWalkerAcc};
({'include', {string_literal, _, File}}, TreeWalkerAcc) ->
include_ast(unescape_string_literal(File), Context, TreeWalkerAcc);
({'if', Expression, Contents}, TreeWalkerAcc) ->
@@ -346,24 +335,22 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
{IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifequal', Args, Contents}, TreeWalkerAcc) ->
+ ({'ifequal', [Arg1, Arg2], Contents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
- ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifequalelse', Args, IfContents, ElseContents}, TreeWalkerAcc) ->
+ ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ ({'ifequalelse', [Arg1, Arg2], IfContents, ElseContents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context,TreeWalker1),
- ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifnotequal', Args, Contents}, TreeWalkerAcc) ->
- {IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
- {ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
- ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifnotequalelse', Args, IfContents, ElseContents}, TreeWalkerAcc) ->
- {IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
- {ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
- ifequalelse_ast(Args, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'apply_filter', Variable, Filter}, TreeWalkerAcc) ->
- filter_ast(Variable, Filter, Context, TreeWalkerAcc);
+ ifelse_ast({'expr', "eq", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ ({'ifnotequal', [Arg1, Arg2], Contents}, TreeWalkerAcc) ->
+ {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
+ {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
+ ifelse_ast({'expr', "ne", Arg1, Arg2}, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ ({'ifnotequalelse', [Arg1, Arg2], IfContents, ElseContents}, TreeWalkerAcc) ->
+ {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) ->
for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalkerAcc);
({'load', Names}, TreeWalkerAcc) ->
@@ -379,7 +366,10 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
({'cycle', Names}, TreeWalkerAcc) ->
cycle_ast(Names, Context, TreeWalkerAcc);
({'cycle_compat', Names}, TreeWalkerAcc) ->
- cycle_compat_ast(Names, Context, TreeWalkerAcc)
+ cycle_compat_ast(Names, Context, TreeWalkerAcc);
+ (ValueToken, TreeWalkerAcc) ->
+ {{ValueAst,ValueInfo},ValueTreeWalker} = value_ast(ValueToken, true, Context, TreeWalkerAcc),
+ {{format(ValueAst, Context),ValueInfo},ValueTreeWalker}
end, TreeWalker, DjangoParseTree),
{AstList, {Info, TreeWalker3}} = lists:mapfoldl(
fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
@@ -410,6 +400,39 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
{{erl_syntax:list(AstList), Info}, TreeWalker3}.
+value_ast(ValueToken, AsString, Context, TreeWalker) ->
+ case ValueToken of
+ {'expr', Operator, Value} ->
+ {{ValueAst,InfoValue}, TreeWalker1} = value_ast(Value, false, Context, TreeWalker),
+ Ast = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
+ erl_syntax:atom(Operator),
+ [ValueAst]),
+ {{Ast, InfoValue}, TreeWalker1};
+ {'expr', Operator, Value1, Value2} ->
+ {{Value1Ast,InfoValue1}, TreeWalker1} = value_ast(Value1, false, Context, TreeWalker),
+ {{Value2Ast,InfoValue2}, TreeWalker2} = value_ast(Value2, false, Context, TreeWalker1),
+ Ast = erl_syntax:application(erl_syntax:atom(erlydtl_runtime),
+ erl_syntax:atom(Operator),
+ [Value1Ast, Value2Ast]),
+ {{Ast, merge_info(InfoValue1,InfoValue2)}, TreeWalker2};
+ {'string_literal', _Pos, String} ->
+ {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context),
+ #ast_info{}}, TreeWalker};
+ {'number_literal', _Pos, Number} ->
+ case AsString of
+ true -> string_ast(Number, TreeWalker);
+ false -> {{erl_syntax:integer(list_to_integer(Number)), #ast_info{}}, TreeWalker}
+ end;
+ {'apply_filter', Variable, Filter} ->
+ filter_ast(Variable, Filter, Context, TreeWalker);
+ {'attribute', _} = Variable ->
+ {Ast, VarName} = resolve_variable_ast(Variable, Context),
+ {{Ast, #ast_info{var_names = [VarName]}}, TreeWalker};
+ {'variable', _} = Variable ->
+ {Ast, VarName} = resolve_variable_ast(Variable, Context),
+ {{Ast, #ast_info{var_names = [VarName]}}, TreeWalker}
+ end.
+
merge_info(Info1, Info2) ->
#ast_info{dependencies =
lists:merge(
@@ -479,9 +502,9 @@ filter_ast(Variable, Filter, Context, TreeWalker) ->
end.
filter_ast_noescape(Variable, [{identifier, _, "escape"}], Context, TreeWalker) ->
- body_ast([Variable], Context, TreeWalker);
+ value_ast(Variable, true, Context, TreeWalker);
filter_ast_noescape(Variable, Filter, Context, TreeWalker) ->
- {{VariableAst, Info}, TreeWalker2} = body_ast([Variable], Context, TreeWalker),
+ {{VariableAst, Info}, TreeWalker2} = value_ast(Variable, true, Context, TreeWalker),
VarValue = filter_ast1(Filter, VariableAst),
{{VarValue, Info}, TreeWalker2}.
@@ -515,9 +538,6 @@ search_for_escape_filter(_Variable, _Filter) ->
resolve_variable_ast(VarTuple, Context) ->
resolve_variable_ast(VarTuple, Context, 'find_value').
-resolve_ifvariable_ast(VarTuple, Context) ->
- resolve_variable_ast(VarTuple, Context, 'find_value').
-
resolve_variable_ast({attribute, {{identifier, _, AttrName}, Variable}}, Context, FinderFunction) ->
{VarAst, VarName} = resolve_variable_ast(Variable, Context, FinderFunction),
{erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
@@ -541,22 +561,6 @@ resolve_variable_ast({apply_filter, Variable, Filter}, Context, FinderFunction)
resolve_variable_ast(What, _Context, _FinderFunction) ->
error_logger:error_msg("~p:resolve_variable_ast unhandled: ~p~n", [?MODULE, What]).
-resolve_multiple_ifvariable_ast(Args, Info, Context) ->
- lists:foldr(fun
- (X, {Asts, AccVarNames}) ->
- case X of
- {string_literal, _, Literal} ->
- {[erl_syntax:string(unescape_string_literal(Literal)) | Asts], AccVarNames};
- {number_literal, _, Literal} ->
- {[erl_syntax:integer(list_to_integer(Literal)) | Asts], AccVarNames};
- Variable ->
- {Ast, VarName} = resolve_ifvariable_ast(Variable, Context),
- {[Ast | Asts], [VarName | AccVarNames]}
- end
- end,
- {[], Info#ast_info.var_names},
- Args).
-
resolve_scoped_variable_ast(VarName, Context) ->
lists:foldl(fun(Scope, Value) ->
case Value of
@@ -595,39 +599,15 @@ firstof_ast(Vars, Context, TreeWalker) ->
{'ifelse', Var, [Var], [Acc]} end,
[], Vars)], Context, TreeWalker).
-ifelse_ast({'not', Expression}, IfAstInfo, ElseAstInfo, Context, TreeWalker) ->
- ifelse_ast(Expression, ElseAstInfo, IfAstInfo, Context, TreeWalker);
-ifelse_ast({'in', Variable1, Variable2}, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
+ifelse_ast(Expression, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
Info = merge_info(IfContentsInfo, ElseContentsInfo),
- {[Ast1, Ast2], VarNames} = resolve_multiple_ifvariable_ast([Variable1, Variable2], Info, Context),
- {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_in), [Ast1, Ast2]),
+ {{Ast, ExpressionInfo}, TreeWalker1} = value_ast(Expression, false, Context, TreeWalker),
+ {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_true), [Ast]),
[erl_syntax:clause([erl_syntax:atom(true)], none,
[IfContentsAst]),
erl_syntax:clause([erl_syntax:underscore()], none,
[ElseContentsAst])
- ]), Info#ast_info{var_names = VarNames}}, TreeWalker};
-ifelse_ast(Variable, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
- Info = merge_info(IfContentsInfo, ElseContentsInfo),
- VarNames = Info#ast_info.var_names,
- {Ast, VarName} = resolve_ifvariable_ast(Variable, Context),
- {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(is_false), [Ast]),
- [erl_syntax:clause([erl_syntax:atom(true)], none,
- [ElseContentsAst]),
- erl_syntax:clause([erl_syntax:underscore()], none,
- [IfContentsAst])
- ]), Info#ast_info{var_names = [VarName | VarNames]}}, TreeWalker}.
-
-
-ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
- Info = merge_info(IfContentsInfo, ElseContentsInfo),
- {[Arg1Ast, Arg2Ast], VarNames} = resolve_multiple_ifvariable_ast(Args, Info, Context),
- Ast = erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(are_equal),
- [Arg1Ast, Arg2Ast]),
- [
- erl_syntax:clause([erl_syntax:atom(true)], none, [IfContentsAst]),
- erl_syntax:clause([erl_syntax:underscore()], none, [ElseContentsAst])
- ]),
- {{Ast, Info#ast_info{var_names = VarNames}}, TreeWalker}.
+ ]), merge_info(ExpressionInfo, Info)}, TreeWalker1}.
for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalker) ->
@@ -312,6 +312,8 @@ weeks_in_year(Y) ->
D2 = calendar:day_of_the_week(Y, 12, 31),
if (D1 =:= 4 orelse D2 =:= 4) -> 53; true -> 52 end.
+utc_diff({Y, M, D}, Time) when Y < 1970->
+ utc_diff({1970, M, D}, Time);
utc_diff(Date, Time) ->
LTime = {Date, Time},
UTime = erlang:localtime_to_universaltime(LTime),
@@ -65,8 +65,6 @@
orelse C =:= $~ orelse C =:= $_))).
%% @doc Adds a number to the value.
-add([Input], Number) when is_list(Input) or is_binary(Input) ->
- add(Input, Number);
add(Input, Number) when is_binary(Input) ->
list_to_binary(add(binary_to_list(Input), Number));
add(Input, Number) when is_list(Input) ->
@@ -75,68 +73,52 @@ add(Input, Number) when is_integer(Input) ->
Input + Number.
%% @doc Capitalizes the first character of the value.
-capfirst([Input]) when is_list(Input) or is_binary (Input) ->
- capfirst(Input);
capfirst([H|T]) when H >= $a andalso H =< $z ->
[H + $A - $a | T];
capfirst(<<Byte:8/integer, Binary/binary>>) when Byte >= $a andalso Byte =< $z ->
- [<<(Byte + $A - $a)>>, Binary].
+ [(Byte + $A - $a)|binary_to_list(Binary)].
%% @doc Centers the value in a field of a given width.
-center([Input], Number) when is_list(Input) or is_binary(Input) ->
- center(Input, Number);
center(Input, Number) when is_binary(Input) ->
list_to_binary(center(binary_to_list(Input), Number));
center(Input, Number) when is_list(Input) ->
string:centre(Input, Number).
%% @doc Formats a date according to the given format.
-date([Input], FormatStr) when is_list(Input) or is_binary(Input) ->
- date(Input, FormatStr);
date(Input, FormatStr) when is_binary(Input) ->
list_to_binary(date(binary_to_list(Input), FormatStr));
-date([{{_,_,_} = Date,{_,_,_} = Time}], FormatStr) ->
+date({{_,_,_} = Date,{_,_,_} = Time}, FormatStr) ->
erlydtl_dateformat:format({Date, Time}, FormatStr);
-date([{_,_,_} = Date], FormatStr) ->
+date({_,_,_} = Date, FormatStr) ->
erlydtl_dateformat:format(Date, FormatStr);
date(Input, _FormatStr) when is_list(Input) ->
io:format("Unexpected date parameter : ~p~n", [Input]),
"".
-default_if_none([undefined], Default) ->
- Default;
default_if_none(undefined, Default) ->
Default;
default_if_none(Input, _) ->
Input.
%% @doc Escapes characters for use in JavaScript strings.
-escapejs([Input]) when is_list(Input) or is_binary(Input) ->
- escapejs(Input);
escapejs(Input) when is_binary(Input) ->
escapejs(Input, 0);
escapejs(Input) when is_list(Input) ->
escapejs(Input, []).
%% @doc Returns the first item in a list.
-first([Input]) when is_list(Input) or is_binary(Input) ->
- first(Input);
first([First|_Rest]) ->
[First];
first(<<First, _/binary>>) ->
<<First>>.
%% @doc Replaces ampersands with &amp; entities.
-fix_ampersands([Input]) when is_list(Input) or is_binary(Input) ->
- fix_ampersands(Input);
fix_ampersands(Input) when is_binary(Input) ->
fix_ampersands(Input, 0);
fix_ampersands(Input) when is_list(Input) ->
fix_ampersands(Input, []).
%% @doc Applies HTML escaping to a string.
-force_escape([Input]) when is_list(Input) or is_binary(Input) ->
- force_escape(Input);
force_escape(Input) when is_list(Input) ->
escape(Input, []);
force_escape(Input) when is_binary(Input) ->
@@ -157,12 +139,10 @@ format_number(Input) ->
Input.
%% @doc Joins a list with a given separator.
-join([Input], Separator) when is_list(Input) ->
+join(Input, Separator) when is_list(Input) ->
join_io(Input, Separator).
%% @doc Returns the last item in a list.
-last([Input]) when is_list(Input) or is_binary(Input) ->
- last(Input);
last(Input) when is_binary(Input) ->
case size(Input) of
0 -> Input;
@@ -175,10 +155,9 @@ last(Input) when is_list(Input) ->
[lists:last(Input)].
%% @doc Returns the length of the value.
-length([]) -> "0";
-length([Input]) when is_list(Input) ->
+length(Input) when is_list(Input) ->
integer_to_list(erlang:length(Input));
-length([Input]) when is_binary(Input) ->
+length(Input) when is_binary(Input) ->
integer_to_list(size(Input)).
%% @doc Returns True iff the value's length is the argument.
@@ -188,40 +167,30 @@ length_is(Input, Number) when is_list(Input), is_list(Number) ->
?MODULE:length(Input) =:= Number.
%% @doc Converts all newlines to HTML line breaks.
-linebreaksbr([Input]) when is_list(Input) or is_binary(Input) ->
- linebreaksbr(Input);
linebreaksbr(Input) when is_binary(Input) ->
linebreaksbr(Input, 0);
linebreaksbr(Input) ->
linebreaksbr(Input, []).
%% @doc Left-aligns the value in a field of a given width.
-ljust([Input], Number) when is_list(Input) or is_binary(Input) ->
- ljust(Input, Number);
ljust(Input, Number) when is_binary(Input) ->
list_to_binary(ljust(binary_to_list(Input), Number));
ljust(Input, Number) when is_list(Input) ->
string:left(Input, Number).
%% @doc Converts a string into all lowercase.
-lower([Input]) when is_list(Input) or is_binary(Input) ->
- lower(Input);
lower(Input) when is_binary(Input) ->
lower(Input, 0);
lower(Input) ->
string:to_lower(Input).
%% @doc Right-aligns the value in a field of a given width.
-rjust([Input], Number) when is_list(Input) or is_binary(Input) ->
- rjust(Input, Number);
rjust(Input, Number) when is_binary(Input) ->
list_to_binary(rjust(binary_to_list(Input), Number));
rjust(Input, Number) ->
string:right(Input, Number).
%% @doc Truncates a string after a certain number of words.
-truncatewords([Input], Max) when is_list(Input) or is_binary(Input) ->
- truncatewords(Input, Max);
truncatewords(Input, Max) when is_binary(Input) ->
list_to_binary(truncatewords(binary_to_list(Input), Max));
truncatewords(_Input, Max) when Max =< 0 ->
@@ -230,16 +199,12 @@ truncatewords(Input, Max) ->
truncatewords(Input, Max, []).
%% @doc Converts a string into all uppercase.
-upper([Input]) when is_list(Input) or is_binary(Input) ->
- upper(Input);
upper(Input) when is_binary(Input) ->
list_to_binary(upper(binary_to_list(Input)));
upper(Input) ->
string:to_upper(Input).
%% @doc Escapes a value for use in a URL.
-urlencode([Input]) when is_list(Input) or is_binary(Input) ->
- urlencode(Input);
urlencode(Input) when is_binary(Input) ->
urlencode(Input, 0);
urlencode(Input) when is_list(Input) ->
Oops, something went wrong.

0 comments on commit b96dd0b

Please sign in to comment.