/
manager.go
235 lines (216 loc) · 6.23 KB
/
manager.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
package html
import (
"fmt"
"io"
"io/fs"
"regexp"
"strings"
"code.gopub.tech/errors"
"code.gopub.tech/tpl/exp"
"code.gopub.tech/tpl/types"
)
var (
// ErrDuplicatedTplName 重复的模板名称
ErrDuplicatedTplName = fmt.Errorf("duplicated template name")
// ErrTplNotFound 模板未找到
ErrTplNotFound = fmt.Errorf("template not found")
// ErrNilTag 找不到 tag 信息
ErrNilTag = fmt.Errorf("unexpected nil tag")
)
var _ types.TemplateManager = (*tplManager)(nil)
// tplManager HTML 模板管理器
type tplManager struct {
subFs string
textTags []string
voidElements []string
tagPrefix string
attrPrefix string
globalScope exp.Scope
files map[string]*Node
templates map[string]*Node
}
// NewTplManager 新建一个 HTML 模板管理器
func NewTplManager() *tplManager {
return (&tplManager{
files: map[string]*Node{},
templates: map[string]*Node{},
}).
SetTextTags(GetDefaultTextTags()).
SetVoidElements(GetDefaultVoidElements()).
SetTagPrefix(DefaultTagPrefix).
SetAttrPrefix(DefaultAttrPrefix).
SetGlobalScope(exp.EmptyScope())
}
// SetSubFS 设置文件系统前缀 后续 Parse 时 会首先调用 fs.SubFS
func (m *tplManager) SetSubFS(dir string) *tplManager {
if dir != "" {
m.subFs = dir
}
return m
}
// SetTextTags 设置只包含文本的标签
// @param textTags 只包含文本的标签, 如 script, title, style, textarea
func (m *tplManager) SetTextTags(textTags []string) *tplManager {
if textTags != nil {
m.textTags = textTags
}
return m
}
// SetVoidElements 设置空标签
// @param voidElements 空标签不包含闭合斜线也不包含内容, 如 meta, br, img
func (m *tplManager) SetVoidElements(voidElements []string) *tplManager {
if voidElements != nil {
m.voidElements = voidElements
}
return m
}
// SetTagPrefix 设置标签前缀
// @param prefix 要设置的前缀
func (m *tplManager) SetTagPrefix(prefix string) *tplManager {
if prefix != "" {
m.tagPrefix = prefix
}
return m
}
// SetAttrPrefix 设置属性前缀
// @param prefix 要设置的前缀
func (m *tplManager) SetAttrPrefix(prefix string) *tplManager {
if prefix != "" {
m.attrPrefix = prefix
}
return m
}
// SetGlobalScope 设置全局的作用域 比如可以注入一些工具函数等
func (m *tplManager) SetGlobalScope(scope exp.Scope) *tplManager {
m.globalScope = scope
return m
}
// ParseWithSuffix 使用指定的后缀匹配文件系统中的每个文件
// 匹配的文件将会被解析为模板
func (m *tplManager) ParseWithSuffix(fsys fs.FS, suffix string) error {
return m.Parse(fsys, func(path string) bool { return strings.HasSuffix(path, suffix) })
}
// ParseWithRegexp 使用正则表达式匹配指定文件系统中的每个文件
// 匹配的文件将会被解析为模板
func (m *tplManager) ParseWithRegexp(fsys fs.FS, pattern *regexp.Regexp) error {
return m.Parse(fsys, func(path string) bool {
return pattern.MatchString(path)
})
}
// Parse 使用指定的函数匹配文件系统中的每个文件
// 匹配的文件将会被解析为模板
func (m *tplManager) Parse(fsys fs.FS, match func(path string) bool) (err error) {
if m.subFs != "" {
fsys, err = fs.Sub(fsys, m.subFs)
if err != nil {
return err
}
}
return fs.WalkDir(fsys, ".", func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d != nil && !d.IsDir() {
if match(filePath) {
file, err := fsys.Open(filePath)
if err != nil {
return err
}
if err := m.Add(filePath, file); err != nil {
return err
}
}
}
return nil
})
}
// Add 添加模板文件
// @param name 文件名
// @param reader 文件内容
func (m *tplManager) Add(fileName string, reader io.Reader) error {
if _, ok := m.templates[fileName]; ok {
return errors.Errorf("failed to add template `%v`: %w",
fileName, ErrDuplicatedTplName)
}
tz := NewHtmlScanner(reader).
SetTextTags(m.textTags).
SetAttrPrefix(m.attrPrefix)
tokens, err := tz.GetAllTokens()
if err != nil {
return errors.Errorf("failed to read html tokens in template `%v`: %w", fileName, err)
}
p := NewParser().SetVoidElements(m.voidElements)
tree, err := p.ParseTokens(tokens)
if err != nil {
return errors.Errorf("failed to parse html tokens in template `%v`: %w", fileName, err)
}
m.files[fileName] = tree
m.templates[fileName] = tree
return m.addDefinedTpl(fileName, tree)
}
// addDefinedTpl 查找模板文件中定义的可复用模板
func (m *tplManager) addDefinedTpl(fileName string, tree *Node) error {
var (
token = tree.Token
attrPrefix = m.attrPrefix
)
if token != nil {
switch token.Kind {
case TokenKindTag:
// <p :define="name">xxx</p>
// <div :insert="name">yyy</div>
// -->
// <div>xxx</div>
tag := token.Tag
if tag == nil {
return errors.Errorf("failed to add template in %v at %v: %w",
fileName, token.Start, ErrNilTag)
}
attr, ok := tag.AttrMap()[attrPrefix+attrDefine]
if !ok {
break
}
tplName, err := attr.Evaluate(exp.EmptyScope())
if err != nil {
return err
}
if _, has := m.templates[tplName]; has {
return errors.Errorf("failed to add template `%v` defined in %v at %v: %w",
tplName, fileName, attr.ValueStart, ErrDuplicatedTplName)
}
m.templates[tplName] = &Node{ // 定义的模板不包括 :define 本身
Children: tree.GetChildrenWithoutHeadTailBlankText(),
}
}
}
for _, child := range tree.Children {
if err := m.addDefinedTpl(fileName, child); err != nil {
return err
}
}
return nil
}
// Files 返回已解析文件的名称
func (m *tplManager) Files() (result map[string]*Node) {
result = make(map[string]*Node, len(m.files))
for k, v := range m.files {
result[k] = v
}
return
}
// Files 返回已解析模板的名称(一个文件本身是一个模板,其中还可以定义多个模板)
func (m *tplManager) Templates() (result map[string]*Node) {
result = make(map[string]*Node, len(m.templates))
for k, v := range m.templates {
result[k] = v
}
return
}
// GetTemplate implements types.TemplateManager. 获取指定的模板
func (m *tplManager) GetTemplate(name string) (types.Template, error) {
tree := m.templates[name]
if tree == nil {
return nil, errors.Errorf(noSuchTemplate+": %w", name, ErrTplNotFound)
}
return NewTemplate(m, name, tree), nil
}