html.go 4.2 KB

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