126 lines
2.3 KiB
Go
126 lines
2.3 KiB
Go
|
package html
|
||
|
|
||
|
import (
|
||
|
"embed"
|
||
|
"html/template"
|
||
|
"io/fs"
|
||
|
"net/http"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
"go.fifitido.net/ytdl-web/pkg/view"
|
||
|
)
|
||
|
|
||
|
type Engine struct {
|
||
|
fs embed.FS
|
||
|
tpl *template.Template
|
||
|
opts *Options
|
||
|
|
||
|
loadOnce sync.Once
|
||
|
mu sync.Mutex
|
||
|
}
|
||
|
|
||
|
var _ view.Engine = (*Engine)(nil)
|
||
|
|
||
|
func New(fs embed.FS, options ...*Options) *Engine {
|
||
|
var opts *Options
|
||
|
if len(options) > 0 && options[0] != nil {
|
||
|
opts = options[0]
|
||
|
} else {
|
||
|
opts = DefaultOptions()
|
||
|
}
|
||
|
|
||
|
tpl := template.New("/")
|
||
|
tpl.Delims(opts.LeftDelim, opts.RightDelim)
|
||
|
tpl.Funcs(opts.FuncMap)
|
||
|
|
||
|
return &Engine{
|
||
|
fs: fs,
|
||
|
tpl: tpl,
|
||
|
opts: opts,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (e *Engine) Load() error {
|
||
|
var err error
|
||
|
e.loadOnce.Do(func() {
|
||
|
err = e.doLoad()
|
||
|
})
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (e *Engine) doLoad() error {
|
||
|
e.mu.Lock()
|
||
|
defer e.mu.Unlock()
|
||
|
|
||
|
return fs.WalkDir(e.fs, ".", func(path string, d fs.DirEntry, err error) error {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if d == nil || d.IsDir() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if len(path) < len(e.opts.Extension) || path[len(path)-len(e.opts.Extension):] != e.opts.Extension {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
rel, err := filepath.Rel(e.opts.BaseDir, path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
name := filepath.ToSlash(rel)
|
||
|
name = strings.TrimSuffix(name, e.opts.Extension)
|
||
|
|
||
|
buf, err := e.fs.ReadFile(path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
_, err = e.tpl.New(name).Parse(string(buf))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (e *Engine) Render(w http.ResponseWriter, view string, data view.Data, layout ...string) {
|
||
|
tmpl := e.tpl.Lookup(view)
|
||
|
if tmpl == nil {
|
||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if len(layout) > 0 && layout[0] != "" {
|
||
|
lay := e.tpl.Lookup(layout[0])
|
||
|
if lay == nil {
|
||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
e.mu.Lock()
|
||
|
defer e.mu.Unlock()
|
||
|
|
||
|
lay.Funcs(map[string]interface{}{
|
||
|
"yield": func() error {
|
||
|
return tmpl.Execute(w, data)
|
||
|
},
|
||
|
})
|
||
|
|
||
|
if err := lay.Execute(w, data); err != nil {
|
||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := tmpl.Execute(w, data); err != nil {
|
||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
}
|