fixes and improvements in broker and render

This commit is contained in:
2025-05-23 14:31:23 +02:00
parent ba604bb8b4
commit 2fea2e6ac5
5 changed files with 116 additions and 94 deletions
+64 -79
View File
@@ -4,8 +4,8 @@ import (
"bytes"
"errors"
"html/template"
"io/fs"
"log/slog"
"maps"
"net/http"
"path/filepath"
"reflect"
@@ -37,7 +37,10 @@ func defaultHTMLRender() *Render {
EnableCache: false,
TemplatesPath: "templates",
TemplateData: TemplateData{},
Functions: template.FuncMap{},
Functions: template.FuncMap{
"default": defaultIfEmpty,
"dict": Dict,
},
templateCache: templateCache{},
}
}
@@ -53,117 +56,99 @@ func (re *Render) apply(opts ...RenderOptions) *Render {
opt(re)
}
}
return re
}
func defaultIfEmpty(fallback, value string) string {
if strings.TrimSpace(value) == "" {
func defaultIfEmpty(fallback string, value any) string {
str, ok := value.(string)
if !ok || strings.TrimSpace(str) == "" {
return fallback
}
return value
return str
}
func (re *Render) Template(w http.ResponseWriter, tmpl string, td *TemplateData) error {
var tc templateCache
var err error
re.Functions["default"] = defaultIfEmpty
func cloneFuncMap(src template.FuncMap) template.FuncMap {
c := make(template.FuncMap)
maps.Copy(c, src)
return c
}
func (re *Render) Template(w http.ResponseWriter, layoutName, pageName string, td *TemplateData) error {
if td == nil {
td = &TemplateData{}
}
tc, err = re.getTemplateCache()
tmpl, err := re.loadTemplateWithLayout(layoutName, pageName)
if err != nil {
return err
}
t, ok := tc[tmpl]
if !ok {
return errors.New("can't get template from cache")
}
buf := new(bytes.Buffer)
if err = t.Execute(buf, td); err != nil {
if err = tmpl.ExecuteTemplate(buf, strings.TrimSuffix(layoutName, ".gohtml"), td); err != nil {
return err
}
if _, err = buf.WriteTo(w); err != nil {
return err
}
return nil
_, err = buf.WriteTo(w)
return err
}
func (re *Render) getTemplateCache() (templateCache, error) {
if len(re.templateCache) == 0 {
cachedTemplates, err := re.createTemplateCache()
if err != nil {
return nil, err
}
re.templateCache = cachedTemplates
func (re *Render) RenderComponent(name string, td *TemplateData) (string, error) {
if td == nil {
td = &TemplateData{}
}
path := filepath.Join(re.TemplatesPath, "components", name)
files := []string{path}
matches, err := filepath.Glob(filepath.Join(re.TemplatesPath, "components", "*.gohtml"))
if err != nil {
return "", err
}
files = append(files, matches...)
funcs := cloneFuncMap(re.Functions)
tmpl, err := template.New(name).Funcs(funcs).ParseFiles(files...)
if err != nil {
return "", err
}
var buf bytes.Buffer
err = tmpl.ExecuteTemplate(&buf, strings.TrimSuffix(name, ".gohtml"), td)
return buf.String(), err
}
func (re *Render) loadTemplateWithLayout(layoutName, pageName string) (*template.Template, error) {
cacheKey := layoutName + "::" + pageName
if re.EnableCache {
return re.templateCache, nil
if tmpl, ok := re.templateCache[cacheKey]; ok {
return tmpl, nil
}
}
return re.createTemplateCache()
}
func (re *Render) findHTMLFiles() ([]string, error) {
var files []string
layoutPath := filepath.Join(re.TemplatesPath, "layouts", layoutName)
pagePath := filepath.Join(re.TemplatesPath, "pages", pageName)
err := filepath.WalkDir(re.TemplatesPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() && filepath.Ext(path) == ".gohtml" {
files = append(files, path)
}
return nil
})
files := []string{layoutPath, pagePath}
componentFiles, err := filepath.Glob(filepath.Join(re.TemplatesPath, "components", "*.gohtml"))
if err != nil {
return nil, err
}
files = append(files, componentFiles...)
funcs := cloneFuncMap(re.Functions)
tmpl, err := template.New(layoutName).Funcs(funcs).ParseFiles(files...)
if err != nil {
return nil, err
}
return files, nil
}
func (re *Render) createTemplateCache() (templateCache, error) {
cache := templateCache{}
var baseTemplates []string
var renderTemplates []string
templates, err := re.findHTMLFiles()
if err != nil {
return cache, err
if re.EnableCache {
re.templateCache[cacheKey] = tmpl
slog.Debug("template cached", "key", cacheKey)
}
slog.Debug("templates", "templates", templates)
for _, file := range templates {
filePathBase := filepath.Base(file)
if strings.Contains(filePathBase, "layout") || strings.Contains(filePathBase, "fragment") {
baseTemplates = append(baseTemplates, file)
}
}
for _, file := range templates {
filePathBase := filepath.Base(file)
if strings.Contains(filePathBase, "page") || strings.Contains(filePathBase, "component") {
renderTemplates = append(baseTemplates, file)
ts, err := template.New(filePathBase).Funcs(re.Functions).ParseFiles(append(baseTemplates, renderTemplates...)...)
if err != nil {
return cache, err
}
cache[filePathBase] = ts
}
}
return cache, nil
return tmpl, nil
}
// Pages contains pagination info.