|
|
|
package template
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
htmlTemplate "html/template"
|
|
|
|
"io"
|
|
|
|
"io/fs"
|
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/Masterminds/sprig/v3"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"github.com/go-sonic/sonic/event"
|
|
|
|
"github.com/go-sonic/sonic/util/xerr"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Template struct {
|
|
|
|
HTMLTemplate *htmlTemplate.Template
|
|
|
|
TextTemplate *template.Template
|
|
|
|
sharedVariable map[string]any
|
|
|
|
lock sync.RWMutex
|
|
|
|
watcher *fsnotify.Watcher
|
|
|
|
logger *zap.Logger
|
|
|
|
paths []string
|
|
|
|
funcMap map[string]any
|
|
|
|
bus event.Bus
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTemplate(logger *zap.Logger, bus event.Bus) *Template {
|
|
|
|
t := &Template{
|
|
|
|
sharedVariable: map[string]any{},
|
|
|
|
logger: logger,
|
|
|
|
funcMap: map[string]any{},
|
|
|
|
bus: bus,
|
|
|
|
}
|
|
|
|
t.addUtilFunc()
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
t.watcher = watcher
|
|
|
|
go t.Watch()
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) Reload(paths []string) error {
|
|
|
|
t.bus.Publish(context.Background(), &event.ThemeFileUpdatedEvent{})
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) Load(paths []string) error {
|
|
|
|
t.lock.Lock()
|
|
|
|
defer t.lock.Unlock()
|
|
|
|
|
|
|
|
t.paths = paths
|
|
|
|
filenames := make([]string, 0)
|
|
|
|
for _, templateDir := range paths {
|
|
|
|
err := filepath.Walk(templateDir, func(path string, _ fs.FileInfo, _ error) error {
|
|
|
|
if filepath.Ext(path) == ".tmpl" {
|
|
|
|
filenames = append(filenames, path)
|
|
|
|
if err := t.watcher.Add(path); err != nil {
|
|
|
|
t.logger.Error("template.load.fsnotify.Add", zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return xerr.WithMsg(err, "traverse template dir err").WithStatus(xerr.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ht, err := htmlTemplate.New("").Funcs(t.funcMap).ParseFiles(filenames...)
|
|
|
|
if err != nil {
|
|
|
|
return xerr.WithMsg(err, "parse template err").WithStatus(xerr.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
tt, err := template.New("").Funcs(t.funcMap).ParseFiles(filenames...)
|
|
|
|
if err != nil {
|
|
|
|
return xerr.WithMsg(err, "parse template err").WithStatus(xerr.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
t.TextTemplate = tt
|
|
|
|
t.HTMLTemplate = ht
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) SetSharedVariable(name string, value interface{}) {
|
|
|
|
t.lock.Lock()
|
|
|
|
defer t.lock.Unlock()
|
|
|
|
t.sharedVariable[name] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) Execute(wr io.Writer, data Model) error {
|
|
|
|
return t.HTMLTemplate.Execute(wr, t.wrapData(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data Model) error {
|
|
|
|
return t.HTMLTemplate.ExecuteTemplate(wr, name, t.wrapData(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) ExecuteText(wr io.Writer, data Model) error {
|
|
|
|
return t.TextTemplate.Execute(wr, t.wrapData(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) ExecuteTextTemplate(wr io.Writer, name string, data Model) error {
|
|
|
|
return t.TextTemplate.ExecuteTemplate(wr, name, t.wrapData(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) wrapData(data Model) map[string]any {
|
|
|
|
if data == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
t.lock.RLock()
|
|
|
|
defer t.lock.RUnlock()
|
|
|
|
data.MergeAttributes(t.sharedVariable)
|
|
|
|
data["now"] = time.Now()
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) AddFunc(name string, fn interface{}) {
|
|
|
|
if t.HTMLTemplate != nil {
|
|
|
|
panic("the template has been parsed")
|
|
|
|
}
|
|
|
|
t.funcMap[name] = fn
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Template) addUtilFunc() {
|
|
|
|
t.funcMap["unix_milli_time_format"] = func(format string, t int64) string {
|
|
|
|
return time.UnixMilli(t).Format(format)
|
|
|
|
}
|
|
|
|
t.funcMap["noescape"] = func(str string) htmlTemplate.HTML {
|
|
|
|
return htmlTemplate.HTML(str)
|
|
|
|
}
|
|
|
|
for name, f := range sprig.FuncMap() {
|
|
|
|
t.funcMap[name] = f
|
|
|
|
}
|
|
|
|
}
|