package templates import ( "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(template.New(""))) 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 = t 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 "Name": strings.TrimSuffix(path.Base(name), ".html"), } newData["Config"] = config.Object() return cachedTemplate.Template().ExecuteTemplate(w, "base", newData) }