You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sonic/template/template.go

142 lines
3.4 KiB
Go

2 years ago
package template
import (
"context"
2 years ago
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"
2 years ago
"github.com/go-sonic/sonic/util/xerr"
)
type Template struct {
2 years ago
HTMLTemplate *htmlTemplate.Template
2 years ago
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
2 years ago
}
func NewTemplate(logger *zap.Logger, bus event.Bus) *Template {
2 years ago
t := &Template{
sharedVariable: map[string]any{},
logger: logger,
funcMap: map[string]any{},
bus: bus,
2 years ago
}
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
2 years ago
}
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 {
2 years ago
err := filepath.Walk(templateDir, func(path string, _ fs.FileInfo, _ error) error {
2 years ago
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
2 years ago
t.HTMLTemplate = ht
2 years ago
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 {
2 years ago
return t.HTMLTemplate.Execute(wr, t.wrapData(data))
2 years ago
}
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data Model) error {
2 years ago
return t.HTMLTemplate.ExecuteTemplate(wr, name, t.wrapData(data))
2 years ago
}
func (t *Template) ExecuteText(wr io.Writer, data Model) error {
2 years ago
return t.TextTemplate.Execute(wr, t.wrapData(data))
2 years ago
}
func (t *Template) ExecuteTextTemplate(wr io.Writer, name string, data Model) error {
2 years ago
return t.TextTemplate.ExecuteTemplate(wr, name, t.wrapData(data))
2 years ago
}
2 years ago
func (t *Template) wrapData(data Model) map[string]any {
2 years ago
if data == nil {
2 years ago
return nil
2 years ago
}
t.lock.RLock()
defer t.lock.RUnlock()
data.MergeAttributes(t.sharedVariable)
data["now"] = time.Now()
2 years ago
return data
2 years ago
}
func (t *Template) AddFunc(name string, fn interface{}) {
2 years ago
if t.HTMLTemplate != nil {
2 years ago
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
}
}