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  }