forked from beego/beego
-
Notifications
You must be signed in to change notification settings - Fork 181
/
template.go
406 lines (373 loc) · 11.5 KB
/
template.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils"
)
var (
beegoTplFuncMap = make(template.FuncMap)
beeViewPathTemplateLocked = false
// beeViewPathTemplates caching map and supported template file extensions per view
beeViewPathTemplates = make(map[string]map[string]*template.Template)
templatesLock sync.RWMutex
// beeTemplateExt stores the template extension which will build
beeTemplateExt = []string{"tpl", "html", "gohtml"}
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
beeTemplateEngines = map[string]templatePreProcessor{}
beeTemplateFS = defaultFSFunc
)
// ExecuteTemplate applies the template with name to the specified data object,
// writing the output to wr.
// A template will be executed safely in parallel.
func ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
return ExecuteViewPathTemplate(wr, name, BConfig.WebConfig.ViewsPath, data)
}
// ExecuteViewPathTemplate applies the template with name and from specific viewPath to the specified data object,
// writing the output to wr.
// A template will be executed safely in parallel.
func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error {
if BConfig.RunMode == DEV {
templatesLock.RLock()
defer templatesLock.RUnlock()
}
if beeTemplates, ok := beeViewPathTemplates[viewPath]; ok {
if t, ok := beeTemplates[name]; ok {
var err error
if t.Lookup(name) != nil {
err = t.ExecuteTemplate(wr, name, data)
} else {
err = t.Execute(wr, data)
}
if err != nil {
logs.Trace("template Execute err:", err)
}
return err
}
panic("can't find templatefile in the path:" + viewPath + "/" + name)
}
panic("Unknown view path:" + viewPath)
}
func init() {
beegoTplFuncMap["dateformat"] = DateFormat
beegoTplFuncMap["date"] = Date
beegoTplFuncMap["compare"] = Compare
beegoTplFuncMap["compare_not"] = CompareNot
beegoTplFuncMap["not_nil"] = NotNil
beegoTplFuncMap["not_null"] = NotNil
beegoTplFuncMap["substr"] = Substr
beegoTplFuncMap["html2str"] = HTML2str
beegoTplFuncMap["str2html"] = Str2html
beegoTplFuncMap["htmlquote"] = Htmlquote
beegoTplFuncMap["htmlunquote"] = Htmlunquote
beegoTplFuncMap["renderform"] = RenderForm
beegoTplFuncMap["assets_js"] = AssetsJs
beegoTplFuncMap["assets_css"] = AssetsCSS
beegoTplFuncMap["config"] = GetConfig
beegoTplFuncMap["map_get"] = MapGet
// Comparisons
beegoTplFuncMap["eq"] = eq // ==
beegoTplFuncMap["ge"] = ge // >=
beegoTplFuncMap["gt"] = gt // >
beegoTplFuncMap["le"] = le // <=
beegoTplFuncMap["lt"] = lt // <
beegoTplFuncMap["ne"] = ne // !=
beegoTplFuncMap["urlfor"] = URLFor // build a URL to match a Controller and it's method
}
// AddFuncMap let user to register a func in the template.
func AddFuncMap(key string, fn interface{}) error {
beegoTplFuncMap[key] = fn
return nil
}
type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error)
type templateFile struct {
root string
files map[string][]string
}
// visit will make the paths into two part,the first is subDir (without tf.root),the second is full path(without tf.root).
// if tf.root="views" and
// paths is "views/errors/404.html",the subDir will be "errors",the file will be "errors/404.html"
// paths is "views/admin/errors/404.html",the subDir will be "admin/errors",the file will be "admin/errors/404.html"
func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error {
if f == nil {
return err
}
if f.IsDir() || (f.Mode()&os.ModeSymlink) > 0 {
return nil
}
if !HasTemplateExt(paths) {
return nil
}
replace := strings.NewReplacer("\\", "/")
file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/")
subDir := filepath.Dir(file)
tf.files[subDir] = append(tf.files[subDir], file)
return nil
}
// HasTemplateExt return this path contains supported template extension of beego or not.
func HasTemplateExt(paths string) bool {
for _, v := range beeTemplateExt {
if strings.HasSuffix(paths, "."+v) {
return true
}
}
return false
}
// AddTemplateExt add new extension for template.
func AddTemplateExt(ext string) {
for _, v := range beeTemplateExt {
if v == ext {
return
}
}
beeTemplateExt = append(beeTemplateExt, ext)
}
// AddViewPath adds a new path to the supported view paths.
//Can later be used by setting a controller ViewPath to this folder
//will panic if called after beego.Run()
func AddViewPath(viewPath string) error {
if beeViewPathTemplateLocked {
if _, exist := beeViewPathTemplates[viewPath]; exist {
return nil //Ignore if viewpath already exists
}
panic("Can not add new view paths after beego.Run()")
}
beeViewPathTemplates[viewPath] = make(map[string]*template.Template)
return BuildTemplate(viewPath)
}
func lockViewPaths() {
beeViewPathTemplateLocked = true
}
// BuildTemplate will build all template files in a directory.
// it makes beego can render any template file in view directory.
func BuildTemplate(dir string, files ...string) error {
var err error
fs := beeTemplateFS()
f, err := fs.Open(dir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.New("dir open err")
}
defer f.Close()
beeTemplates, ok := beeViewPathTemplates[dir]
if !ok {
panic("Unknown view path: " + dir)
}
self := &templateFile{
root: dir,
files: make(map[string][]string),
}
err = Walk(fs, dir, func(path string, f os.FileInfo, err error) error {
return self.visit(path, f, err)
})
if err != nil {
fmt.Printf("Walk() returned %v\n", err)
return err
}
buildAllFiles := len(files) == 0
for _, v := range self.files {
for _, file := range v {
if buildAllFiles || utils.InSlice(file, files) {
templatesLock.Lock()
ext := filepath.Ext(file)
var t *template.Template
if len(ext) == 0 {
t, err = getTemplate(self.root, fs, file, v...)
} else if fn, ok := beeTemplateEngines[ext[1:]]; ok {
t, err = fn(self.root, file, beegoTplFuncMap)
} else {
t, err = getTemplate(self.root, fs, file, v...)
}
if err != nil {
logs.Error("parse template err:", file, err)
templatesLock.Unlock()
return err
}
beeTemplates[file] = t
templatesLock.Unlock()
}
}
}
return nil
}
func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *template.Template) (*template.Template, [][]string, error) {
var fileAbsPath string
var rParent string
var err error
if strings.HasPrefix(file, "../") {
rParent = filepath.Join(filepath.Dir(parent), file)
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
} else {
rParent = file
fileAbsPath = filepath.Join(root, file)
}
f, err := fs.Open(fileAbsPath)
if err != nil {
panic("can't find template file:" + file)
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, [][]string{}, err
}
t, err = t.New(file).Parse(string(data))
if err != nil {
return nil, [][]string{}, err
}
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"")
allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, m := range allSub {
if len(m) == 2 {
tl := t.Lookup(m[1])
if tl != nil {
continue
}
if !HasTemplateExt(m[1]) {
continue
}
_, _, err = getTplDeep(root, fs, m[1], rParent, t)
if err != nil {
return nil, [][]string{}, err
}
}
}
return t, allSub, nil
}
func getTemplate(root string, fs http.FileSystem, file string, others ...string) (t *template.Template, err error) {
t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap)
var subMods [][]string
t, subMods, err = getTplDeep(root, fs, file, "", t)
if err != nil {
return nil, err
}
t, err = _getTemplate(t, root, fs, subMods, others...)
if err != nil {
return nil, err
}
return
}
func _getTemplate(t0 *template.Template, root string, fs http.FileSystem, subMods [][]string, others ...string) (t *template.Template, err error) {
t = t0
for _, m := range subMods {
if len(m) == 2 {
tpl := t.Lookup(m[1])
if tpl != nil {
continue
}
//first check filename
for _, otherFile := range others {
if otherFile == m[1] {
var subMods1 [][]string
t, subMods1, err = getTplDeep(root, fs, otherFile, "", t)
if err != nil {
logs.Trace("template parse file err:", err)
} else if len(subMods1) > 0 {
t, err = _getTemplate(t, root, fs, subMods1, others...)
}
break
}
}
//second check define
for _, otherFile := range others {
var data []byte
fileAbsPath := filepath.Join(root, otherFile)
f, err := fs.Open(fileAbsPath)
if err != nil {
f.Close()
logs.Trace("template file parse error, not success open file:", err)
continue
}
data, err = ioutil.ReadAll(f)
f.Close()
if err != nil {
logs.Trace("template file parse error, not success read file:", err)
continue
}
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"")
allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, sub := range allSub {
if len(sub) == 2 && sub[1] == m[1] {
var subMods1 [][]string
t, subMods1, err = getTplDeep(root, fs, otherFile, "", t)
if err != nil {
logs.Trace("template parse file err:", err)
} else if len(subMods1) > 0 {
t, err = _getTemplate(t, root, fs, subMods1, others...)
if err != nil {
logs.Trace("template parse file err:", err)
}
}
break
}
}
}
}
}
return
}
type templateFSFunc func() http.FileSystem
func defaultFSFunc() http.FileSystem {
return FileSystem{}
}
// SetTemplateFSFunc set default filesystem function
func SetTemplateFSFunc(fnt templateFSFunc) {
beeTemplateFS = fnt
}
// SetViewsPath sets view directory path in beego application.
func SetViewsPath(path string) *App {
BConfig.WebConfig.ViewsPath = path
return BeeApp
}
// SetStaticPath sets static directory path and proper url pattern in beego application.
// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public".
func SetStaticPath(url string, path string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
if url != "/" {
url = strings.TrimRight(url, "/")
}
BConfig.WebConfig.StaticDir[url] = path
return BeeApp
}
// DelStaticPath removes the static folder setting in this url pattern in beego application.
func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
if url != "/" {
url = strings.TrimRight(url, "/")
}
delete(BConfig.WebConfig.StaticDir, url)
return BeeApp
}
// AddTemplateEngine add a new templatePreProcessor which support extension
func AddTemplateEngine(extension string, fn templatePreProcessor) *App {
AddTemplateExt(extension)
beeTemplateEngines[extension] = fn
return BeeApp
}