|
@@ -38,7 +38,7 @@
|
|
|
%% --------------------------------------------------------------------
|
|
|
%% Definitions
|
|
|
%% --------------------------------------------------------------------
|
|
|
--export([compile/2, compile/3, parse/1]).
|
|
|
+-export([compile/2, compile/3, compile_dir/2, compile_dir/3, parse/1]).
|
|
|
|
|
|
-record(dtl_context, {
|
|
|
local_scopes = [],
|
|
@@ -48,6 +48,7 @@
|
|
|
parse_trail = [],
|
|
|
vars = [],
|
|
|
custom_tags_dir = [],
|
|
|
+ custom_tags_module = none,
|
|
|
reader = {file, read_file},
|
|
|
module = [],
|
|
|
compiler_options = [verbose, report_errors],
|
|
@@ -62,7 +63,6 @@
|
|
|
pre_render_asts = []}).
|
|
|
|
|
|
-record(treewalker, {
|
|
|
-% custom_tags = [],
|
|
|
counter = 0
|
|
|
}).
|
|
|
|
|
@@ -96,18 +96,7 @@ compile(File, Module, Options) ->
|
|
|
{ok, DjangoParseTree, CheckSum} ->
|
|
|
case compile_to_binary(File, DjangoParseTree, Context, CheckSum) of
|
|
|
{ok, Module1, Bin} ->
|
|
|
- case proplists:get_value(out_dir, Options) of
|
|
|
- undefined ->
|
|
|
- ok;
|
|
|
- OutDir ->
|
|
|
- BeamFile = filename:join([OutDir, atom_to_list(Module1) ++ ".beam"]),
|
|
|
- case file:write_file(BeamFile, Bin) of
|
|
|
- ok ->
|
|
|
- ok;
|
|
|
- {error, Reason} ->
|
|
|
- {error, lists:concat(["beam generation failed (", Reason, "): ", BeamFile])}
|
|
|
- end
|
|
|
- end;
|
|
|
+ write_binary(Module1, Bin, Options);
|
|
|
Err ->
|
|
|
Err
|
|
|
end;
|
|
@@ -116,34 +105,99 @@ compile(File, Module, Options) ->
|
|
|
end.
|
|
|
|
|
|
|
|
|
+compile_dir(Dir, Module) ->
|
|
|
+ compile_dir(Dir, Module, []).
|
|
|
+
|
|
|
+compile_dir(Dir, Module, Options) ->
|
|
|
+ Context = init_dtl_context_dir(Dir, Module, Options),
|
|
|
+ Files = case file:list_dir(Dir) of
|
|
|
+ {ok, FileList} -> FileList;
|
|
|
+ _ -> []
|
|
|
+ end,
|
|
|
+ {ParserResults, ParserErrors} = lists:foldl(fun
|
|
|
+ ("."++_, Acc) -> Acc;
|
|
|
+ (File, {ResultAcc, ErrorAcc}) ->
|
|
|
+ FilePath = filename:join([Dir, File]),
|
|
|
+ case parse(FilePath, Context) of
|
|
|
+ ok -> {ResultAcc, ErrorAcc};
|
|
|
+ {ok, DjangoParseTree, CheckSum} -> {[{File, DjangoParseTree, CheckSum}|ResultAcc], ErrorAcc};
|
|
|
+ Err -> {ResultAcc, [Err|ErrorAcc]}
|
|
|
+ end
|
|
|
+ end, {[], []}, Files),
|
|
|
+ case ParserErrors of
|
|
|
+ [] ->
|
|
|
+ case compile_multiple_to_binary(Dir, ParserResults, Context) of
|
|
|
+ {ok, Module1, Bin} ->
|
|
|
+ write_binary(Module1, Bin, Options);
|
|
|
+ Err ->
|
|
|
+ Err
|
|
|
+ end;
|
|
|
+ [Error|_] ->
|
|
|
+ Error
|
|
|
+ end.
|
|
|
+
|
|
|
%%====================================================================
|
|
|
%% Internal functions
|
|
|
%%====================================================================
|
|
|
|
|
|
+write_binary(Module1, Bin, Options) ->
|
|
|
+ case proplists:get_value(out_dir, Options) of
|
|
|
+ undefined ->
|
|
|
+ ok;
|
|
|
+ OutDir ->
|
|
|
+ BeamFile = filename:join([OutDir, atom_to_list(Module1) ++ ".beam"]),
|
|
|
+ case file:write_file(BeamFile, Bin) of
|
|
|
+ ok ->
|
|
|
+ ok;
|
|
|
+ {error, Reason} ->
|
|
|
+ {error, lists:concat(["beam generation failed (", Reason, "): ", BeamFile])}
|
|
|
+ end
|
|
|
+ end.
|
|
|
+
|
|
|
+compile_multiple_to_binary(Dir, ParserResults, Context) ->
|
|
|
+ {Functions, {AstInfo, _}} = lists:mapfoldl(fun({File, DjangoParseTree, CheckSum}, {AstInfo, TreeWalker}) ->
|
|
|
+ FilePath = full_path(File, Context#dtl_context.doc_root),
|
|
|
+ {{BodyAst, BodyInfo}, TreeWalker1} = with_dependency({FilePath, CheckSum}, body_ast(DjangoParseTree, Context, TreeWalker)),
|
|
|
+ FunctionName = filename:rootname(filename:basename(File)),
|
|
|
+ Function1 = erl_syntax:function(erl_syntax:atom(FunctionName),
|
|
|
+ [erl_syntax:clause([erl_syntax:variable("Variables")], none,
|
|
|
+ [erl_syntax:application(none, erl_syntax:atom(FunctionName),
|
|
|
+ [erl_syntax:variable("Variables"), erl_syntax:atom(none)])])]),
|
|
|
+ Function2 = erl_syntax:function(erl_syntax:atom(FunctionName),
|
|
|
+ [erl_syntax:clause([erl_syntax:variable("Variables"), erl_syntax:variable("TranslationFun")], none,
|
|
|
+ [BodyAst])]),
|
|
|
+ {{FunctionName, Function1, Function2}, {merge_info(AstInfo, BodyInfo), TreeWalker1}}
|
|
|
+ end, {#ast_info{}, #treewalker{}}, ParserResults),
|
|
|
+ Forms = custom_forms(Dir, Context#dtl_context.module, Functions, AstInfo),
|
|
|
+ compile_forms_and_reload(Dir, Forms, Context#dtl_context.compiler_options).
|
|
|
+
|
|
|
compile_to_binary(File, DjangoParseTree, Context, CheckSum) ->
|
|
|
try body_ast(DjangoParseTree, Context, #treewalker{}) of
|
|
|
{{BodyAst, BodyInfo}, BodyTreeWalker} ->
|
|
|
try custom_tags_ast(BodyInfo#ast_info.custom_tags, Context, BodyTreeWalker) of
|
|
|
{{CustomTagsAst, CustomTagsInfo}, _} ->
|
|
|
- case compile:forms(forms(File, Context#dtl_context.module, {BodyAst, BodyInfo}, {CustomTagsAst, CustomTagsInfo}, CheckSum),
|
|
|
- Context#dtl_context.compiler_options) of
|
|
|
- {ok, Module1, Bin} ->
|
|
|
- code:purge(Module1),
|
|
|
- case code:load_binary(Module1, atom_to_list(Module1) ++ ".erl", Bin) of
|
|
|
- {module, _} -> {ok, Module1, Bin};
|
|
|
- _ -> {error, lists:concat(["code reload failed: ", Module1])}
|
|
|
- end;
|
|
|
- error ->
|
|
|
- {error, lists:concat(["compilation failed: ", File])};
|
|
|
- OtherError ->
|
|
|
- OtherError
|
|
|
- end
|
|
|
+ Forms = forms(File, Context#dtl_context.module, {BodyAst, BodyInfo}, {CustomTagsAst, CustomTagsInfo}, CheckSum),
|
|
|
+ compile_forms_and_reload(File, Forms, Context#dtl_context.compiler_options)
|
|
|
catch
|
|
|
throw:Error -> Error
|
|
|
end
|
|
|
catch
|
|
|
throw:Error -> Error
|
|
|
end.
|
|
|
+
|
|
|
+compile_forms_and_reload(File, Forms, CompilerOptions) ->
|
|
|
+ case compile:forms(Forms, CompilerOptions) of
|
|
|
+ {ok, Module1, Bin} ->
|
|
|
+ code:purge(Module1),
|
|
|
+ case code:load_binary(Module1, atom_to_list(Module1) ++ ".erl", Bin) of
|
|
|
+ {module, _} -> {ok, Module1, Bin};
|
|
|
+ _ -> {error, lists:concat(["code reload failed: ", Module1])}
|
|
|
+ end;
|
|
|
+ error ->
|
|
|
+ {error, lists:concat(["compilation failed: ", File])};
|
|
|
+ OtherError ->
|
|
|
+ OtherError
|
|
|
+ end.
|
|
|
|
|
|
init_dtl_context(File, Module, Options) when is_list(Module) ->
|
|
|
init_dtl_context(File, list_to_atom(Module), Options);
|
|
@@ -154,6 +208,23 @@ init_dtl_context(File, Module, Options) ->
|
|
|
module = Module,
|
|
|
doc_root = proplists:get_value(doc_root, Options, filename:dirname(File)),
|
|
|
custom_tags_dir = proplists:get_value(custom_tags_dir, Options, filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags"])),
|
|
|
+ custom_tags_module = proplists:get_value(custom_tags_module, Options, Ctx#dtl_context.custom_tags_module),
|
|
|
+ vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
|
|
|
+ reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
|
|
|
+ compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
|
|
|
+ force_recompile = proplists:get_value(force_recompile, Options, Ctx#dtl_context.force_recompile),
|
|
|
+ locale = proplists:get_value(locale, Options, Ctx#dtl_context.locale)}.
|
|
|
+
|
|
|
+init_dtl_context_dir(Dir, Module, Options) when is_list(Module) ->
|
|
|
+ init_dtl_context_dir(Dir, list_to_atom(Module), Options);
|
|
|
+init_dtl_context_dir(Dir, Module, Options) ->
|
|
|
+ Ctx = #dtl_context{},
|
|
|
+ #dtl_context{
|
|
|
+ parse_trail = [],
|
|
|
+ module = Module,
|
|
|
+ doc_root = proplists:get_value(doc_root, Options, Dir),
|
|
|
+ custom_tags_dir = proplists:get_value(custom_tags_dir, Options, filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags"])),
|
|
|
+ custom_tags_module = proplists:get_value(custom_tags_module, Options, Module),
|
|
|
vars = proplists:get_value(vars, Options, Ctx#dtl_context.vars),
|
|
|
reader = proplists:get_value(reader, Options, Ctx#dtl_context.reader),
|
|
|
compiler_options = proplists:get_value(compiler_options, Options, Ctx#dtl_context.compiler_options),
|
|
@@ -249,7 +320,7 @@ custom_tags_clauses_ast1([Tag|CustomTags], ExcludeTags, ClauseAcc, InfoAcc, Cont
|
|
|
true ->
|
|
|
custom_tags_clauses_ast1(CustomTags, ExcludeTags, ClauseAcc, InfoAcc, Context, TreeWalker);
|
|
|
false ->
|
|
|
- CustomTagFile = filename:join([Context#dtl_context.custom_tags_dir, Tag]),
|
|
|
+ CustomTagFile = full_path(Tag, Context#dtl_context.custom_tags_dir),
|
|
|
case parse(CustomTagFile, Context) of
|
|
|
{ok, DjangoParseTree, CheckSum} ->
|
|
|
{{BodyAst, BodyAstInfo}, TreeWalker1} = with_dependency({CustomTagFile, CheckSum},
|
|
@@ -262,7 +333,43 @@ custom_tags_clauses_ast1([Tag|CustomTags], ExcludeTags, ClauseAcc, InfoAcc, Cont
|
|
|
throw(Error)
|
|
|
end
|
|
|
end.
|
|
|
-
|
|
|
+
|
|
|
+dependencies_function(Dependencies) ->
|
|
|
+ erl_syntax:function(
|
|
|
+ erl_syntax:atom(dependencies), [erl_syntax:clause([], none,
|
|
|
+ [erl_syntax:list(lists:map(fun
|
|
|
+ ({XFile, XCheckSum}) ->
|
|
|
+ erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
|
|
|
+ end, Dependencies))])]).
|
|
|
+
|
|
|
+translatable_strings_function(TranslatableStrings) ->
|
|
|
+ erl_syntax:function(
|
|
|
+ erl_syntax:atom(translatable_strings), [erl_syntax:clause([], none,
|
|
|
+ [erl_syntax:list(lists:map(fun(String) -> erl_syntax:string(String) end,
|
|
|
+ TranslatableStrings))])]).
|
|
|
+
|
|
|
+custom_forms(Dir, Module, Functions, AstInfo) ->
|
|
|
+ ModuleAst = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
|
|
|
+ ExportAst = erl_syntax:attribute(erl_syntax:atom(export),
|
|
|
+ [erl_syntax:list([
|
|
|
+ erl_syntax:arity_qualifier(erl_syntax:atom(source_dir), erl_syntax:integer(0)),
|
|
|
+ erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0)),
|
|
|
+ erl_syntax:arity_qualifier(erl_syntax:atom(translatable_strings), erl_syntax:integer(0))
|
|
|
+ |
|
|
|
+ lists:foldl(fun({FunctionName, _, _}, Acc) ->
|
|
|
+ [erl_syntax:arity_qualifier(erl_syntax:atom(FunctionName), erl_syntax:integer(1)),
|
|
|
+ erl_syntax:arity_qualifier(erl_syntax:atom(FunctionName), erl_syntax:integer(2))|Acc]
|
|
|
+ end, [], Functions)]
|
|
|
+ )]),
|
|
|
+ SourceFunctionAst = erl_syntax:function(
|
|
|
+ erl_syntax:atom(source_dir), [erl_syntax:clause([], none, [erl_syntax:string(Dir)])]),
|
|
|
+ DependenciesFunctionAst = dependencies_function(AstInfo#ast_info.dependencies),
|
|
|
+ TranslatableStringsFunctionAst = translatable_strings_function(AstInfo#ast_info.translatable_strings),
|
|
|
+ FunctionAsts = lists:foldl(fun({_, Function1, Function2}, Acc) -> [Function1, Function2 | Acc] end, [], Functions),
|
|
|
+
|
|
|
+ [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, SourceFunctionAst, DependenciesFunctionAst, TranslatableStringsFunctionAst
|
|
|
+ | FunctionAsts]].
|
|
|
+
|
|
|
forms(File, Module, {BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}, CheckSum) ->
|
|
|
MergedInfo = merge_info(BodyInfo, CustomTagsInfo),
|
|
|
Render0FunctionAst = erl_syntax:function(erl_syntax:atom(render),
|
|
@@ -290,17 +397,9 @@ forms(File, Module, {BodyAst, BodyInfo}, {CustomTagsFunctionAst, CustomTagsInfo}
|
|
|
erl_syntax:atom(source),
|
|
|
[erl_syntax:clause([], none, [SourceFunctionTuple])]),
|
|
|
|
|
|
- DependenciesFunctionAst = erl_syntax:function(
|
|
|
- erl_syntax:atom(dependencies), [erl_syntax:clause([], none,
|
|
|
- [erl_syntax:list(lists:map(fun
|
|
|
- ({XFile, XCheckSum}) ->
|
|
|
- erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
|
|
|
- end, MergedInfo#ast_info.dependencies))])]),
|
|
|
+ DependenciesFunctionAst = dependencies_function(MergedInfo#ast_info.dependencies),
|
|
|
|
|
|
- TranslatableStringsAst = erl_syntax:function(
|
|
|
- erl_syntax:atom(translatable_strings), [erl_syntax:clause([], none,
|
|
|
- [erl_syntax:list(lists:map(fun(String) -> erl_syntax:string(String) end,
|
|
|
- MergedInfo#ast_info.translatable_strings))])]),
|
|
|
+ TranslatableStringsAst = translatable_strings_function(MergedInfo#ast_info.translatable_strings),
|
|
|
|
|
|
BodyAstTmp = erl_syntax:application(
|
|
|
erl_syntax:atom(erlydtl_runtime),
|
|
@@ -790,15 +889,23 @@ full_path(File, DocRoot) ->
|
|
|
|
|
|
tag_ast(Name, Args, Context, TreeWalker) ->
|
|
|
{InterpretedArgs, AstInfo} = lists:mapfoldl(fun
|
|
|
- ({{identifier, _, Key}, {string_literal, _, Value}}, AstInfoAcc) ->
|
|
|
- {erl_syntax:tuple([erl_syntax:string(Key), erl_syntax:string(unescape_string_literal(Value))]), AstInfoAcc};
|
|
|
- ({{identifier, _, Key}, Value}, AstInfoAcc) ->
|
|
|
- {AST, AstInfo1} = resolve_variable_ast(Value, Context),
|
|
|
- {erl_syntax:tuple([erl_syntax:string(Key), format(AST,Context)]), merge_info(AstInfo1, AstInfoAcc)}
|
|
|
- end, #ast_info{}, Args),
|
|
|
- RenderAst = erl_syntax:application(none, erl_syntax:atom(render_tag),
|
|
|
- [erl_syntax:string(Name), erl_syntax:list(InterpretedArgs), erl_syntax:variable("TranslationFun")]),
|
|
|
- {{RenderAst, AstInfo#ast_info{custom_tags = [Name]}}, TreeWalker}.
|
|
|
+ ({{identifier, _, Key}, {string_literal, _, Value}}, AstInfoAcc) ->
|
|
|
+ {erl_syntax:tuple([erl_syntax:string(Key), erl_syntax:string(unescape_string_literal(Value))]), AstInfoAcc};
|
|
|
+ ({{identifier, _, Key}, Value}, AstInfoAcc) ->
|
|
|
+ {AST, AstInfo1} = resolve_variable_ast(Value, Context),
|
|
|
+ {erl_syntax:tuple([erl_syntax:string(Key), format(AST,Context)]), merge_info(AstInfo1, AstInfoAcc)}
|
|
|
+ end, #ast_info{}, Args),
|
|
|
+
|
|
|
+ {RenderAst, RenderInfo} = case Context#dtl_context.custom_tags_module of
|
|
|
+ none ->
|
|
|
+ {erl_syntax:application(none, erl_syntax:atom(render_tag),
|
|
|
+ [erl_syntax:string(Name), erl_syntax:list(InterpretedArgs), erl_syntax:variable("TranslationFun")]),
|
|
|
+ AstInfo#ast_info{custom_tags = [Name]}};
|
|
|
+ Module ->
|
|
|
+ {erl_syntax:application(Module, erl_syntax:atom(Name),
|
|
|
+ [erl_syntax:list(InterpretedArgs), erl_syntax:variable("TranslationFun")]), AstInfo}
|
|
|
+ end,
|
|
|
+ {{RenderAst, RenderInfo}, TreeWalker}.
|
|
|
|
|
|
call_ast(Module, TreeWalkerAcc) ->
|
|
|
call_ast(Module, erl_syntax:variable("Variables"), #ast_info{}, TreeWalkerAcc).
|
|
@@ -811,7 +918,7 @@ call_ast(Module, Variable, AstInfo, TreeWalker) ->
|
|
|
AppAst = erl_syntax:application(
|
|
|
erl_syntax:atom(Module),
|
|
|
erl_syntax:atom(render),
|
|
|
- [Variable]),
|
|
|
+ [Variable, erl_syntax:variable("TranslationFun")]),
|
|
|
RenderedAst = erl_syntax:variable("Rendered"),
|
|
|
OkAst = erl_syntax:clause(
|
|
|
[erl_syntax:tuple([erl_syntax:atom(ok), RenderedAst])],
|