-
Notifications
You must be signed in to change notification settings - Fork 8
/
gview.go
155 lines (139 loc) · 4.36 KB
/
gview.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
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 视图管理
package gview
import (
"sync"
"bytes"
"errors"
"html/template"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/gspath"
)
// 视图对象
type View struct {
mu sync.RWMutex
paths *gspath.SPath // 模板查找目录(绝对路径)
funcmap map[string]interface{} // FuncMap
contents *gmap.StringStringMap // 已解析的模板文件内容
}
// 视图表
var viewMap = gmap.NewStringInterfaceMap()
// 默认的视图对象
var viewObj = Get(".")
// 输出到模板页面时保留HTML标签原意,不做自动escape处理
func HTML(content string) template.HTML {
return template.HTML(content)
}
// 直接解析模板内容,返回解析后的内容
func ParseContent(content string, params map[string]interface{}) ([]byte, error) {
return viewObj.ParseContent(content, params)
}
// 获取或者创建一个视图对象
func Get(path string) *View {
if r := viewMap.Get(path); r != nil {
return r.(*View)
}
v := New(path)
viewMap.Set(path, v)
return v
}
// 生成一个视图对象
func New(path string) *View {
s := gspath.New()
s.Set(path)
view := &View {
paths : s,
funcmap : make(map[string]interface{}),
contents : gmap.NewStringStringMap(),
}
view.BindFunc("include", view.funcInclude)
return view
}
// 设置模板目录绝对路径
func (view *View) SetPath(path string) error {
return view.paths.Set(path)
}
// 添加模板目录搜索路径
func (view *View) AddPath(path string) error {
return view.paths.Add(path)
}
// 解析模板,返回解析后的内容
func (view *View) Parse(file string, params map[string]interface{}) ([]byte, error) {
path := view.paths.Search(file)
content := view.contents.Get(path)
if content == "" {
content = gfile.GetContents(path)
if content != "" {
view.addMonitor(path)
view.contents.Set(path, content)
}
}
if content == "" {
return nil, errors.New("tpl \"" + file + "\" not found")
}
// 执行模板解析,互斥锁主要是用于funcmap
view.mu.RLock()
defer view.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
if tpl, err := template.New(path).Funcs(view.funcmap).Parse(content); err != nil {
return nil, err
} else {
if err := tpl.Execute(buffer, params); err != nil {
return nil, err
}
}
return buffer.Bytes(), nil
}
// 直接解析模板内容,返回解析后的内容
func (view *View) ParseContent(content string, params map[string]interface{}) ([]byte, error) {
view.mu.RLock()
defer view.mu.RUnlock()
name := gconv.String(ghash.BKDRHash64([]byte(content)))
buffer := bytes.NewBuffer(nil)
if tpl, err := template.New(name).Funcs(view.funcmap).Parse(content); err != nil {
return nil, err
} else {
if err := tpl.Execute(buffer, params); err != nil {
return nil, err
}
}
return buffer.Bytes(), nil
}
// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制
func (view *View) BindFunc(name string, function interface{}) {
view.mu.Lock()
view.funcmap[name] = function
view.mu.Unlock()
}
// 模板内置方法:include
func (view *View) funcInclude(file string, data...map[string]interface{}) template.HTML {
var m map[string]interface{} = nil
if len(data) > 0 {
m = data[0]
}
content, err := view.Parse(file, m)
if err != nil {
return template.HTML(err.Error())
}
return template.HTML(content)
}
// 添加模板文件监控
func (view *View) addMonitor(path string) {
if view.contents.Get(path) == "" {
gfsnotify.Add(path, func(event *gfsnotify.Event) {
if event.IsRemove() {
gfsnotify.Remove(event.Path)
return
}
view.contents.Remove(event.Path)
})
}
}