forked from devfeel/dotweb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
render.go
125 lines (109 loc) · 3.24 KB
/
render.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package dotweb
import (
"errors"
"html/template"
"io"
"path"
"path/filepath"
"sync"
"github.com/devfeel/dotweb/framework/file"
)
// Renderer is the interface that wraps the render method.
type Renderer interface {
SetTemplatePath(path string)
Render(io.Writer, interface{}, Context, ...string) error
RegisterTemplateFunc(string, interface{})
}
type innerRenderer struct {
templatePath string
// Template cache (for FromCache())
enabledCache bool
templateCache map[string]*template.Template
templateCacheMutex sync.RWMutex
// used to manager template func
templateFuncs map[string]interface{}
templateFuncsMutex *sync.RWMutex
}
// Render render view use http/template
func (r *innerRenderer) Render(w io.Writer, data interface{}, ctx Context, tpl ...string) error {
if len(tpl) <= 0 {
return errors.New("no enough render template files")
}
t, err := r.parseFiles(tpl...)
if err != nil {
return err
}
return t.Execute(w, data)
}
// SetTemplatePath set default template path
func (r *innerRenderer) SetTemplatePath(path string) {
r.templatePath = path
}
// RegisterTemplateFunc used to register template func in renderer
func (r *innerRenderer) RegisterTemplateFunc(funcName string, funcHandler interface{}) {
r.templateFuncsMutex.Lock()
r.templateFuncs[funcName] = funcHandler
r.templateFuncsMutex.Unlock()
}
// unescaped inner template func used to encapsulates a known safe HTML document fragment
func unescaped(x string) interface{} { return template.HTML(x) }
// return http/template by gived file name
func (r *innerRenderer) parseFiles(fileNames ...string) (*template.Template, error) {
var realFileNames []string
var filesCacheKey string
var err error
for _, v := range fileNames {
if !file.Exist(v) {
v = path.Join(r.templatePath, v)
}
realFileNames = append(realFileNames, v)
filesCacheKey = filesCacheKey + v
}
var t *template.Template
var exists bool
if r.enabledCache {
// check from chach
t, exists = r.parseFilesFromCache(filesCacheKey)
}
if !exists {
name := filepath.Base(fileNames[0])
t = template.New(name)
if len(r.templateFuncs) > 0 {
t = t.Funcs(r.templateFuncs)
}
t, err = t.ParseFiles(realFileNames...)
if err != nil {
return nil, err
}
r.templateCacheMutex.Lock()
defer r.templateCacheMutex.Unlock()
r.templateCache[filesCacheKey] = t
}
return t, nil
}
func (r *innerRenderer) parseFilesFromCache(filesCacheKey string) (*template.Template, bool) {
r.templateCacheMutex.RLock()
defer r.templateCacheMutex.RUnlock()
t, exists := r.templateCache[filesCacheKey]
return t, exists
}
// registeInnerTemplateFunc registe default support funcs
func registeInnerTemplateFunc(funcMap map[string]interface{}) {
funcMap["unescaped"] = unescaped
}
// NewInnerRenderer create a inner renderer instance
func NewInnerRenderer() Renderer {
r := new(innerRenderer)
r.enabledCache = true
r.templateCache = make(map[string]*template.Template)
r.templateFuncs = make(map[string]interface{})
r.templateFuncsMutex = new(sync.RWMutex)
registeInnerTemplateFunc(r.templateFuncs)
return r
}
// NewInnerRendererNoCache create a inner renderer instance with no cache mode
func NewInnerRendererNoCache() Renderer {
r := NewInnerRenderer().(*innerRenderer)
r.enabledCache = false
return r
}