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