Files
goview/view.go
2019-05-07 19:46:40 +08:00

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
}
}