From 3fcdc79c951fcfb111f0f56d73c59eaa5ed03bf9 Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Fri, 26 Sep 2014 18:40:45 +0200 Subject: [PATCH 1/6] add errorType to razor compilation message. --- src/Common/Razor.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index a2de7db8c..d826826cb 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -83,7 +83,8 @@ type RazorRender(layoutRoots, namespaces) = use _c = Log.colored ConsoleColor.Red printfn "\nProcessing the file '%s' failed\nSource written to: '%s'\nCompilation errors:" source csharp for error in ex.Errors do - printfn " - (%d, %d) %s" error.Line error.Column error.ErrorText + let errorType = if error.IsWarning then "warning" else "error" + printfn " - %s: (%d, %d) %s" errorType error.Line error.Column error.ErrorText printfn "" ) failwith "Generating HTML failed." From 22e78ef4da15dbe542aa3c4fdf82f79db969f49d Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Fri, 26 Sep 2014 19:46:38 +0200 Subject: [PATCH 2/6] fix error message not printed correctly --- src/Common/Razor.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index d826826cb..b9a76765e 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -87,6 +87,7 @@ type RazorRender(layoutRoots, namespaces) = printfn " - %s: (%d, %d) %s" errorType error.Line error.Column error.ErrorText printfn "" ) + Log.close() // wait for the message to be printed completly failwith "Generating HTML failed." and StringDictionary(dict:IDictionary) = From 17e868bdb5a749fc4a78d918924feb8e8bedf02c Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Mon, 29 Sep 2014 12:44:40 +0200 Subject: [PATCH 3/6] improve speed by using razor caching feature. --- src/Common/Razor.fs | 49 +++++++++++++++++++------------ src/FSharp.MetadataFormat/Main.fs | 21 +++++++++---- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index b9a76765e..e94063488 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -34,7 +34,7 @@ open RazorEngine.Templating open RazorEngine.Configuration type RazorRender(layoutRoots, namespaces) = - // Create resolver & set it to the global static filed + // Create resolver & set it to the global static field let templateResolver = { new ITemplateResolver with member x.Resolve name = File.ReadAllText(RazorRender.Resolve(layoutRoots, name + ".cshtml")) } @@ -50,6 +50,23 @@ type RazorRender(layoutRoots, namespaces) = let templateservice = new TemplateService(config) do Razor.SetTemplateService(templateservice) + let handleCompile source f = + try + f () + with + | :? TemplateCompilationException as ex -> + let csharp = Path.GetTempFileName() + ".cs" + File.WriteAllText(csharp, ex.SourceCode) + Log.run (fun () -> + use _c = Log.colored ConsoleColor.Red + printfn "\nProcessing the file '%s' failed\nSource written to: '%s'\nCompilation errors:" source csharp + for error in ex.Errors do + let errorType = if error.IsWarning then "warning" else "error" + printfn " - %s: (%d, %d) %s" errorType error.Line error.Column error.ErrorText + printfn "" + ) + Log.close() // wait for the message to be printed completly + failwith "Generating HTML failed." /// Global resolver (for use in 'DocPageTempalateBase') static member val Resolver = null with get, set /// Find file in one of the specified layout roots @@ -66,30 +83,24 @@ type RazorRender(layoutRoots, namespaces) = member val Model : obj = obj() with get, set /// Dynamic object with more properties (?) member val ViewBag = new DynamicViewBag() with get,set - + member x.CompileTemplate(source, ?modelType) = + handleCompile source (fun _ -> + match modelType with + | Some t -> Razor.Compile(source, t, source) + | _ -> Razor.Compile(source, source)) + member x.ProcessFileCache(name, ?properties) = + x.ViewBag <- new DynamicViewBag() + for k, v in defaultArg properties [] do + x.ViewBag.AddValue(k, v) + Razor.Run(name, x.Model, x.ViewBag) /// Process source file and return result as a string member x.ProcessFile(source, ?properties) = - try + handleCompile source (fun _ -> x.ViewBag <- new DynamicViewBag() for k, v in defaultArg properties [] do x.ViewBag.AddValue(k, v) let html = Razor.Parse(File.ReadAllText(source), x.Model, x.ViewBag, source) - html - with - | :? TemplateCompilationException as ex -> - let csharp = Path.GetTempFileName() + ".cs" - File.WriteAllText(csharp, ex.SourceCode) - Log.run (fun () -> - use _c = Log.colored ConsoleColor.Red - printfn "\nProcessing the file '%s' failed\nSource written to: '%s'\nCompilation errors:" source csharp - for error in ex.Errors do - let errorType = if error.IsWarning then "warning" else "error" - printfn " - %s: (%d, %d) %s" errorType error.Line error.Column error.ErrorText - printfn "" - ) - Log.close() // wait for the message to be printed completly - failwith "Generating HTML failed." - + html) and StringDictionary(dict:IDictionary) = member x.Dictionary = dict /// Report more useful errors when key not found (.NET dictionary does not do this...) diff --git a/src/FSharp.MetadataFormat/Main.fs b/src/FSharp.MetadataFormat/Main.fs index a49004a14..6e99d0f56 100644 --- a/src/FSharp.MetadataFormat/Main.fs +++ b/src/FSharp.MetadataFormat/Main.fs @@ -899,14 +899,16 @@ type MetadataFormat = [ for ns in asm.Namespaces do for n in ns.Modules do yield! nestedModules n ] + let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"]) + let moduleTemplateFile = RazorRender.Resolve(layoutRoots, moduleTemplate) - Parallel.pfor modules (fun () -> RazorRender(layoutRoots,["FSharp.MetadataFormat"])) (fun modul _ razor -> + razor.CompileTemplate(moduleTemplateFile, typeof) + for modul in modules do Log.logf "Generating module: %s" modul.UrlName razor.Model <- box (ModuleInfo.Create(modul, asm)) - let out = razor.ProcessFile(moduleTemplateFile, props) + let out = razor.ProcessFileCache(moduleTemplateFile, props) File.WriteAllText(outDir @@ (modul.UrlName + ".html"), out) Log.logf "Finished module: %s" modul.UrlName - razor) Log.logf "Generating types..." let rec nestedTypes (modul:Module) = seq { @@ -919,10 +921,17 @@ type MetadataFormat = // Generate documentation for all types let typeTemplateFile = RazorRender.Resolve(layoutRoots, typeTemplate) - Parallel.pfor types (fun () -> RazorRender(layoutRoots, ["FSharp.MetadataFormat"])) (fun typ _ razor -> + razor.CompileTemplate(typeTemplateFile, typeof) + for typ in types do Log.logf "Generating type: %s" typ.UrlName razor.Model <- box (TypeInfo.Create(typ, asm)) - let out = razor.ProcessFile(typeTemplateFile, props) + let out = razor.ProcessFileCache(typeTemplateFile, props) File.WriteAllText(outDir @@ (typ.UrlName + ".html"), out) Log.logf "Finished type: %s" typ.UrlName - razor) + //Parallel.pfor types (fun () -> RazorRender(layoutRoots, ["FSharp.MetadataFormat"])) (fun typ _ razor -> + // Log.logf "Generating type: %s" typ.UrlName + // razor.Model <- box (TypeInfo.Create(typ, asm)) + // let out = razor.ProcessFile(typeTemplateFile, props) + // File.WriteAllText(outDir @@ (typ.UrlName + ".html"), out) + // Log.logf "Finished type: %s" typ.UrlName + // razor) From b2ce45beffaaa69999d60068bbbdbf435b9099bd Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Mon, 29 Sep 2014 16:55:40 +0200 Subject: [PATCH 4/6] rewrite RazorRender and design for caching. --- src/Common/Razor.fs | 108 +++++++++++++++++++++--------- src/FSharp.Literate/Formatting.fs | 4 +- src/FSharp.MetadataFormat/Main.fs | 25 ++----- 3 files changed, 83 insertions(+), 54 deletions(-) diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index e94063488..d9f6434fc 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -28,16 +28,28 @@ open System open System.IO open System.Dynamic open System.Collections.Generic +open System.Collections.Concurrent open RazorEngine open RazorEngine.Text open RazorEngine.Templating open RazorEngine.Configuration - -type RazorRender(layoutRoots, namespaces) = +type RazorRender(layoutRoots, namespaces, templateName:string, ?model_type:System.Type) = + let templateName = + if templateName.EndsWith(".cshtml") then + templateName.Substring(0, templateName.Length - 7) + else templateName + let templateCache = new ConcurrentDictionary() // Create resolver & set it to the global static field let templateResolver = { new ITemplateResolver with - member x.Resolve name = File.ReadAllText(RazorRender.Resolve(layoutRoots, name + ".cshtml")) } + member x.Resolve name = + templateCache.GetOrAdd (name, fun name -> + match RazorRender.Resolve(layoutRoots, name + ".cshtml") with + | Some file -> File.ReadAllText(file) + | None -> + failwith "Could not find template file: %s\nSearching in: %A" name layoutRoots + null) + } do RazorRender.Resolver <- templateResolver // Configure templating engine @@ -67,40 +79,68 @@ type RazorRender(layoutRoots, namespaces) = ) Log.close() // wait for the message to be printed completly failwith "Generating HTML failed." + + let withProperties properties oldViewbag = + let viewBag = new DynamicViewBag(oldViewbag) + for k, v in defaultArg properties [] do + viewBag.AddValue(k, v) + viewBag + + do + handleCompile templateName (fun _ -> + //let templateString = File.ReadAllText(templateFile) + //if Razor.Resolve(templateName) = null then + let templateContent = templateResolver.Resolve templateName + //templateCache.AddOrUpdate(templateName, templateContent, fun _ _ -> templateContent) |> ignore + match model_type with + | Some t -> Razor.Compile(templateContent, t, templateName) + | None -> + Razor.Compile(templateContent, templateName)) + // + //Razor.GetTemplate(templateString, cache_name)) /// Global resolver (for use in 'DocPageTempalateBase') static member val Resolver = null with get, set /// Find file in one of the specified layout roots static member Resolve(layoutRoots, name) = - let partFileOpt = - layoutRoots |> Seq.tryPick (fun layoutRoot -> - let partFile = Path.Combine(layoutRoot, name) - if File.Exists(partFile) then Some partFile else None) - match partFileOpt with - | None -> failwithf "Could not find template file: %s\nSearching in: %A" name layoutRoots - | Some partFile -> partFile - - /// Model - whatever the user specifies for the page - member val Model : obj = obj() with get, set + layoutRoots |> Seq.tryPick (fun layoutRoot -> + let partFile = Path.Combine(layoutRoot, name) + if File.Exists(partFile) then Some partFile else None) + static member ForceResolve(layoutRoots, name) = + match RazorRender.Resolve(layoutRoots, name) with + | Some f -> f + | None -> + failwith "Could not find template file: %s\nSearching in: %A" name layoutRoots + + member internal x.HandleCompile source f = handleCompile source f + member internal x.TemplateName = templateName + member internal x.WithProperties properties = withProperties properties x.ViewBag + /// Dynamic object with more properties (?) member val ViewBag = new DynamicViewBag() with get,set - member x.CompileTemplate(source, ?modelType) = - handleCompile source (fun _ -> - match modelType with - | Some t -> Razor.Compile(source, t, source) - | _ -> Razor.Compile(source, source)) - member x.ProcessFileCache(name, ?properties) = - x.ViewBag <- new DynamicViewBag() - for k, v in defaultArg properties [] do - x.ViewBag.AddValue(k, v) - Razor.Run(name, x.Model, x.ViewBag) + member x.ProcessFile(?properties) = + Razor.Run(templateName, null, x.WithProperties properties) /// Process source file and return result as a string - member x.ProcessFile(source, ?properties) = - handleCompile source (fun _ -> - x.ViewBag <- new DynamicViewBag() - for k, v in defaultArg properties [] do - x.ViewBag.AddValue(k, v) - let html = Razor.Parse(File.ReadAllText(source), x.Model, x.ViewBag, source) - html) + member x.ProcessFileParse(?properties) = + handleCompile templateName (fun _ -> + Razor.Parse(templateResolver.Resolve templateName, null, x.WithProperties properties, templateName)) + + member x.ProcessFileModel(model:obj,?properties) = + Razor.Run(templateName, model, x.WithProperties properties) + /// Process source file and return result as a string + member x.ProcessFileParseModel(model:obj, ?properties) = + handleCompile templateName (fun _ -> + Razor.Parse(templateResolver.Resolve templateName, model, x.WithProperties properties, templateName)) + +and RazorRender<'model>(layoutRoots, namespaces, templateName) = + inherit RazorRender(layoutRoots, namespaces, templateName, typeof<'model>) + + member x.ProcessFile(model:'model, ?properties) = + Razor.Run<'model>(x.TemplateName, model, x.WithProperties properties) + /// Process source file and return result as a string + member x.ProcessFileParse(model:'model, ?properties) = + x.HandleCompile x.TemplateName (fun _ -> + Razor.Parse<'model>(RazorRender.Resolver.Resolve x.TemplateName, model, x.WithProperties properties, x.TemplateName)) + and StringDictionary(dict:IDictionary) = member x.Dictionary = dict /// Report more useful errors when key not found (.NET dictionary does not do this...) @@ -147,7 +187,9 @@ and [] DocPageTemplateBase<'T>() = with get() = StringDictionary(defaultArg (x.tryGetViewBagValue> "Properties") (dict [])) and set (value:StringDictionary) = x.trySetViewBagValue> "Properties" value.Dictionary - member x.Root = x.Properties.["root"] - + member x.Root = x.Properties.["root"] member x.RenderPart(name, model:obj) = - Razor.Parse(RazorRender.Resolver.Resolve(name), model) + if Razor.Resolve(name, model) = null then + let templateContent = RazorRender.Resolver.Resolve(name) + Razor.Compile(templateContent, model.GetType(), name) + Razor.Run(name, model) diff --git a/src/FSharp.Literate/Formatting.fs b/src/FSharp.Literate/Formatting.fs index d1c56034b..9570893e1 100644 --- a/src/FSharp.Literate/Formatting.fs +++ b/src/FSharp.Literate/Formatting.fs @@ -73,9 +73,9 @@ module Templating = let private generateFile contentTag parameters templateOpt output layoutRoots = match templateOpt with | Some (file:string) when file.EndsWith("cshtml", true, CultureInfo.InvariantCulture) -> - let razor = RazorRender(layoutRoots, []) + let razor = RazorRender(layoutRoots, [], Path.GetFileNameWithoutExtension file) let props = [ "Properties", dict parameters ] - let generated = razor.ProcessFile(file, props) + let generated = razor.ProcessFile(props) File.WriteAllText(output, generated) | _ -> let templateOpt = templateOpt |> Option.map File.ReadAllText diff --git a/src/FSharp.MetadataFormat/Main.fs b/src/FSharp.MetadataFormat/Main.fs index 6e99d0f56..deeede1a1 100644 --- a/src/FSharp.MetadataFormat/Main.fs +++ b/src/FSharp.MetadataFormat/Main.fs @@ -883,11 +883,10 @@ type MetadataFormat = // Generate all the HTML stuff Log.logf "Starting razor engine" - let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"]) - razor.Model <- box asm + let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"], namespaceTemplate) Log.logf "Generating: index.html" - let out = razor.ProcessFile(RazorRender.Resolve(layoutRoots, namespaceTemplate), props) + let out = razor.ProcessFile(asm, props) File.WriteAllText(outDir @@ "index.html", out) // Generate documentation for all modules @@ -899,14 +898,11 @@ type MetadataFormat = [ for ns in asm.Namespaces do for n in ns.Modules do yield! nestedModules n ] - let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"]) + let razor = RazorRender(layoutRoots, ["FSharp.MetadataFormat"], moduleTemplate) - let moduleTemplateFile = RazorRender.Resolve(layoutRoots, moduleTemplate) - razor.CompileTemplate(moduleTemplateFile, typeof) for modul in modules do Log.logf "Generating module: %s" modul.UrlName - razor.Model <- box (ModuleInfo.Create(modul, asm)) - let out = razor.ProcessFileCache(moduleTemplateFile, props) + let out = razor.ProcessFile(ModuleInfo.Create(modul, asm), props) File.WriteAllText(outDir @@ (modul.UrlName + ".html"), out) Log.logf "Finished module: %s" modul.UrlName @@ -920,18 +916,9 @@ type MetadataFormat = yield! ns.Types ] // Generate documentation for all types - let typeTemplateFile = RazorRender.Resolve(layoutRoots, typeTemplate) - razor.CompileTemplate(typeTemplateFile, typeof) + let razor = new RazorRender(layoutRoots, ["FSharp.MetadataFormat"], typeTemplate) for typ in types do Log.logf "Generating type: %s" typ.UrlName - razor.Model <- box (TypeInfo.Create(typ, asm)) - let out = razor.ProcessFileCache(typeTemplateFile, props) + let out = razor.ProcessFile(TypeInfo.Create(typ, asm), props) File.WriteAllText(outDir @@ (typ.UrlName + ".html"), out) Log.logf "Finished type: %s" typ.UrlName - //Parallel.pfor types (fun () -> RazorRender(layoutRoots, ["FSharp.MetadataFormat"])) (fun typ _ razor -> - // Log.logf "Generating type: %s" typ.UrlName - // razor.Model <- box (TypeInfo.Create(typ, asm)) - // let out = razor.ProcessFile(typeTemplateFile, props) - // File.WriteAllText(outDir @@ (typ.UrlName + ".html"), out) - // Log.logf "Finished type: %s" typ.UrlName - // razor) From 0f36c8fcb4b51ede171738b15002b12e71ddb6c8 Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Mon, 29 Sep 2014 20:27:16 +0200 Subject: [PATCH 5/6] Workaround that DynamicViewBag(DynamicViewBag) constructor is not available on this version. --- src/Common/Razor.fs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index d9f6434fc..319818218 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -33,6 +33,12 @@ open RazorEngine open RazorEngine.Text open RazorEngine.Templating open RazorEngine.Configuration + +type GetMemberBinderImpl (name) = + inherit GetMemberBinder(name, false) + let notImpl () = raise <| new NotImplementedException() + override x.FallbackGetMember(v, sug) = notImpl() + type RazorRender(layoutRoots, namespaces, templateName:string, ?model_type:System.Type) = let templateName = if templateName.EndsWith(".cshtml") then @@ -80,24 +86,25 @@ type RazorRender(layoutRoots, namespaces, templateName:string, ?model_type:Syste Log.close() // wait for the message to be printed completly failwith "Generating HTML failed." - let withProperties properties oldViewbag = - let viewBag = new DynamicViewBag(oldViewbag) + let withProperties properties (oldViewbag:DynamicViewBag) = + let viewBag = new DynamicViewBag() + // TODO: use new DynamicViewBag(oldViewbag) and remove GetMemberBinderImpl + for old in oldViewbag.GetDynamicMemberNames() do + match oldViewbag.TryGetMember(new GetMemberBinderImpl(old)) with + | true, v -> viewBag.AddValue(old, v) + | _ -> () for k, v in defaultArg properties [] do viewBag.AddValue(k, v) viewBag do handleCompile templateName (fun _ -> - //let templateString = File.ReadAllText(templateFile) - //if Razor.Resolve(templateName) = null then let templateContent = templateResolver.Resolve templateName - //templateCache.AddOrUpdate(templateName, templateContent, fun _ _ -> templateContent) |> ignore match model_type with | Some t -> Razor.Compile(templateContent, t, templateName) | None -> Razor.Compile(templateContent, templateName)) - // - //Razor.GetTemplate(templateString, cache_name)) + /// Global resolver (for use in 'DocPageTempalateBase') static member val Resolver = null with get, set /// Find file in one of the specified layout roots From ef989dac1b0e403042abed933fc48b133985ab8e Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Mon, 29 Sep 2014 21:17:20 +0200 Subject: [PATCH 6/6] be lazy with the compiling. --- src/Common/Razor.fs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Common/Razor.fs b/src/Common/Razor.fs index 319818218..d12c32e1b 100644 --- a/src/Common/Razor.fs +++ b/src/Common/Razor.fs @@ -97,14 +97,6 @@ type RazorRender(layoutRoots, namespaces, templateName:string, ?model_type:Syste viewBag.AddValue(k, v) viewBag - do - handleCompile templateName (fun _ -> - let templateContent = templateResolver.Resolve templateName - match model_type with - | Some t -> Razor.Compile(templateContent, t, templateName) - | None -> - Razor.Compile(templateContent, templateName)) - /// Global resolver (for use in 'DocPageTempalateBase') static member val Resolver = null with get, set /// Find file in one of the specified layout roots @@ -117,22 +109,29 @@ type RazorRender(layoutRoots, namespaces, templateName:string, ?model_type:Syste | Some f -> f | None -> failwith "Could not find template file: %s\nSearching in: %A" name layoutRoots - + static member Run<'m>(name, model:'m, viewBag:DynamicViewBag) = + if Razor.Resolve<'m>(name, model) = null then + let templateContent = RazorRender.Resolver.Resolve(name) + Razor.Compile(templateContent, (if obj.ReferenceEquals(model, null) then typeof<'m> else model.GetType()), name) + Razor.Run<'m>(name, model, viewBag) + member internal x.HandleCompile source f = handleCompile source f member internal x.TemplateName = templateName member internal x.WithProperties properties = withProperties properties x.ViewBag /// Dynamic object with more properties (?) - member val ViewBag = new DynamicViewBag() with get,set + member val ViewBag = new DynamicViewBag() with get, set member x.ProcessFile(?properties) = - Razor.Run(templateName, null, x.WithProperties properties) + handleCompile templateName (fun _ -> + RazorRender.Run(templateName, null, x.WithProperties properties)) /// Process source file and return result as a string member x.ProcessFileParse(?properties) = handleCompile templateName (fun _ -> Razor.Parse(templateResolver.Resolve templateName, null, x.WithProperties properties, templateName)) member x.ProcessFileModel(model:obj,?properties) = - Razor.Run(templateName, model, x.WithProperties properties) + handleCompile templateName (fun _ -> + RazorRender.Run(templateName, model, x.WithProperties properties)) /// Process source file and return result as a string member x.ProcessFileParseModel(model:obj, ?properties) = handleCompile templateName (fun _ -> @@ -142,7 +141,8 @@ and RazorRender<'model>(layoutRoots, namespaces, templateName) = inherit RazorRender(layoutRoots, namespaces, templateName, typeof<'model>) member x.ProcessFile(model:'model, ?properties) = - Razor.Run<'model>(x.TemplateName, model, x.WithProperties properties) + x.HandleCompile x.TemplateName (fun _ -> + RazorRender.Run<'model>(x.TemplateName, model, x.WithProperties properties)) /// Process source file and return result as a string member x.ProcessFileParse(model:'model, ?properties) = x.HandleCompile x.TemplateName (fun _ ->