mirror of
https://github.com/foolin/goview.git
synced 2025-09-26 19:01:15 +08:00
194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
package goview
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// HTMLContentType const templateEngineKey = "httpx_templateEngine"
|
|
var HTMLContentType = []string{"text/html; charset=utf-8"}
|
|
|
|
// DefaultConfig default config
|
|
var DefaultConfig = Config{
|
|
Root: "views",
|
|
Extension: ".html",
|
|
Master: "layouts/master",
|
|
Partials: []string{},
|
|
Funcs: make(template.FuncMap),
|
|
DisableCache: false,
|
|
Delims: Delims{Left: "{{", Right: "}}"},
|
|
}
|
|
|
|
// ViewEngine view template engine
|
|
type ViewEngine struct {
|
|
config Config
|
|
tplMap map[string]*template.Template
|
|
tplMutex sync.RWMutex
|
|
fileHandler FileHandler
|
|
}
|
|
|
|
// Config configuration options
|
|
type Config struct {
|
|
Root string //view root
|
|
Extension string //template extension
|
|
Master string //template master
|
|
Partials []string //template partial, such as head, foot
|
|
Funcs template.FuncMap //template functions
|
|
DisableCache bool //disable cache, debug mode
|
|
Delims Delims //delimeters
|
|
}
|
|
|
|
// M map interface for data
|
|
type M map[string]interface{}
|
|
|
|
// Delims delims for template
|
|
type Delims struct {
|
|
Left string
|
|
Right string
|
|
}
|
|
|
|
// FileHandler file handler interface
|
|
type FileHandler func(config Config, tplFile string) (content string, err error)
|
|
|
|
// New new template engine
|
|
func New(config Config) *ViewEngine {
|
|
return &ViewEngine{
|
|
config: config,
|
|
tplMap: make(map[string]*template.Template),
|
|
tplMutex: sync.RWMutex{},
|
|
fileHandler: DefaultFileHandler(),
|
|
}
|
|
}
|
|
|
|
// Default new default template engine
|
|
func Default() *ViewEngine {
|
|
return New(DefaultConfig)
|
|
}
|
|
|
|
// Render render template with http.ResponseWriter
|
|
func (e *ViewEngine) Render(w http.ResponseWriter, statusCode int, name string, data interface{}) error {
|
|
header := w.Header()
|
|
if val := header["Content-Type"]; len(val) == 0 {
|
|
header["Content-Type"] = HTMLContentType
|
|
}
|
|
w.WriteHeader(statusCode)
|
|
return e.executeRender(w, name, data)
|
|
}
|
|
|
|
// RenderWriter render template with io.Writer
|
|
func (e *ViewEngine) RenderWriter(w io.Writer, name string, data interface{}) error {
|
|
return e.executeRender(w, name, data)
|
|
}
|
|
|
|
func (e *ViewEngine) executeRender(out io.Writer, name string, data interface{}) error {
|
|
useMaster := true
|
|
if filepath.Ext(name) == e.config.Extension {
|
|
useMaster = false
|
|
name = strings.TrimSuffix(name, e.config.Extension)
|
|
|
|
}
|
|
return e.executeTemplate(out, name, data, useMaster)
|
|
}
|
|
|
|
func (e *ViewEngine) executeTemplate(out io.Writer, name string, data interface{}, useMaster bool) error {
|
|
var tpl *template.Template
|
|
var err error
|
|
var ok bool
|
|
|
|
allFuncs := make(template.FuncMap, 0)
|
|
allFuncs["include"] = func(layout string) (template.HTML, error) {
|
|
buf := new(bytes.Buffer)
|
|
err := e.executeTemplate(buf, layout, data, false)
|
|
return template.HTML(buf.String()), err
|
|
}
|
|
|
|
// Get the plugin collection
|
|
for k, v := range e.config.Funcs {
|
|
allFuncs[k] = v
|
|
}
|
|
|
|
e.tplMutex.RLock()
|
|
tpl, ok = e.tplMap[name]
|
|
e.tplMutex.RUnlock()
|
|
|
|
exeName := name
|
|
if useMaster && e.config.Master != "" {
|
|
exeName = e.config.Master
|
|
}
|
|
|
|
if !ok || e.config.DisableCache {
|
|
tplList := make([]string, 0)
|
|
if useMaster {
|
|
//render()
|
|
if e.config.Master != "" {
|
|
tplList = append(tplList, e.config.Master)
|
|
}
|
|
}
|
|
tplList = append(tplList, name)
|
|
tplList = append(tplList, e.config.Partials...)
|
|
|
|
// Loop through each template and test the full path
|
|
tpl = template.New(name).Funcs(allFuncs).Delims(e.config.Delims.Left, e.config.Delims.Right)
|
|
for _, v := range tplList {
|
|
var data string
|
|
data, err = e.fileHandler(e.config, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var tmpl *template.Template
|
|
if v == name {
|
|
tmpl = tpl
|
|
} else {
|
|
tmpl = tpl.New(v)
|
|
}
|
|
_, err = tmpl.Parse(data)
|
|
if err != nil {
|
|
return fmt.Errorf("ViewEngine render parser name:%v, error: %v", v, err)
|
|
}
|
|
}
|
|
e.tplMutex.Lock()
|
|
e.tplMap[name] = tpl
|
|
e.tplMutex.Unlock()
|
|
}
|
|
|
|
// Display the content to the screen
|
|
err = tpl.Funcs(allFuncs).ExecuteTemplate(out, exeName, data)
|
|
if err != nil {
|
|
return fmt.Errorf("ViewEngine execute template error: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetFileHandler set file handler
|
|
func (e *ViewEngine) SetFileHandler(handle FileHandler) {
|
|
if handle == nil {
|
|
panic("FileHandler can't set nil!")
|
|
}
|
|
e.fileHandler = handle
|
|
}
|
|
|
|
// DefaultFileHandler new default file handler
|
|
func DefaultFileHandler() FileHandler {
|
|
return func(config Config, tplFile string) (content string, err error) {
|
|
// Get the absolute path of the root template
|
|
path, err := filepath.Abs(config.Root + string(os.PathSeparator) + tplFile + config.Extension)
|
|
if err != nil {
|
|
return "", fmt.Errorf("ViewEngine path:%v error: %v", path, err)
|
|
}
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("ViewEngine render read name:%v, path:%v, error: %v", tplFile, path, err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
}
|