-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathutils.go
410 lines (332 loc) · 7.61 KB
/
utils.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
407
408
409
410
package template
import (
"fmt"
"io/ioutil"
"path"
"strconv"
"strings"
"text/template/parse"
)
type treeReceiver func(tree *parse.Tree, kind Kind) error
/**
共享模板的名字, 共享模板永远不被执行.
用于优化 Clone(), 共享 common, FuncMap.
*/
const shareName = "57b11edbe4825b57ab27b7beab9848ed"
/**
从文件进行读取, 解析, 命名, hack
filename 是经过 clear 后的绝对路径.
*/
func parseFiles(t *Template, filename ...string) error {
var (
kind Kind
)
// 再次检查使用的模板名
names := map[string]bool{}
for _, name := range filename {
switch path.Ext(name) {
case "":
return fmt.Errorf("template: invalid file name: %q", name)
case ".html":
kind = HTML
default:
kind = TEXT
}
// 懒方法, 直接跳过
if t.base.Lookup(name).IsValid() {
continue
}
b, err := ioutil.ReadFile(name)
if err != nil {
return err
}
err = parseText(t, names, kind, name, string(b))
if err != nil {
return err
}
}
filename = []string{}
for k, toload := range names {
if toload {
filename = append(filename, k)
}
}
// 递归载入文件
if len(filename) != 0 {
return parseFiles(t, filename...)
}
return nil
}
/**
parseText 需要知道第一个 tree 的 kind. 以便添加到 t.
可能会载入新的文件, 产生模板名称对应的模板尚未载入.
直到全部解析完才会完整载入.
参数 ns 是给 tree 的 uri 命名.
*/
func parseText(t *Template, names map[string]bool,
kind Kind, ns, text string) error {
var name string
trees, err := parse.Parse(ns, text,
t.leftDelim, t.rightDelim, placeholderFuncs, t.base.funcs)
if err != nil {
return err
}
rootdir := t.RootDir()
dir := absPath(ns)
for from, tree := range trees {
name = from
// define 内嵌模板不能有扩展名
if name != ns {
if path.Ext(name) != "" {
return fmt.Errorf(
"template: extension are not supported on define %q", from)
}
name = relToURI(rootdir, dir, name)
}
if name == "" {
return fmt.Errorf("template: is invalid on define %q", from)
}
// 需要再次检查模板是否被载入
if t.base.Lookup(name).IsValid() {
return fmt.Errorf(
"template: redefinition of %q", name)
}
tree.Name = name
tree.ParseName = ns
err = hackNode(t, names, name, tree.Root, -1, tree.Root)
if err == nil {
_, err = t.AddParseTree(kind, tree)
}
if err != nil {
return err
}
}
names[ns] = false
return nil
}
/**
hackNode 对模板中 template/import 的目标模板重新命名.
规则:
没有扩展名当作内嵌模板, 反之当作文件模板.
目标名称变更为绝对路径名.
所有 template 用 import 替换. 格式为:
import "from" "target" args...
参数:
t 模板.
names 所有使用的模板需要检查是否已经载入.
from 来源模板名. 绝对路径.
node 待 hack 的原始 parse.Node.
返回:
是否有错误发生的错误.
*/
func hackNode(t *Template, names map[string]bool, from string,
list *parse.ListNode, i int, node parse.Node) error {
var (
pipe *parse.PipeNode
args []parse.Node
target *parse.StringNode
)
rootdir := t.RootDir()
switch n := node.(type) {
default:
return nil
case *parse.ListNode:
for i, node := range n.Nodes {
err := hackNode(t, names, from, n, i, node)
if err != nil {
return err
}
}
return nil
case *parse.TemplateNode:
args = make([]parse.Node, 3)
args[0] = parse.NewIdentifier("import").SetPos(n.Pos)
// from, 保存调用者
args[1] = &parse.StringNode{
NodeType: parse.NodeString,
Pos: n.Position(), // 伪造
Quoted: strconv.Quote(from),
Text: from,
}
// target, 重建目标
args[2] = &parse.StringNode{
NodeType: parse.NodeString,
Pos: n.Position(), // 伪造
Quoted: strconv.Quote(n.Name),
Text: n.Name,
}
// 复制其它参数
pipe = n.Pipe
if pipe != nil &&
len(pipe.Cmds) != 0 &&
pipe.Cmds[0].NodeType == parse.NodeCommand {
for _, arg := range pipe.Cmds[0].Args {
args = append(args, arg)
}
} else {
if pipe == nil {
pipe = &parse.PipeNode{
NodeType: parse.NodePipe,
Pos: n.Position(), // 伪造
Line: n.Line,
Cmds: []*parse.CommandNode{
&parse.CommandNode{
NodeType: parse.NodeCommand,
Pos: n.Position(),
},
},
}
}
}
pipe.Cmds[0].Args = args
// 改成 ActionNode
list.Nodes[i] = &parse.ActionNode{
NodeType: parse.NodeAction,
Pos: n.Pos,
Line: n.Line,
Pipe: pipe,
}
case *parse.ActionNode:
pipe = n.Pipe
if pipe == nil ||
len(pipe.Decl) != 0 ||
len(pipe.Cmds) == 0 ||
pipe.Cmds[0].NodeType != parse.NodeCommand ||
len(pipe.Cmds[0].Args) == 0 ||
pipe.Cmds[0].Args[0].Type() != parse.NodeIdentifier ||
pipe.Cmds[0].Args[0].String() != "import" {
return nil
}
args = make([]parse.Node, len(pipe.Cmds[0].Args)+1)
args[0] = pipe.Cmds[0].Args[0]
// from, 增加调用者来源
args[1] = &parse.StringNode{
NodeType: parse.NodeString,
Pos: args[0].Position(), // 伪造
Quoted: strconv.Quote(from),
Text: from,
}
// 复制其它参数
for i, arg := range pipe.Cmds[0].Args {
if i != 0 {
args[i+1] = arg
}
}
pipe.Cmds[0].Args = args
}
// 处理目标模板 args[2], 有可能是变量.
target, _ = args[2].(*parse.StringNode)
if target == nil {
return nil
}
// 计算目标路径
name := relToURI(rootdir, absPath(from), target.Text)
if name == "" {
return fmt.Errorf(
"template: is invalid on define %q", target.Text)
}
// 判断文件模板是否载入
if path.Ext(name) != "" &&
!t.base.Lookup(name).IsValid() {
names[name] = true
}
target.Text = name
target.Quoted = strconv.Quote(name)
return nil
}
func absPath(name string) string {
if path.Ext(name) != "" {
return path.Dir(name)
}
return path.Dir(path.Dir(name))
}
/**
absToURI 根据绝对 uri 路径 rootdir, 计算绝对路径 name 的 uri 路径.
如果返回值为 "" 表示 name 非法.
*/
func absToURI(rootdir, name string) string {
name = cleanURI(name)
if name == "" ||
len(name) <= len(rootdir) ||
name[len(rootdir)] != '/' ||
name[:len(rootdir)] != rootdir {
return ""
}
return name
}
/**
clearSlash 清理 s 中的反斜杠, 连续反斜杠, 连续斜杠.
*/
func clearSlash(s string) string {
var to []byte
if len(s) == 0 {
return s
}
b := -1
for i := 0; i < len(s); i++ {
c := s[i]
if c == '\\' || c == '/' {
if b == -1 {
b = i
continue
}
c = '/'
}
if b != -1 {
if to == nil {
// 连续斜杠
if c == '/' && (s[i-1] == '\\' || s[i-1] == '/') {
continue
}
to = make([]byte, 0, len(s))
to = append(to, s[:b]...)
}
b = -1
to = append(to, '/')
}
if to != nil {
to = append(to, c)
}
}
if len(to) == 0 {
return s
}
return string(to)
}
/**
cleanURI 返回 uri 的最短路径写法.
*/
func cleanURI(uri string) string {
uri = clearSlash(uri)
if uri == "" {
return ""
}
if strings.Index(uri, "/.") != -1 {
uri = path.Clean(uri)
}
if uri[0] == '.' {
return ""
}
return uri
}
/**
relToURI 根据绝对路径 rootdir 和 dir, 计算 name 的绝对 uri 路径.
如果返回值为 "" 表示 name 非法.
参数:
rootdir 根目录绝对路径
dir 当前目录绝对路径, 如果 dir 为空, 以 rootdir 替代.
name 已经 clean 后的相对路径.
以 "/" 开头以 rootdir 计算, 否则以 dir 计算.
*/
func relToURI(rootdir, dir, name string) string {
if name == "" {
return ""
}
if name[0] == '/' {
return absToURI(rootdir, rootdir+name)
}
if dir == "" {
return absToURI(rootdir, rootdir+"/"+name)
}
return absToURI(rootdir, dir+"/"+name)
}