-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathprocess.go
More file actions
187 lines (161 loc) · 4.5 KB
/
Copy pathprocess.go
File metadata and controls
187 lines (161 loc) · 4.5 KB
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
package mold
import (
"fmt"
"text/template/parse"
)
// processTree traverses the node tree and swaps render and partial declarations with equivalent template calls.
// It returns all referenced templates encountered during the traversal.
func processTree(t *templateFile) ([]nestedFile, error) {
ts, err := processNode(t, nil, 0, t.Tree.Root)
if err != nil {
if err, ok := err.(posErr); ok {
line, col := pos(t.body, err.pos)
return ts, fmt.Errorf("%s:%d:%d: %s: %w", t.Name(), line, col, t.typ, err)
}
}
return ts, nil
}
func processNode(root *templateFile, parent *parse.ListNode, index int, node parse.Node) (ts []nestedFile, err error) {
// appendResult appends the specified templates to the list of template names when there are no errors
appendResult := func(t []nestedFile, err1 error) {
if err1 != nil {
err = err1
}
if err == nil {
ts = append(ts, t...)
}
}
if a, ok := node.(*parse.ActionNode); ok {
if len(a.Pipe.Cmds) > 0 {
funcName, tname, _ := getActionArgs(a.Pipe.Cmds[0])
if err := processActionNode(root, parent, index, node, funcName); err != nil {
return ts, err
}
if funcName == partialFunc.String() && tname != "" {
ts = append(ts, nestedFile{name: tname, typ: partialFunc})
} else if funcName == renderFunc.String() && tname != "" {
ts = append(ts, nestedFile{name: tname, typ: renderFunc})
}
}
}
if w, ok := node.(*parse.WithNode); ok && w != nil {
appendResult(processNode(root, parent, index, w.List))
appendResult(processNode(root, parent, index, w.ElseList))
}
if l, ok := node.(*parse.ListNode); ok && l != nil {
for i, n := range l.Nodes {
appendResult(processNode(root, l, i, n))
}
}
if i, ok := node.(*parse.IfNode); ok && i != nil {
appendResult(processNode(root, parent, index, i.List))
appendResult(processNode(root, parent, index, i.ElseList))
}
if r, ok := node.(*parse.RangeNode); ok && r != nil {
appendResult(processNode(root, parent, index, r.List))
appendResult(processNode(root, parent, index, r.ElseList))
}
return ts, err
}
func processActionNode(root *templateFile, parent *parse.ListNode, index int, node parse.Node, funcName string) error {
actionNode := node.(*parse.ActionNode)
cmd := actionNode.Pipe.Cmds[0]
_, name, field := getActionArgs(cmd)
if name == root.Name() {
return posErr{pos: int(actionNode.Pos), message: "cyclic reference"}
}
// validate for view and partial
if invalidFuncType(root.typ, funcName) {
return posErr{pos: int(actionNode.Pos), message: fmt.Sprintf("%s not supported", funcName)}
}
var arg parse.Node = &parse.DotNode{}
// only handle if the function name is render or partial
switch {
case funcName == partialFunc.String():
if field != nil {
arg = field
}
if name == "" {
return posErr{pos: int(actionNode.Pos), message: `path to partial file is not specified`}
}
case funcName == renderFunc.String():
if name == "" {
name = "body"
}
default:
return nil
}
cmd.Args = []parse.Node{arg}
actionNode.Pipe.Cmds = []*parse.CommandNode{cmd}
tn := &parse.TemplateNode{
NodeType: parse.NodeTemplate,
Pos: actionNode.Pos,
Line: actionNode.Line,
Name: name,
Pipe: actionNode.Pipe,
}
// replace the ActionNode with a TemplateNode.
parent.Nodes[index] = tn
return nil
}
func invalidFuncType(typ templateType, funcName string) bool {
switch typ {
case viewType:
return funcName == renderFunc.String()
case partialType:
return funcName == renderFunc.String() || funcName == partialFunc.String()
}
return false
}
func getActionArgs(cmd *parse.CommandNode) (fn, file string, field *parse.FieldNode) {
if len(cmd.Args) > 0 {
if i, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
fn = i.Ident
}
}
if len(cmd.Args) > 1 {
if s, ok := cmd.Args[1].(*parse.StringNode); ok {
file = s.Text
}
}
if len(cmd.Args) > 2 {
if f, ok := cmd.Args[2].(*parse.FieldNode); ok {
field = f
}
}
return
}
// posErr tracks the position in the template file when a parse error occurs.
type posErr struct {
pos int
message string
}
func (p posErr) Error() string {
return p.message
}
func pos(body string, pos int) (line int, col int) {
line = 1
col = 1
for i, char := range body {
if i >= pos {
break
}
if char == '\n' {
line++
col = 1
} else {
col++
}
}
return line, col
}
type nestingFunc string
func (t nestingFunc) String() string { return string(t) }
const (
renderFunc nestingFunc = "render"
partialFunc nestingFunc = "partial"
)
type nestedFile struct {
name string
typ nestingFunc
}