-
Notifications
You must be signed in to change notification settings - Fork 0
/
template.go
139 lines (123 loc) · 3.49 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
package treetop
import (
"errors"
"fmt"
"html/template"
"sort"
"strconv"
"strings"
"text/template/parse"
)
type TemplateLoader struct {
Load func(string) (string, error)
Funcs template.FuncMap
}
func NewTemplateLoader(funcs template.FuncMap, load func(string) (string, error)) *TemplateLoader {
return &TemplateLoader{
Load: load,
Funcs: funcs,
}
}
func (tl TemplateLoader) ViewTemplate(view *View) (*template.Template, error) {
if view == nil {
return nil, nil
}
var out *template.Template
queue := viewQueue{}
queue.add(view)
for !queue.empty() {
v, _ := queue.next()
var t *template.Template
if out == nil {
out = template.New(v.Defines).Funcs(tl.Funcs)
t = out
} else {
t = out.New(v.Defines)
}
templateString, err := tl.Load(v.Template)
if err != nil {
return nil, fmt.Errorf(`failed to load template %#v: %s`, v.Template, err)
}
if _, err := t.Parse(templateString); err != nil {
return nil, fmt.Errorf(`failed to parse template %#v: %s`, v.Template, err)
}
// require template to declare a template/block node for each direct subview name
if err := checkTemplateForBlockNames(t, v.SubViews); err != nil {
return nil, fmt.Errorf("template %s: %s", v.Template, err)
}
for _, sub := range v.SubViews {
if sub != nil {
queue.add(sub)
}
}
}
return out, nil
}
// utilities ---
var errEmptyViewQueue = errors.New("empty view queue")
// viewQueue simple queue implementation used for breath first traversal
//
// NB: this is only suitable for localized short-lived queues since the underlying
// array will not deallocate pointers
type viewQueue struct {
offset int
items []*View
}
func (q *viewQueue) add(v *View) {
q.items = append(q.items, v)
}
func (q *viewQueue) next() (*View, error) {
if q.empty() {
return nil, errEmptyViewQueue
}
next := q.items[q.offset]
q.offset++
return next, nil
}
func (q *viewQueue) empty() bool {
return q.offset >= len(q.items)
}
// checkTemplateForBlockNames will scan the parsed templates for blocks/template slots
// that match the declared block names. If a block naming is not present, return an error
func checkTemplateForBlockNames(tmpl *template.Template, subviews map[string]*View) error {
parsedBlocks := make(map[string]bool)
for _, tplName := range listTemplateNodeName(tmpl.Tree.Root) {
parsedBlocks[tplName] = true
}
var missing []string
for blockName := range subviews {
if _, ok := parsedBlocks[blockName]; !ok {
missing = append(missing, strconv.Quote(blockName))
}
}
if len(missing) == 0 {
return nil
}
sort.Strings(missing)
return fmt.Errorf("missing template declaration(s) for sub view blocks: %s", strings.Join(missing, ", "))
}
// listTemplateNodeName will scan a parsed template tree for template nodes
// and list all template names found
func listTemplateNodeName(list *parse.ListNode) (names []string) {
if list == nil {
return
}
for _, node := range list.Nodes {
switch n := node.(type) {
case *parse.TemplateNode:
names = append(names, n.Name)
case *parse.IfNode:
names = append(names, listTemplateNodeName(n.List)...)
names = append(names, listTemplateNodeName(n.ElseList)...)
case *parse.RangeNode:
names = append(names, listTemplateNodeName(n.List)...)
names = append(names, listTemplateNodeName(n.ElseList)...)
case *parse.WithNode:
names = append(names, listTemplateNodeName(n.List)...)
names = append(names, listTemplateNodeName(n.ElseList)...)
case *parse.ListNode:
names = append(names, listTemplateNodeName(n)...)
}
}
return
}