Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

1. Can resolve variables from function calls to parameterized modules…

…, e.g.

    -module(foo, [Var]).
    get_var() -> Var.
    ...
    render([{var1, foo:new("bar")}]).
    ...
    {{ var1.get_var }} => <<"bar">>

2. Support for recursive variable attributes, e.g. {{ var.attr.attr.attr }}

Tests added for both.
  • Loading branch information...
commit b4bf177fc25b580e69512c6941d1f46f4ed79b5d 1 parent dac49f3
@evanmiller evanmiller authored
View
70 src/erlydtl/erlydtl_compiler.erl
@@ -299,34 +299,39 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
({'comment', _Contents}, TreeWalkerAcc) ->
empty_ast(TreeWalkerAcc);
({'autoescape', {identifier, _, OnOrOff}, Contents}, TreeWalkerAcc) ->
- body_ast(Contents, Context#dtl_context{auto_escape = list_to_atom(OnOrOff)}, TreeWalkerAcc);
+ body_ast(Contents, Context#dtl_context{auto_escape = list_to_atom(OnOrOff)},
+ TreeWalkerAcc);
({'text', _Pos, String}, TreeWalkerAcc) ->
string_ast(String, TreeWalkerAcc);
({'string_literal', _Pos, String}, TreeWalkerAcc) ->
- {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context), #ast_info{}}, TreeWalkerAcc};
+ {{auto_escape(erl_syntax:string(unescape_string_literal(String)), Context),
+ #ast_info{}}, TreeWalkerAcc};
({'number_literal', _Pos, Number}, TreeWalkerAcc) ->
string_ast(Number, TreeWalkerAcc);
- ({'variable', Variable}, 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', {variable, Variable}, Contents}, TreeWalkerAcc) ->
- {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
- {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
- ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'if', {'not', {variable, Variable}}, Contents}, TreeWalkerAcc) ->
+ ({'if', {'not', Variable}, Contents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = empty_ast(TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(Contents, Context, TreeWalker1),
ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifelse', {variable, Variable}, IfContents, ElseContents}, TreeWalkerAcc) ->
- {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
- {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
+ ({'if', Variable, Contents}, TreeWalkerAcc) ->
+ {IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
+ {ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifelse', {'not', {variable, Variable}}, IfContents, ElseContents}, TreeWalkerAcc) ->
+ ({'ifelse', {'not', Variable}, IfContents, ElseContents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(ElseContents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(IfContents, Context, TreeWalker1),
ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ ({'ifelse', Variable, IfContents, ElseContents}, TreeWalkerAcc) ->
+ {IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
+ {ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
+ ifelse_ast(Variable, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
({'ifequal', Args, Contents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
@@ -353,8 +358,8 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
tag_ast(Name, Args, Context, TreeWalkerAcc);
({'call', {'identifier', _, Name}}, TreeWalkerAcc) ->
call_ast(Name, TreeWalkerAcc);
- ({'call', {'identifier', _, Name}, {variable, With}}, TreeWalkerAcc) ->
- call_with_ast(Name, With, Context, TreeWalkerAcc)
+ ({'call', {'identifier', _, Name}, With}, TreeWalkerAcc) ->
+ call_with_ast(Name, With, Context, TreeWalkerAcc)
end, TreeWalker, DjangoParseTree),
{AstList, {Info, TreeWalker3}} = lists:mapfoldl(
fun({Ast, Info}, {InfoAcc, TreeWalkerAcc}) ->
@@ -481,33 +486,26 @@ resolve_variable_ast(VarTuple, Context) ->
resolve_ifvariable_ast(VarTuple, Context) ->
resolve_variable_ast(VarTuple, Context, 'find_value').
-resolve_variable_ast({{identifier, _, VarName}}, Context, FinderFunction) ->
- {resolve_variable_name_ast(VarName, Context, FinderFunction), VarName};
-
-resolve_variable_ast({{identifier, _, VarName}, {identifier, _, AttrName}}, Context, FinderFunction) ->
+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),
- [erl_syntax:atom(AttrName), resolve_variable_name_ast(VarName, Context)]), VarName}.
+ [erl_syntax:atom(AttrName), VarAst]), VarName};
-resolve_variable_name_ast(VarName, Context) ->
- resolve_variable_name_ast(VarName, Context, 'fetch_value').
-
-resolve_variable_name_ast(VarName, Context, FinderFunction) ->
+resolve_variable_ast({variable, {identifier, _, VarName}}, Context, FinderFunction) ->
VarValue = lists:foldl(fun(Scope, Value) ->
case Value of
- undefined ->
- proplists:get_value(list_to_atom(VarName), Scope);
- _ ->
- Value
+ undefined -> proplists:get_value(list_to_atom(VarName), Scope);
+ _ -> Value
end
end, undefined, Context#dtl_context.local_scopes),
- case VarValue of
+ VarValue1 = case VarValue of
undefined ->
erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(FinderFunction),
[erl_syntax:atom(VarName), erl_syntax:variable("Variables")]);
_ ->
VarValue
- end.
-
+ end,
+ {VarValue1, VarName}.
format(Ast, Context) ->
auto_escape(format_integer_ast(Ast), Context).
@@ -545,13 +543,13 @@ ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCon
{[Arg1Ast, Arg2Ast], VarNames} = lists:foldl(fun
(X, {Asts, AccVarNames}) ->
case X of
- {variable, Var} ->
- {Ast, VarName} = resolve_ifvariable_ast(Var, Context),
- {[Ast | Asts], [VarName | 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}
+ {[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},
@@ -565,7 +563,7 @@ ifequalelse_ast(Args, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseCon
{{Ast, Info#ast_info{var_names = VarNames}}, TreeWalker}.
-for_loop_ast(IteratorList, {variable, Variable}, Contents, Context, TreeWalker) ->
+for_loop_ast(IteratorList, Variable, Contents, Context, TreeWalker) ->
Vars = lists:map(fun({identifier, _, Iterator}) ->
erl_syntax:variable("Var_" ++ Iterator)
end, IteratorList),
@@ -641,7 +639,7 @@ tag_ast(Name, Args, Context, TreeWalker) ->
InterpretedArgs = lists:map(fun
({{identifier, _, Key}, {string_literal, _, Value}}) ->
{list_to_atom(Key), erl_syntax:string(unescape_string_literal(Value))};
- ({{identifier, _, Key}, {variable, Value}}) ->
+ ({{identifier, _, Key}, Value}) ->
{list_to_atom(Key), format(resolve_variable_ast(Value, Context), Context)}
end, Args),
DefaultFilePath = filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags", Name]),
View
33 src/erlydtl/erlydtl_parser.yrl
@@ -36,7 +36,7 @@ Nonterminals
Elements
Literal
- VariableBraced
+ ValueBraced
ExtendsTag
IncludeTag
@@ -75,6 +75,7 @@ Nonterminals
AutoEscapeBraced
EndAutoEscapeBraced
+ Value
Variable
Filter
@@ -129,7 +130,7 @@ Rootsymbol
Elements -> '$empty' : [].
Elements -> Elements text : '$1' ++ ['$2'].
-Elements -> Elements VariableBraced : '$1' ++ ['$2'].
+Elements -> Elements ValueBraced : '$1' ++ ['$2'].
Elements -> Elements ExtendsTag : '$1' ++ ['$2'].
Elements -> Elements IncludeTag : '$1' ++ ['$2'].
Elements -> Elements LoadTag : '$1' ++ ['$2'].
@@ -144,13 +145,14 @@ Elements -> Elements CustomTag : '$1' ++ ['$2'].
Elements -> Elements CallTag : '$1' ++ ['$2'].
Elements -> Elements CallWithTag : '$1' ++ ['$2'].
-VariableBraced -> open_var Variable close_var : '$2'.
+ValueBraced -> open_var Value close_var : '$2'.
-Variable -> Variable pipe Filter : {apply_filter, '$1', '$3'}.
-Variable -> identifier : {variable, {'$1'}}.
-Variable -> identifier dot identifier : {variable, {'$1', '$3'}}.
-Variable -> string_literal : '$1'.
-Variable -> number_literal : '$1'.
+Value -> Value pipe Filter : {apply_filter, '$1', '$3'}.
+Value -> Variable : '$1'.
+Value -> Literal : '$1'.
+
+Variable -> identifier : {variable, '$1'}.
+Variable -> Value dot identifier : {attribute, {'$3', '$1'}}.
ExtendsTag -> open_tag extends_keyword string_literal close_tag : {extends, '$3'}.
IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3'}.
@@ -170,7 +172,6 @@ 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 -> Variable : '$1'.
ForExpression -> ForGroup in_keyword Variable : {'in', '$1', '$3'}.
ForGroup -> identifier : ['$1'].
ForGroup -> ForGroup comma identifier : '$1' ++ ['$3'].
@@ -179,21 +180,21 @@ IfBlock -> IfBraced Elements ElseBraced Elements EndIfBraced : {ifelse, '$1', '$
IfBlock -> IfBraced Elements EndIfBraced : {'if', '$1', '$2'}.
IfBraced -> open_tag if_keyword IfExpression close_tag : '$3'.
IfExpression -> not_keyword IfExpression : {'not', '$2'}.
-IfExpression -> Variable : '$1'.
+IfExpression -> Value : '$1'.
ElseBraced -> open_tag else_keyword close_tag.
EndIfBraced -> open_tag endif_keyword close_tag.
IfEqualBlock -> IfEqualBraced Elements ElseBraced Elements EndIfEqualBraced : {ifequalelse, '$1', '$2', '$4'}.
IfEqualBlock -> IfEqualBraced Elements EndIfEqualBraced : {ifequal, '$1', '$2'}.
-IfEqualBraced -> open_tag ifequal_keyword IfEqualExpression Variable close_tag : ['$3', '$4'].
-IfEqualExpression -> Variable : '$1'.
+IfEqualBraced -> open_tag ifequal_keyword IfEqualExpression Value close_tag : ['$3', '$4'].
+IfEqualExpression -> Value : '$1'.
EndIfEqualBraced -> open_tag endifequal_keyword close_tag.
IfNotEqualBlock -> IfNotEqualBraced Elements ElseBraced Elements EndIfNotEqualBraced : {ifnotequalelse, '$1', '$2', '$4'}.
IfNotEqualBlock -> IfNotEqualBraced Elements EndIfNotEqualBraced : {ifnotequal, '$1', '$2'}.
-IfNotEqualBraced -> open_tag ifnotequal_keyword IfNotEqualExpression Variable close_tag : ['$3', '$4'].
-IfNotEqualExpression -> Variable : '$1'.
+IfNotEqualBraced -> open_tag ifnotequal_keyword IfNotEqualExpression Value close_tag : ['$3', '$4'].
+IfNotEqualExpression -> Value : '$1'.
EndIfNotEqualBraced -> open_tag endifnotequal_keyword close_tag.
AutoEscapeBlock -> AutoEscapeBraced Elements EndAutoEscapeBraced : {autoescape, '$1', '$2'}.
@@ -209,7 +210,7 @@ Literal -> number_literal : '$1'.
CustomTag -> open_tag identifier Args close_tag : {tag, '$2', '$3'}.
Args -> '$empty' : [].
-Args -> Args identifier equal Variable : '$1' ++ [{'$2', '$4'}].
+Args -> Args identifier equal Value : '$1' ++ [{'$2', '$4'}].
CallTag -> open_tag call_keyword identifier close_tag : {call, '$3'}.
-CallWithTag -> open_tag call_keyword identifier with_keyword Variable close_tag: {call, '$3', '$5'}.
+CallWithTag -> open_tag call_keyword identifier with_keyword Value close_tag : {call, '$3', '$5'}.
View
25 src/erlydtl/erlydtl_runtime.erl
@@ -4,19 +4,30 @@
find_value(Key, L) when is_list(L) ->
proplists:get_value(Key, L);
-find_value(Key, {GBSize, GBData}) ->
+find_value(Key, {GBSize, GBData}) when is_integer(GBSize) ->
case gb_trees:lookup(Key, {GBSize, GBData}) of
{value, Val} ->
Val;
_ ->
undefined
end;
-find_value(Key, Dict) ->
- case dict:find(Key, Dict) of
- {ok, Val} ->
- Val;
- _ ->
- undefined
+find_value(Key, Tuple) when is_tuple(Tuple) ->
+ Module = element(1, Tuple),
+ case Module of
+ dict ->
+ case dict:find(Key, Tuple) of
+ {ok, Val} ->
+ Val;
+ _ ->
+ undefined
+ end;
+ Module ->
+ case proplists:get_value(Key, Module:module_info(exports)) of
+ 1 ->
+ Tuple:Key();
+ _ ->
+ undefined
+ end
end.
fetch_value(Key, Data) ->
View
5 src/tests/erlydtl_example_variable_storage.erl
@@ -0,0 +1,5 @@
+-module(erlydtl_example_variable_storage, [SomeVar]).
+-compile(export_all).
+
+some_var() ->
+ SomeVar.
View
10 src/tests/erlydtl_unittests.erl
@@ -42,7 +42,12 @@ tests() ->
{"Render variable with attribute in dict",
<<"{{ var1.attr }}">>, [{var1, dict:store(attr, "Othello", dict:new())}], <<"Othello">>},
{"Render variable with attribute in gb_tree",
- <<"{{ var1.attr }}">>, [{var1, gb_trees:insert(attr, "Othello", gb_trees:empty())}], <<"Othello">>}
+ <<"{{ var1.attr }}">>, [{var1, gb_trees:insert(attr, "Othello", gb_trees:empty())}], <<"Othello">>},
+ {"Render variable in parameterized module",
+ <<"{{ var1.some_var }}">>, [{var1, erlydtl_example_variable_storage:new("foo")}], <<"foo">>},
+ {"Nested attributes",
+ <<"{{ person.city.state.country }}">>, [{person, [{city, [{state, [{country, "Italy"}]}]}]}],
+ <<"Italy">>}
]},
{"if", [
{"If/else",
@@ -77,6 +82,9 @@ tests() ->
{"Resolve variable attribute",
<<"{% for number in person.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{numbers, ["411", "911"]}]}],
<<"411\n911\n">>},
+ {"Resolve nested variable attribute",
+ <<"{% for number in person.home.numbers %}{{ number }}\n{% endfor %}">>, [{person, [{home, [{numbers, ["411", "911"]}]}]}],
+ <<"411\n911\n">>},
{"Nested for loop",
<<"{% for outer in list %}{% for inner in outer %}{{ inner }}\n{% endfor %}{% endfor %}">>,
[{'list', [["Al", "Albert"], ["Jo", "Joseph"]]}],
Please sign in to comment.
Something went wrong with that request. Please try again.