Co-authored-by: James Mills <prologic@shortcircuit.net.au> Reviewed-on: https://git.mills.io/prologic/spyda/pulls/2
146 lines
3.6 KiB
Go
146 lines
3.6 KiB
Go
package internal
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/Masterminds/sprig"
|
|
humanize "github.com/dustin/go-humanize"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"git.mills.io/prologic/spyda/internal/templates"
|
|
)
|
|
|
|
const (
|
|
templatesPath = "templates"
|
|
baseTemplate = "base.html"
|
|
partialsTemplate = "_partials.html"
|
|
baseName = "base"
|
|
)
|
|
|
|
type TemplateManager struct {
|
|
sync.RWMutex
|
|
|
|
debug bool
|
|
templates map[string]*template.Template
|
|
funcMap template.FuncMap
|
|
}
|
|
|
|
func NewTemplateManager(conf *Config) (*TemplateManager, error) {
|
|
templates := make(map[string]*template.Template)
|
|
|
|
funcMap := sprig.FuncMap()
|
|
|
|
funcMap["time"] = humanize.Time
|
|
funcMap["hostnameFromURL"] = HostnameFromURL
|
|
funcMap["prettyURL"] = PrettyURL
|
|
funcMap["isLocalURL"] = IsLocalURLFactory(conf)
|
|
funcMap["formatForDateTime"] = FormatForDateTime
|
|
funcMap["isAdminUser"] = IsAdminUserFactory(conf)
|
|
|
|
m := &TemplateManager{debug: conf.Debug, templates: templates, funcMap: funcMap}
|
|
|
|
if err := m.LoadTemplates(); err != nil {
|
|
log.WithError(err).Error("error loading templates")
|
|
return nil, fmt.Errorf("error loading templates: %w", err)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m *TemplateManager) LoadTemplates() error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
err := fs.WalkDir(templates.FS(), ".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
log.WithError(err).Error("error walking templates")
|
|
return fmt.Errorf("error walking templates: %w", err)
|
|
}
|
|
|
|
fname := d.Name()
|
|
if !d.IsDir() && fname != baseTemplate {
|
|
// Skip _partials.html and also editor swap files, to improve the development
|
|
// cycle. Editors often add suffixes to their swap files, e.g "~" or ".swp"
|
|
// (Vim) and those files are not parsable as templates, causing panics.
|
|
if fname == partialsTemplate || !strings.HasSuffix(fname, ".html") {
|
|
return nil
|
|
}
|
|
|
|
name := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
t := template.New(name).Option("missingkey=zero")
|
|
t.Funcs(m.funcMap)
|
|
|
|
template.Must(t.Parse(templates.MustGetTemplate(fname)))
|
|
template.Must(t.Parse(templates.MustGetTemplate(partialsTemplate)))
|
|
template.Must(t.Parse(templates.MustGetTemplate(baseTemplate)))
|
|
|
|
m.templates[name] = t
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.WithError(err).Error("error loading templates")
|
|
return fmt.Errorf("error loading templates: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *TemplateManager) Add(name string, template *template.Template) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.templates[name] = template
|
|
}
|
|
|
|
func (m *TemplateManager) Exec(name string, ctx *Context) (io.WriterTo, error) {
|
|
if m.debug {
|
|
log.Debug("reloading templates in debug mode...")
|
|
if err := m.LoadTemplates(); err != nil {
|
|
log.WithError(err).Error("error reloading templates")
|
|
return nil, fmt.Errorf("error reloading templates: %w", err)
|
|
}
|
|
}
|
|
|
|
m.RLock()
|
|
template, ok := m.templates[name]
|
|
m.RUnlock()
|
|
|
|
if !ok {
|
|
log.WithField("name", name).Errorf("template not found")
|
|
return nil, fmt.Errorf("no such template: %s", name)
|
|
}
|
|
|
|
if ctx == nil {
|
|
ctx = &Context{}
|
|
}
|
|
|
|
buf := bytes.NewBuffer([]byte{})
|
|
err := template.ExecuteTemplate(buf, baseName, ctx)
|
|
if err != nil {
|
|
log.WithError(err).WithField("name", name).Errorf("error executing template")
|
|
return nil, fmt.Errorf("error executing template %s: %w", name, err)
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
// RenderHTML ...
|
|
func RenderHTML(tpl string, ctx *Context) (string, error) {
|
|
t := template.Must(template.New("tpl").Parse(tpl))
|
|
buf := bytes.NewBuffer([]byte{})
|
|
err := t.Execute(buf, ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|