package templates
import (
"fmt"
"html/template"
"io"
"log"
"path"
"strings"
"git.phc.dm.unipi.it/phc/website/config"
"git.phc.dm.unipi.it/phc/website/util"
)
// CachedTemplate holds a reference to the list of patterns to preload and a reference to the already parsed template
type CachedTemplate struct {
PreLoadTemplatePatterns [ ] string
cachedTmpl * template . Template
}
// NewCacheTemplate creates a new template from a list of patterns a loads it
func NewCacheTemplate ( loadPatterns ... string ) * CachedTemplate {
cachedTemplate := & CachedTemplate {
PreLoadTemplatePatterns : loadPatterns ,
cachedTmpl : nil ,
}
template . Must ( cachedTemplate . Load ( nil ) )
return cachedTemplate
}
// Load returns the cached template or loads it from disk using the list of preload (glob) patterns.
func ( ct * CachedTemplate ) Load ( t * template . Template ) ( * template . Template , error ) {
if ct . cachedTmpl != nil {
return ct . cachedTmpl , nil
}
ct . cachedTmpl = template . New ( "" ) . Funcs ( template . FuncMap {
"raw" : func ( value any ) template . HTML {
return template . HTML ( fmt . Sprint ( value ) )
} ,
} )
for _ , pattern := range ct . PreLoadTemplatePatterns {
var err error
ct . cachedTmpl , err = ct . cachedTmpl . ParseGlob ( pattern )
if err != nil {
return nil , err
}
}
return ct . cachedTmpl , nil
}
// Reload the pattern from disk (used for developing)
func ( ct * CachedTemplate ) Reload ( ) {
ct . cachedTmpl = nil
template . Must ( ct . Load ( nil ) )
}
// Template returns the cached template
func ( ct * CachedTemplate ) Template ( ) * template . Template {
log . Printf ( "preloadPatterns: %v" , ct . PreLoadTemplatePatterns )
return ct . cachedTmpl
}
// TODO: Add a render function to CachedTemplate instead of returning the template reference. In this way the Template Renderer would not have a "direct" dependency on "html/template"...
// TemplateRenderer holds cached templates for rendering
type TemplateRenderer struct {
viewsDir string
preloadTemplatePatterns [ ] string
templateCache map [ string ] * CachedTemplate
}
// NewRenderer constructs a template renderer with a base file
func NewRenderer ( rootPath string , loadPatterns ... string ) * TemplateRenderer {
return & TemplateRenderer {
rootPath ,
loadPatterns ,
map [ string ] * CachedTemplate { } ,
}
}
func ( r * TemplateRenderer ) Load ( name string ) * CachedTemplate {
cachedTemplate , present := r . templateCache [ name ]
if ! present {
loaders := [ ] string { }
loaders = append ( loaders , r . preloadTemplatePatterns ... )
loaders = append ( loaders , path . Join ( r . viewsDir , name ) )
cachedTemplate = NewCacheTemplate ( loaders ... )
r . templateCache [ name ] = cachedTemplate
}
return cachedTemplate
}
// Render the template, also injects "Page" and "Config" values in the template
func ( r * TemplateRenderer ) Render ( w io . Writer , name string , data util . Map ) error {
cachedTemplate := r . Load ( name )
if config . Mode == "development" {
cachedTemplate . Reload ( )
}
newData := util . Map { }
newData . Apply ( data )
newData [ "Page" ] = util . Map {
// Used to inject a page specific class on <body>
"Name" : strings . TrimSuffix ( path . Base ( name ) , ".html" ) ,
}
newData [ "Config" ] = config . Object ( )
return cachedTemplate . Template ( ) . ExecuteTemplate ( w , "base" , newData )
}