github.com/gofiber/fiber/v2@v2.47.0/internal/template/html/html.go (about) 1 package html 2 3 import ( 4 "fmt" 5 "html/template" 6 "io" 7 "log" 8 "net/http" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 14 "github.com/gofiber/fiber/v2/internal/template/utils" 15 ) 16 17 // Engine struct 18 type Engine struct { 19 // delimiters 20 left string 21 right string 22 // views folder 23 directory string 24 // http.FileSystem supports embedded files 25 fileSystem http.FileSystem 26 // views extension 27 extension string 28 // layout variable name that incapsulates the template 29 layout string 30 // determines if the engine parsed all templates 31 loaded bool 32 // reload on each render 33 reload bool 34 // debug prints the parsed templates 35 debug bool 36 // lock for funcmap and templates 37 mutex sync.RWMutex 38 // template funcmap 39 funcmap map[string]interface{} 40 // templates 41 Templates *template.Template 42 } 43 44 // New returns a HTML render engine for Fiber 45 func New(directory, extension string) *Engine { 46 engine := &Engine{ 47 left: "{{", 48 right: "}}", 49 directory: directory, 50 extension: extension, 51 layout: "embed", 52 funcmap: make(map[string]interface{}), 53 } 54 engine.AddFunc(engine.layout, func() error { 55 return fmt.Errorf("layout called unexpectedly.") 56 }) 57 return engine 58 } 59 60 // NewFileSystem ... 61 func NewFileSystem(fs http.FileSystem, extension string) *Engine { 62 engine := &Engine{ 63 left: "{{", 64 right: "}}", 65 directory: "/", 66 fileSystem: fs, 67 extension: extension, 68 layout: "embed", 69 funcmap: make(map[string]interface{}), 70 } 71 engine.AddFunc(engine.layout, func() error { 72 return fmt.Errorf("layout called unexpectedly.") 73 }) 74 return engine 75 } 76 77 // Layout defines the variable name that will incapsulate the template 78 func (e *Engine) Layout(key string) *Engine { 79 e.layout = key 80 return e 81 } 82 83 // Delims sets the action delimiters to the specified strings, to be used in 84 // templates. An empty delimiter stands for the 85 // corresponding default: {{ or }}. 86 func (e *Engine) Delims(left, right string) *Engine { 87 e.left, e.right = left, right 88 return e 89 } 90 91 // AddFunc adds the function to the template's function map. 92 // It is legal to overwrite elements of the default actions 93 func (e *Engine) AddFunc(name string, fn interface{}) *Engine { 94 e.mutex.Lock() 95 e.funcmap[name] = fn 96 e.mutex.Unlock() 97 return e 98 } 99 100 // Reload if set to true the templates are reloading on each render, 101 // use it when you're in development and you don't want to restart 102 // the application when you edit a template file. 103 func (e *Engine) Reload(enabled bool) *Engine { 104 e.reload = enabled 105 return e 106 } 107 108 // Debug will print the parsed templates when Load is triggered. 109 func (e *Engine) Debug(enabled bool) *Engine { 110 e.debug = enabled 111 return e 112 } 113 114 // Parse is deprecated, please use Load() instead 115 func (e *Engine) Parse() error { 116 log.Println("[Warning] Parse() is deprecated, please use Load() instead.") 117 return e.Load() 118 } 119 120 // Load parses the templates to the engine. 121 func (e *Engine) Load() error { 122 if e.loaded { 123 return nil 124 } 125 // race safe 126 e.mutex.Lock() 127 defer e.mutex.Unlock() 128 e.Templates = template.New(e.directory) 129 130 // Set template settings 131 e.Templates.Delims(e.left, e.right) 132 e.Templates.Funcs(e.funcmap) 133 134 walkFn := func(path string, info os.FileInfo, err error) error { 135 // Return error if exist 136 if err != nil { 137 return err 138 } 139 // Skip file if it's a directory or has no file info 140 if info == nil || info.IsDir() { 141 return nil 142 } 143 // Skip file if it does not equal the given template extension 144 if len(e.extension) >= len(path) || path[len(path)-len(e.extension):] != e.extension { 145 return nil 146 } 147 // Get the relative file path 148 // ./views/html/index.tmpl -> index.tmpl 149 rel, err := filepath.Rel(e.directory, path) 150 if err != nil { 151 return err 152 } 153 // Reverse slashes '\' -> '/' and 154 // partials\footer.tmpl -> partials/footer.tmpl 155 name := filepath.ToSlash(rel) 156 // Remove ext from name 'index.tmpl' -> 'index' 157 name = strings.TrimSuffix(name, e.extension) 158 // name = strings.Replace(name, e.extension, "", -1) 159 // Read the file 160 // #gosec G304 161 buf, err := utils.ReadFile(path, e.fileSystem) 162 if err != nil { 163 return err 164 } 165 // Create new template associated with the current one 166 // This enable use to invoke other templates {{ template .. }} 167 _, err = e.Templates.New(name).Parse(string(buf)) 168 if err != nil { 169 return err 170 } 171 // Debugging 172 if e.debug { 173 log.Printf("views: parsed template: %s\n", name) 174 } 175 return err 176 } 177 // notify engine that we parsed all templates 178 e.loaded = true 179 if e.fileSystem != nil { 180 return utils.Walk(e.fileSystem, e.directory, walkFn) 181 } 182 return filepath.Walk(e.directory, walkFn) 183 } 184 185 // Render will execute the template name along with the given values. 186 func (e *Engine) Render(out io.Writer, template string, binding interface{}, layout ...string) error { 187 tmpl := e.Templates.Lookup(template) 188 if tmpl == nil { 189 return fmt.Errorf("render: template %s does not exist", template) 190 } 191 if len(layout) > 0 && layout[0] != "" { 192 lay := e.Templates.Lookup(layout[0]) 193 if lay == nil { 194 return fmt.Errorf("render: layout %s does not exist", layout[0]) 195 } 196 e.mutex.Lock() 197 defer e.mutex.Unlock() 198 lay.Funcs(map[string]interface{}{ 199 e.layout: func() error { 200 return tmpl.Execute(out, binding) 201 }, 202 }) 203 return lay.Execute(out, binding) 204 } 205 return tmpl.Execute(out, binding) 206 }