html.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package html
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io"
  6. "log"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. core "github.com/gofiber/template"
  12. "github.com/gofiber/utils"
  13. )
  14. // Engine struct
  15. type Engine struct {
  16. core.Engine
  17. // templates
  18. Templates *template.Template
  19. }
  20. // New returns an HTML render engine for Fiber
  21. func New(directory, extension string) *Engine {
  22. engine := &Engine{
  23. Engine: core.Engine{
  24. Left: "{{",
  25. Right: "}}",
  26. Directory: directory,
  27. Extension: extension,
  28. LayoutName: "embed",
  29. Funcmap: make(map[string]interface{}),
  30. },
  31. }
  32. engine.AddFunc(engine.LayoutName, func() error {
  33. return fmt.Errorf("layoutName called unexpectedly")
  34. })
  35. return engine
  36. }
  37. // NewFileSystem returns an HTML render engine for Fiber with file system
  38. func NewFileSystem(fs http.FileSystem, extension string) *Engine {
  39. engine := &Engine{
  40. Engine: core.Engine{
  41. Left: "{{",
  42. Right: "}}",
  43. Directory: "/",
  44. FileSystem: fs,
  45. Extension: extension,
  46. LayoutName: "embed",
  47. Funcmap: make(map[string]interface{}),
  48. },
  49. }
  50. engine.AddFunc(engine.LayoutName, func() error {
  51. return fmt.Errorf("layoutName called unexpectedly")
  52. })
  53. return engine
  54. }
  55. // Load parses the templates to the engine.
  56. func (e *Engine) Load() error {
  57. if e.Loaded {
  58. return nil
  59. }
  60. // race safe
  61. e.Mutex.Lock()
  62. defer e.Mutex.Unlock()
  63. e.Templates = template.New(e.Directory)
  64. // Set template settings
  65. e.Templates.Delims(e.Left, e.Right)
  66. e.Templates.Funcs(e.Funcmap)
  67. walkFn := func(path string, info os.FileInfo, err error) error {
  68. // Return error if exist
  69. if err != nil {
  70. return err
  71. }
  72. // Skip file if it's a directory or has no file info
  73. if info == nil || info.IsDir() {
  74. return nil
  75. }
  76. // Skip file if it does not equal the given template Extension
  77. if len(e.Extension) >= len(path) || path[len(path)-len(e.Extension):] != e.Extension {
  78. return nil
  79. }
  80. // Get the relative file path
  81. // ./views/html/index.tmpl -> index.tmpl
  82. rel, err := filepath.Rel(e.Directory, path)
  83. if err != nil {
  84. return err
  85. }
  86. // Reverse slashes '\' -> '/' and
  87. // partials\footer.tmpl -> partials/footer.tmpl
  88. name := filepath.ToSlash(rel)
  89. // Remove ext from name 'index.tmpl' -> 'index'
  90. name = strings.TrimSuffix(name, e.Extension)
  91. // name = strings.Replace(name, e.Extension, "", -1)
  92. // Read the file
  93. // #gosec G304
  94. buf, err := utils.ReadFile(path, e.FileSystem)
  95. if err != nil {
  96. return err
  97. }
  98. // Create new template associated with the current one
  99. // This enable use to invoke other templates {{ template .. }}
  100. _, err = e.Templates.New(name).Parse(string(buf))
  101. if err != nil {
  102. return err
  103. }
  104. // Debugging
  105. if e.Verbose {
  106. log.Printf("views: parsed template: %s\n", name)
  107. }
  108. return err
  109. }
  110. // notify engine that we parsed all templates
  111. e.Loaded = true
  112. if e.FileSystem != nil {
  113. return utils.Walk(e.FileSystem, e.Directory, walkFn)
  114. }
  115. return filepath.Walk(e.Directory, walkFn)
  116. }
  117. // Render will execute the template name along with the given values.
  118. func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout ...string) error {
  119. if !e.Loaded || e.ShouldReload {
  120. if e.ShouldReload {
  121. e.Loaded = false
  122. }
  123. if err := e.Load(); err != nil {
  124. return err
  125. }
  126. }
  127. tmpl := e.Templates.Lookup(name)
  128. if tmpl == nil {
  129. return fmt.Errorf("render: template %s does not exist", name)
  130. }
  131. render := renderFuncCreate(e, out, binding, *tmpl, nil)
  132. if len(layout) > 0 && layout[0] != "" {
  133. e.Mutex.Lock()
  134. defer e.Mutex.Unlock()
  135. }
  136. // construct a nested render function to embed templates in layouts
  137. for _, layName := range layout {
  138. if layName == "" {
  139. break
  140. }
  141. lay := e.Templates.Lookup(layName)
  142. if lay == nil {
  143. return fmt.Errorf("render: LayoutName %s does not exist", layName)
  144. }
  145. render = renderFuncCreate(e, out, binding, *lay, render)
  146. }
  147. return render()
  148. }
  149. func renderFuncCreate(e *Engine, out io.Writer, binding interface{}, tmpl template.Template, childRenderFunc func() error) func() error {
  150. return func() error {
  151. tmpl.Funcs(map[string]interface{}{
  152. e.LayoutName: childRenderFunc,
  153. })
  154. return tmpl.Execute(out, binding)
  155. }
  156. }