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 } }