/
render.go
206 lines (194 loc) · 6.61 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Package html is HTML render for http client.
package html
import (
"sync"
"text/template"
"time"
tp "github.com/henrylee2cn/teleport"
"github.com/henrylee2cn/teleport/utils"
)
// CtxMeta the metadata method sets of context
type CtxMeta interface {
// SetMeta sets the header metadata 'key=value' for reply packet.
SetMeta(key, value string)
}
var (
t = template.New("")
tLocker sync.RWMutex
parseFuncs = make(map[string]func(*template.Template), 128)
parseFuncsLocker sync.RWMutex
)
// Delims sets the action delimiters to the specified strings, to be used in
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
// definitions will inherit the settings. An empty delimiter stands for the
// corresponding default: {{ or }}.
// The return value is the template, so calls can be chained.
//
// Note: Must be called before Parse, ParseFiles, and ParseGlob.
//
func Delims(left, right string) {
tLocker.Lock()
defer tLocker.Unlock()
t.Delims(left, right)
}
// Funcs adds the elements of the argument map to the template's function map.
// It must be called before the template is parsed.
// It panics if a value in the map is not a function with appropriate return
// type or if the name cannot be used syntactically as a function in a template.
// It is legal to overwrite elements of the map. The return value is the template,
// so calls can be chained.
func Funcs(funcMap template.FuncMap) {
tLocker.Lock()
defer tLocker.Unlock()
t.Funcs(funcMap)
}
// Option sets options for the template. Options are described by
// strings, either a simple string or "key=value". There can be at
// most one equals sign in an option string. If the option string
// is unrecognized or otherwise invalid, Option panics.
//
// Known options:
//
// missingkey: Control the behavior during execution if a map is
// indexed with a key that is not present in the map.
// "missingkey=default" or "missingkey=invalid"
// The default behavior: Do nothing and continue execution.
// If printed, the result of the index operation is the string
// "<no value>".
// "missingkey=zero"
// The operation returns the zero value for the map type's element.
// "missingkey=error"
// Execution stops immediately with an error.
//
func Option(opt ...string) {
tLocker.Lock()
defer tLocker.Unlock()
t.Option(opt...)
}
// Parse parses text as a template body for t.
// Named template definitions ({{define ...}} or {{block ...}} statements) in text
// define additional templates associated with t and are removed from the
// definition of t itself.
//
// Templates can be redefined in successive calls to Parse.
// A template definition with a body containing only white space and comments
// is considered empty and will not replace an existing template's body.
// This allows using Parse to add new named template definitions without
// overwriting the main template body.
func Parse(name, text string) error {
tLocker.Lock()
defer tLocker.Unlock()
_, err := t.New(name).Parse(text)
return err
}
// ParseFiles parses the named files and associates the resulting templates with
// t. If an error occurs, parsing stops and the returned template is nil;
// otherwise it is t. There must be at least one file.
// Since the templates created by ParseFiles are named by the base
// names of the argument files, t should usually have the name of one
// of the (base) names of the files. If it does not, depending on t's
// contents before calling ParseFiles, t.Execute may fail. In that
// case use t.ExecuteTemplate to execute a valid template.
//
// When parsing multiple files with the same name in different directories,
// the last one mentioned will be the one that results.
func ParseFiles(filenames ...string) (err error) {
tLocker.Lock()
defer func() {
tLocker.Unlock()
if err != nil {
return
}
parseFuncsLocker.Lock()
defer parseFuncsLocker.Unlock()
for _, f := range filenames {
parseFuncs[f] = func(nt *template.Template) {
if _, err := nt.ParseFiles(f); err != nil {
tp.Errorf("ParseFiles: filename: %s, error: %s", f, err.Error())
}
}
}
}()
_, err = t.ParseFiles(filenames...)
return err
}
// ParseGlob creates a new Template and parses the template definitions from the
// files identified by the pattern, which must match at least one file. The
// returned template will have the (base) name and (parsed) contents of the
// first file matched by the pattern. ParseGlob is equivalent to calling
// ParseFiles with the list of files matched by the pattern.
//
// When parsing multiple files with the same name in different directories,
// the last one mentioned will be the one that results.
//
// The pattern syntax is:
//
// pattern:
// { term }
// term:
// '*' matches any sequence of non-Separator characters
// '?' matches any single non-Separator character
// '[' [ '^' ] { character-range } ']'
// character class (must be non-empty)
// c matches character c (c != '*', '?', '\\', '[')
// '\\' c matches character c
//
// character-range:
// c matches character c (c != '\\', '-', ']')
// '\\' c matches character c
// lo '-' hi matches character c for lo <= c <= hi
//
// Match requires pattern to match all of name, not just a substring.
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
//
// On Windows, escaping is disabled. Instead, '\\' is treated as
// path separator.
//
func ParseGlob(pattern string) (err error) {
tLocker.Lock()
defer func() {
tLocker.Unlock()
if err != nil {
return
}
parseFuncsLocker.Lock()
defer parseFuncsLocker.Unlock()
parseFuncs[pattern] = func(nt *template.Template) {
if _, err := nt.ParseGlob(pattern); err != nil {
tp.Errorf("ParseGlob: pattern: %s, error: %s", pattern, err.Error())
}
}
}()
_, err = t.ParseGlob(pattern)
return err
}
// GoTimingRefresh runs a goroutine that periodically refreshes template files.
func GoTimingRefresh(d time.Duration) {
go func() {
ticker := time.NewTicker(d)
for range ticker.C {
parseFuncsLocker.RLock()
nt, _ := t.Clone()
for _, fn := range parseFuncs {
fn(nt)
}
parseFuncsLocker.RUnlock()
tLocker.Lock()
t = nt
tLocker.Unlock()
}
}()
}
// Render renders the initialized html template by name.
func Render(ctxMeta CtxMeta, tmplName string, data interface{}) ([]byte, *tp.Rerror) {
ctxMeta.SetMeta("Content-Type", "text/html; charset=utf-8")
buf := utils.AcquireByteBuffer()
tLocker.RLock()
err := t.ExecuteTemplate(buf, tmplName, data)
tLocker.RUnlock()
if err != nil {
return nil, tp.NewRerror(tp.CodeInternalServerError, "Template Rendering Failed", err.Error())
}
return buf.Bytes(), nil
}