package html import ( "fmt" "html/template" "io" "log" "net/http" "os" "path/filepath" "strings" core "github.com/gofiber/template" "github.com/gofiber/utils" ) // Engine struct type Engine struct { core.Engine // templates Templates *template.Template } // New returns an HTML render engine for Fiber func New(directory, extension string) *Engine { engine := &Engine{ Engine: core.Engine{ Left: "{{", Right: "}}", Directory: directory, Extension: extension, LayoutName: "embed", Funcmap: make(map[string]interface{}), }, } engine.AddFunc(engine.LayoutName, func() error { return fmt.Errorf("layoutName called unexpectedly") }) return engine } // NewFileSystem returns an HTML render engine for Fiber with file system func NewFileSystem(fs http.FileSystem, extension string) *Engine { engine := &Engine{ Engine: core.Engine{ Left: "{{", Right: "}}", Directory: "/", FileSystem: fs, Extension: extension, LayoutName: "embed", Funcmap: make(map[string]interface{}), }, } engine.AddFunc(engine.LayoutName, func() error { return fmt.Errorf("layoutName called unexpectedly") }) return engine } // Load parses the templates to the engine. func (e *Engine) Load() error { if e.Loaded { return nil } // race safe e.Mutex.Lock() defer e.Mutex.Unlock() e.Templates = template.New(e.Directory) // Set template settings e.Templates.Delims(e.Left, e.Right) e.Templates.Funcs(e.Funcmap) walkFn := func(path string, info os.FileInfo, err error) error { // Return error if exist if err != nil { return err } // Skip file if it's a directory or has no file info if info == nil || info.IsDir() { return nil } // Skip file if it does not equal the given template Extension if len(e.Extension) >= len(path) || path[len(path)-len(e.Extension):] != e.Extension { return nil } // Get the relative file path // ./views/html/index.tmpl -> index.tmpl rel, err := filepath.Rel(e.Directory, path) if err != nil { return err } // Reverse slashes '\' -> '/' and // partials\footer.tmpl -> partials/footer.tmpl name := filepath.ToSlash(rel) // Remove ext from name 'index.tmpl' -> 'index' name = strings.TrimSuffix(name, e.Extension) // name = strings.Replace(name, e.Extension, "", -1) // Read the file // #gosec G304 buf, err := utils.ReadFile(path, e.FileSystem) if err != nil { return err } // Create new template associated with the current one // This enable use to invoke other templates {{ template .. }} _, err = e.Templates.New(name).Parse(string(buf)) if err != nil { return err } // Debugging if e.Verbose { log.Printf("views: parsed template: %s\n", name) } return err } // notify engine that we parsed all templates e.Loaded = true if e.FileSystem != nil { return utils.Walk(e.FileSystem, e.Directory, walkFn) } return filepath.Walk(e.Directory, walkFn) } // Render will execute the template name along with the given values. func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout ...string) error { if !e.Loaded || e.ShouldReload { if e.ShouldReload { e.Loaded = false } if err := e.Load(); err != nil { return err } } tmpl := e.Templates.Lookup(name) if tmpl == nil { return fmt.Errorf("render: template %s does not exist", name) } render := renderFuncCreate(e, out, binding, *tmpl, nil) if len(layout) > 0 && layout[0] != "" { e.Mutex.Lock() defer e.Mutex.Unlock() } // construct a nested render function to embed templates in layouts for _, layName := range layout { if layName == "" { break } lay := e.Templates.Lookup(layName) if lay == nil { return fmt.Errorf("render: LayoutName %s does not exist", layName) } render = renderFuncCreate(e, out, binding, *lay, render) } return render() } func renderFuncCreate(e *Engine, out io.Writer, binding interface{}, tmpl template.Template, childRenderFunc func() error) func() error { return func() error { tmpl.Funcs(map[string]interface{}{ e.LayoutName: childRenderFunc, }) return tmpl.Execute(out, binding) } }