mirror of https://github.com/gofiber/fiber.git
206 lines
5.1 KiB
Go
206 lines
5.1 KiB
Go
package html
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gofiber/fiber/v2/internal/template/utils"
|
|
)
|
|
|
|
// Engine struct
|
|
type Engine struct {
|
|
// delimiters
|
|
left string
|
|
right string
|
|
// views folder
|
|
directory string
|
|
// http.FileSystem supports embedded files
|
|
fileSystem http.FileSystem
|
|
// views extension
|
|
extension string
|
|
// layout variable name that incapsulates the template
|
|
layout string
|
|
// determines if the engine parsed all templates
|
|
loaded bool
|
|
// reload on each render
|
|
reload bool
|
|
// debug prints the parsed templates
|
|
debug bool
|
|
// lock for funcmap and templates
|
|
mutex sync.RWMutex
|
|
// template funcmap
|
|
funcmap map[string]interface{}
|
|
// templates
|
|
Templates *template.Template
|
|
}
|
|
|
|
// New returns a HTML render engine for Fiber
|
|
func New(directory, extension string) *Engine {
|
|
engine := &Engine{
|
|
left: "{{",
|
|
right: "}}",
|
|
directory: directory,
|
|
extension: extension,
|
|
layout: "embed",
|
|
funcmap: make(map[string]interface{}),
|
|
}
|
|
engine.AddFunc(engine.layout, func() error {
|
|
return fmt.Errorf("layout called unexpectedly.")
|
|
})
|
|
return engine
|
|
}
|
|
|
|
// NewFileSystem ...
|
|
func NewFileSystem(fs http.FileSystem, extension string) *Engine {
|
|
engine := &Engine{
|
|
left: "{{",
|
|
right: "}}",
|
|
directory: "/",
|
|
fileSystem: fs,
|
|
extension: extension,
|
|
layout: "embed",
|
|
funcmap: make(map[string]interface{}),
|
|
}
|
|
engine.AddFunc(engine.layout, func() error {
|
|
return fmt.Errorf("layout called unexpectedly.")
|
|
})
|
|
return engine
|
|
}
|
|
|
|
// Layout defines the variable name that will incapsulate the template
|
|
func (e *Engine) Layout(key string) *Engine {
|
|
e.layout = key
|
|
return e
|
|
}
|
|
|
|
// Delims sets the action delimiters to the specified strings, to be used in
|
|
// templates. An empty delimiter stands for the
|
|
// corresponding default: {{ or }}.
|
|
func (e *Engine) Delims(left, right string) *Engine {
|
|
e.left, e.right = left, right
|
|
return e
|
|
}
|
|
|
|
// AddFunc adds the function to the template's function map.
|
|
// It is legal to overwrite elements of the default actions
|
|
func (e *Engine) AddFunc(name string, fn interface{}) *Engine {
|
|
e.mutex.Lock()
|
|
e.funcmap[name] = fn
|
|
e.mutex.Unlock()
|
|
return e
|
|
}
|
|
|
|
// Reload if set to true the templates are reloading on each render,
|
|
// use it when you're in development and you don't want to restart
|
|
// the application when you edit a template file.
|
|
func (e *Engine) Reload(enabled bool) *Engine {
|
|
e.reload = enabled
|
|
return e
|
|
}
|
|
|
|
// Debug will print the parsed templates when Load is triggered.
|
|
func (e *Engine) Debug(enabled bool) *Engine {
|
|
e.debug = enabled
|
|
return e
|
|
}
|
|
|
|
// Parse is deprecated, please use Load() instead
|
|
func (e *Engine) Parse() error {
|
|
fmt.Println("Parse() is deprecated, please use Load() instead.")
|
|
return e.Load()
|
|
}
|
|
|
|
// 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.debug {
|
|
fmt.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, template string, binding interface{}, layout ...string) error {
|
|
tmpl := e.Templates.Lookup(template)
|
|
if tmpl == nil {
|
|
return fmt.Errorf("render: template %s does not exist", template)
|
|
}
|
|
if len(layout) > 0 && layout[0] != "" {
|
|
lay := e.Templates.Lookup(layout[0])
|
|
if lay == nil {
|
|
return fmt.Errorf("render: layout %s does not exist", layout[0])
|
|
}
|
|
e.mutex.Lock()
|
|
defer e.mutex.Unlock()
|
|
lay.Funcs(map[string]interface{}{
|
|
e.layout: func() error {
|
|
return tmpl.Execute(out, binding)
|
|
},
|
|
})
|
|
return lay.Execute(out, binding)
|
|
}
|
|
return tmpl.Execute(out, binding)
|
|
}
|