Browse Source

added recompilation-skip if beamfile exists and checksum of template source file has not changed (currently disabled, but works)

Roberto Saccon 17 years ago
parent
commit
34d4599ea4
2 changed files with 113 additions and 45 deletions
  1. 2 1
      src/demo/erlydtl_demo.erl
  2. 111 44
      src/erlydtl/erlydtl_compiler.erl

+ 2 - 1
src/demo/erlydtl_demo.erl

@@ -335,7 +335,8 @@ render(Name, Args) ->
     Module = list_to_atom("test_" ++ Name),
     case catch Module:render(Args) of
         {ok, Val} -> 
-            case file:open(filename:join([OutDir, filename:basename(Module:source())]), [write]) of
+            {File, _} = Module:source(),
+            case file:open(filename:join([OutDir, filename:basename(File)]), [write]) of
                 {ok, IoDev} ->
                     file:write(IoDev, Val),
                     file:close(IoDev),

+ 111 - 44
src/erlydtl/erlydtl_compiler.erl

@@ -35,7 +35,7 @@
 -author('rsaccon@gmail.com').
 -author('emmiller@gmail.com').
 
--export([compile/2, compile/3, compile/4, compile/5, compile/6,compile/7, parse/2, scan/2, body_ast/3]).
+-export([compile/2, compile/3, compile/4, compile/5, compile/6,compile/7]).
 
 -record(dtl_context, {
     local_scopes = [], 
@@ -45,7 +45,8 @@
     parse_trail = [],
     preset_vars = [],
     custom_tags_dir = [],
-    reader = {file, read_file}}).
+    reader = {file, read_file},
+    module = []}).
 
 -record(ast_info, {
     dependencies = [],
@@ -72,15 +73,22 @@ compile(File, Module, DocRoot, Vars, CustomTagsDir) ->
 compile(File, Module, DocRoot, Vars, CustomTagsDir, Reader) ->
     compile(File, Module, DocRoot, Vars, CustomTagsDir, Reader, "ebin").
 
-compile(File, Module, DocRoot, Vars, CustomTagsDir, Reader, OutDir) ->   
-    case parse(File, Reader) of
-        {ok, DjangoParseTree} ->        
+
+compile(File, Module, DocRoot, Vars, CustomTagsDir, Reader, OutDir) ->  
+    crypto:start(),
+    case parse(File, list_to_atom(Module), Reader) of  
+        ok ->
+            ok;
+        {ok, DjangoParseTree, CheckSum} ->
             try body_ast(DjangoParseTree, #dtl_context{
                     doc_root = DocRoot,
                     custom_tags_dir = CustomTagsDir,
-                    parse_trail = [File], preset_vars = Vars, reader = Reader}, #treewalker{}) of
+                    parse_trail = [File], 
+                    preset_vars = Vars, 
+                    reader = Reader,
+                    module = list_to_atom(Module)}, #treewalker{}) of
                 {{Ast, Info}, _} ->
-                    case compile:forms(forms(File, Module, Ast, Info), []) of
+                    case compile:forms(forms(File, Module, Ast, Info, CheckSum), []) of
                         {ok, Module1, Bin} ->       
                             BeamFile = filename:join([OutDir, atom_to_list(Module1) ++ ".beam"]),
                             case file:write_file(BeamFile, Bin) of
@@ -100,31 +108,77 @@ compile(File, Module, DocRoot, Vars, CustomTagsDir, Reader, OutDir) ->
                     end
             catch 
                 throw:Error -> Error
-            end; 
-        Error ->
-            Error
+            end;
+        Err ->
+            Err
     end.
+    
 
-
-scan(File, {Module, Function}) ->
-    case catch Module:Function(File) of
-        {ok, B} ->
-            erlydtl_scanner:scan(binary_to_list(B));
+is_up_to_date(CheckSum, Module, {M, F}) ->
+    case catch Module:source() of
+        {_, CheckSum} -> 
+            case catch Module:dependencies() of
+                L when is_list(L) ->
+                    RecompileList = lists:foldl(fun
+                            ({XFile, XCheckSum}, Acc) ->
+                                case catch M:F(XFile) of
+                                    {ok, Data} ->
+                                        case binary_to_list(crypto:sha(Data)) of
+                                            XCheckSum ->
+                                                Acc;
+                                            _ ->
+                                                [recompile | Acc]
+                                        end;
+                                    _ ->
+                                        [recompile | Acc]
+                                end                                        
+                        end, [], L),
+                    case RecompileList of
+                        [] -> true; 
+                        _ -> false
+                    end;
+                _ ->
+                    false
+            end;
         _ ->
-            {error, "reading " ++ File ++ " failed "}
+            false
     end.
-
-
-parse(File, Reader) ->
-    case scan(File, Reader) of
+    
+    
+parse(Data) ->
+    case erlydtl_scanner:scan(binary_to_list(Data)) of
         {ok, Tokens} ->
             erlydtl_parser:parse(Tokens);
         Err ->
             Err
-    end.
+    end.        
   
+        
+parse(CheckSum, Module, Data, Reader) ->
+    case is_up_to_date(CheckSum, Module, Reader) of
+        true_ignoring_for_now ->  %% it works, but is disabled, to enable change to 'true' 
+            ok;
+        _ ->
+            case parse(Data) of
+                {ok, Val} ->
+                    {ok, Val, CheckSum};
+                Err ->
+                    Err
+            end
+    end.
+    
+
+parse(File, Module, {M, F} = Reader) ->  
+    case catch M:F(File) of
+        {ok, Data} ->
+            CheckSum = binary_to_list(crypto:sha(Data)),
+            parse(CheckSum, Module, Data, Reader);
+        _ ->
+            {error, "reading " ++ File ++ " failed "}
+    end.
+
   
-forms(File, Module, BodyAst, BodyInfo) ->    
+forms(File, Module, BodyAst, BodyInfo, CheckSum) ->    
     Render0FunctionAst = erl_syntax:function(erl_syntax:atom(render),
         [erl_syntax:clause([], none, [erl_syntax:application(none, 
                         erl_syntax:atom(render), [erl_syntax:list([])])])]),
@@ -136,21 +190,29 @@ forms(File, Module, BodyAst, BodyInfo) ->
         [erl_syntax:tuple([erl_syntax:atom(error), erl_syntax:variable("Err")])]),            
     Render1FunctionAst = erl_syntax:function(erl_syntax:atom(render),
         [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
-                [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),  
+            [erl_syntax:try_expr([Function2], [ClauseOk], [ClauseCatch])])]),  
+     
+    SourceFunctionTuple = erl_syntax:tuple(
+        [erl_syntax:string(File), erl_syntax:string(CheckSum)]),
     SourceFunctionAst = erl_syntax:function(
         erl_syntax:atom(source),
-        [erl_syntax:clause([], none, [erl_syntax:string(File)])]),
+            [erl_syntax:clause([], none, [SourceFunctionTuple])]),
+    
     DependenciesFunctionAst = erl_syntax:function(
         erl_syntax:atom(dependencies), [erl_syntax:clause([], none, 
-                [erl_syntax:list(lists:map(fun(Dep) -> erl_syntax:string(Dep) end, 
-                            BodyInfo#ast_info.dependencies))])]),     
+            [erl_syntax:list(lists:map(fun 
+                    ({XFile, XCheckSum}) -> 
+                        erl_syntax:tuple([erl_syntax:string(XFile), erl_syntax:string(XCheckSum)])
+                end, BodyInfo#ast_info.dependencies))])]),     
+    
     RenderInternalFunctionAst = erl_syntax:function(
         erl_syntax:atom(render2), 
-        [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
+            [erl_syntax:clause([erl_syntax:variable("Variables")], none, 
                 [BodyAst])]),   
+    
     ProplistsClauseErr = erl_syntax:clause([erl_syntax:atom(undefined)], none, 
-    [erl_syntax:application(none, erl_syntax:atom(throw),
-        [erl_syntax:tuple([erl_syntax:atom(undefined_variable), erl_syntax:variable("Key")])])]),  
+        [erl_syntax:application(none, erl_syntax:atom(throw),
+            [erl_syntax:tuple([erl_syntax:atom(undefined_variable), erl_syntax:variable("Key")])])]),  
     ProplistsClauseOk = erl_syntax:clause([erl_syntax:variable("Val")], none, 
         [erl_syntax:variable("Val")]),       
     ProplistsFunctionAst = erl_syntax:function(erl_syntax:atom(get_value), 
@@ -158,12 +220,15 @@ forms(File, Module, BodyAst, BodyInfo) ->
                 [erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(proplists), 
                             erl_syntax:atom(get_value), [erl_syntax:variable("Key"), erl_syntax:variable("L")]), 
                         [ProplistsClauseErr, ProplistsClauseOk])])]),
+    
     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(render), erl_syntax:integer(0)),
                     erl_syntax:arity_qualifier(erl_syntax:atom(render), erl_syntax:integer(1)),
                     erl_syntax:arity_qualifier(erl_syntax:atom(source), erl_syntax:integer(0)),
                     erl_syntax:arity_qualifier(erl_syntax:atom(dependencies), erl_syntax:integer(0))])]),
+    
     [erl_syntax:revert(X) || X <- [ModuleAst, ExportAst, Render0FunctionAst,
             Render1FunctionAst, SourceFunctionAst, DependenciesFunctionAst, RenderInternalFunctionAst, 
             ProplistsFunctionAst | BodyInfo#ast_info.pre_render_asts]].    
@@ -176,7 +241,8 @@ body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, T
         true ->
             throw({error, "Circular file inclusion!"});
         _ ->
-            {ok, ParentParseTree} = parse(File, Context#dtl_context.reader),
+            %% TODO: check for errors
+            {ok, ParentParseTree, CheckSum} = parse(File, Context#dtl_context.module, Context#dtl_context.reader),
             BlockDict = lists:foldl(
                 fun
                     ({block, {identifier, _, Name}, Contents}, Dict) ->
@@ -184,7 +250,7 @@ body_ast([{extends, {string_literal, _Pos, String}} | ThisParseTree], Context, T
                     (_, Dict) ->
                         Dict
                 end, dict:new(), ThisParseTree),
-            with_dependency(File, body_ast(ParentParseTree, Context#dtl_context{
+            with_dependency({File, CheckSum}, body_ast(ParentParseTree, Context#dtl_context{
                     block_dict = dict:merge(fun(_Key, _ParentVal, ChildVal) -> ChildVal end,
                         BlockDict, Context#dtl_context.block_dict),
                     parse_trail = [File | Context#dtl_context.parse_trail]}, TreeWalker))
@@ -326,8 +392,9 @@ string_ast(String, TreeWalker) ->
 
 include_ast(File, Context, TreeWalker) ->
     FilePath = full_path(File, Context#dtl_context.doc_root),
-    {ok, InclusionParseTree} = parse(FilePath, Context#dtl_context.reader),
-    with_dependency(FilePath, body_ast(InclusionParseTree, Context#dtl_context{
+    %% TODO: check for errors (and throw error report)
+    {ok, InclusionParseTree, CheckSum} = parse(FilePath, Context#dtl_context.module, Context#dtl_context.reader),
+    with_dependency({FilePath, CheckSum}, body_ast(InclusionParseTree, Context#dtl_context{
         parse_trail = [FilePath | Context#dtl_context.parse_trail]}, TreeWalker)).
 
 
@@ -557,9 +624,9 @@ tag_ast(Name, Args, Context, TreeWalker) ->
             DefaultFilePath = filename:join([erlydtl_deps:get_base_dir(), "priv", "custom_tags", Name]),
             case Context#dtl_context.custom_tags_dir of
                 [] ->
-                    case parse(DefaultFilePath, Context#dtl_context.reader) of
-                        {ok, TagParseTree} ->
-                            tag_ast2(DefaultFilePath, TagParseTree, InterpretedArgs, Context, TreeWalker);
+                    case parse(DefaultFilePath, Context#dtl_context.module, Context#dtl_context.reader) of
+                        {ok, TagParseTree, CheckSum} ->
+                            tag_ast2({DefaultFilePath, CheckSum}, TagParseTree, InterpretedArgs, Context, TreeWalker);
                         _ ->
                             Reason = lists:concat(["Loading tag source for '", Name, "' failed: ", 
                                 DefaultFilePath]),
@@ -567,13 +634,13 @@ tag_ast(Name, Args, Context, TreeWalker) ->
                     end;
                 _ ->
                     CustomFilePath = filename:join([Context#dtl_context.custom_tags_dir, Name]),
-                    case parse(CustomFilePath, Context#dtl_context.reader) of
-                        {ok, TagParseTree} ->
-                            tag_ast2(CustomFilePath, TagParseTree, InterpretedArgs, Context, TreeWalker);
+                    case parse(CustomFilePath, Context#dtl_context.module, Context#dtl_context.reader) of
+                        {ok, TagParseTree, CheckSum} ->
+                            tag_ast2({CustomFilePath, CheckSum},TagParseTree, InterpretedArgs, Context, TreeWalker);
                         _ ->
-                            case parse(DefaultFilePath, Context#dtl_context.reader) of
-                                {ok, TagParseTree} ->
-                                    tag_ast2(DefaultFilePath, TagParseTree, InterpretedArgs, Context, TreeWalker);
+                            case parse(DefaultFilePath, Context#dtl_context.module, Context#dtl_context.reader) of
+                                {ok, TagParseTree, CheckSum} ->
+                                    tag_ast2({DefaultFilePath, CheckSum}, TagParseTree, InterpretedArgs, Context, TreeWalker);
                                 _ ->
                                     Reason = lists:concat(["Loading tag source for '", Name, "' failed: ", 
                                         CustomFilePath, ", ", DefaultFilePath]),
@@ -585,8 +652,8 @@ tag_ast(Name, Args, Context, TreeWalker) ->
             throw({error, lists:concat(["Custom tag '", Name, "' not loaded"])})
     end.
  
- tag_ast2(Source, TagParseTree, InterpretedArgs, Context, TreeWalker) ->
-    with_dependency(Source, body_ast(TagParseTree, Context#dtl_context{
+ tag_ast2({Source, _} = Dep, TagParseTree, InterpretedArgs, Context, TreeWalker) ->
+    with_dependency(Dep, body_ast(TagParseTree, Context#dtl_context{
         local_scopes = [ InterpretedArgs | Context#dtl_context.local_scopes ],
         parse_trail = [ Source | Context#dtl_context.parse_trail ]}, TreeWalker)).