-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemplate.go
More file actions
283 lines (248 loc) · 6.14 KB
/
template.go
File metadata and controls
283 lines (248 loc) · 6.14 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
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
package tmpl
import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"reflect"
"sync"
)
//Mode is a type that represents one of two modes, Production or Development.
//See CompileMode for details.
type Mode bool
//String prints the mode in a human readable format.
func (m Mode) String() string {
if bool(m) {
return "Production"
}
return "Development"
}
const (
Development Mode = true
Production Mode = false
)
var (
modeChan = make(chan Mode)
modeChange = make(chan Mode)
flocks = newFileLock()
)
func init() {
go modeSpitter()
}
func modeSpitter() {
mode := Development
for {
select {
case modeChan <- mode:
case mode = <-modeChange:
}
}
}
//CompileMode sets the compilation mode for the package. In Development mode,
//templates read in and compile each file it needs to execute every time it needs
//to execute, always getting the most recent changes. In Production mode, templates
//read and compile each file they need only the first time, caching the results
//for subsequent Execute calls. By default, the package is in Production mode.
func CompileMode(mode Mode) {
modeChange <- mode
}
var cache = map[string]*parseTree{}
func newTemplate(file string) *Template {
return &Template{
base: file,
dirty: true,
}
}
type funcDecl struct {
name string
val reflect.Value
}
//Template is the type that represents a template. It is created by using the
//Parse function and dependencies are attached through Blocks and Call.
type Template struct {
//base and globs represent work to be done
base string
globs []string
funcs []funcDecl
dirty bool
compileLk sync.RWMutex
//our parse tree
tree *parseTree
}
//Blocks attaches all of the block definitions in files that match the glob
//patterns to the template for every Execute call so the base template can
//evoke them.
func (t *Template) Blocks(globs ...string) *Template {
t.globs = append(t.globs, globs...)
t.dirty = true
return t
}
//Call attaches a function to the template under the specified name for every
//Execute call so the base template can call them. The second argument must
//be a function, or Call will panic.
func (t *Template) Call(name string, fnc interface{}) *Template {
rv := reflect.ValueOf(fnc)
if rv.Kind() != reflect.Func {
panic(fmt.Errorf("%q is not a function.", fnc))
}
t.funcs = append(t.funcs, funcDecl{name, rv})
t.dirty = true
return t
}
func (t *Template) compile(mode Mode) (err error) {
if err = t.updateBase(mode); err != nil {
return
}
if err = t.updateGlobs(t.globs, mode); err != nil {
return
}
for _, decl := range t.funcs {
t.tree.context.funcs[decl.name] = decl.val
}
t.dirty = false
return
}
func parseFile(file string) (tree *parseTree, err error) {
data, err := ioutil.ReadFile(file)
if err != nil {
return
}
tree, err = parse(lex(data))
if err != nil {
return
}
tree.context.setFile(file)
return
}
//treeFor grabs the parseTree for the specified absolute path, grabbing it from
//the cache if t.compiled is true
func (t *Template) treeFor(abs string, mode Mode) (tree *parseTree, err error) {
flocks.Lock(abs)
defer flocks.Unlock(abs)
if mode == Production {
//check for the cache
if tr, ex := cache[abs]; ex {
tree = tr
return
}
}
tree, err = parseFile(abs)
if err != nil {
return
}
cache[abs] = tree
return
}
func (t *Template) updateBase(mode Mode) (err error) {
abs, err := filepath.Abs(t.base)
if err != nil {
return
}
t.tree, err = t.treeFor(abs, mode)
return
}
func (t *Template) updateGlobs(globs []string, mode Mode) (err error) {
for _, glob := range globs {
err = t.updateGlob(glob, mode)
if err != nil {
return
}
}
return
}
func (t *Template) updateGlob(glob string, mode Mode) (err error) {
files, err := filepath.Glob(glob)
if err != nil {
return
}
for _, file := range files {
err = t.loadBlocks(file, mode)
if err != nil {
return
}
}
return
}
func (t *Template) loadBlocks(file string, mode Mode) (err error) {
abs, err := filepath.Abs(file)
if err != nil {
return
}
tree, err := t.treeFor(abs, mode)
if err != nil {
return
}
err = t.updateBlocks(file, tree.context.blocks)
return
}
func (t *Template) updateBlocks(file string, blocks map[string]*executeBlockValue) (err error) {
tblk := t.tree.context.blocks
for id, bl := range blocks {
if _, ex := tblk[id]; ex {
err = fmt.Errorf("%q: %q already exists from %q", file, id, bl.file)
return
}
bl.file = file
tblk[id] = bl
}
return
}
func (t *Template) runCompilation(globs []string, mode Mode) (err error) {
//grab the compile lock
t.compileLk.Lock()
defer t.compileLk.Unlock()
//unset the tree and compile it
t.tree = nil
if err = t.compile(mode); err != nil {
return
}
t.tree.context.dup()
err = t.updateGlobs(globs, mode)
return
}
func (t *Template) lockedUpdateGlobs(globs []string, mode Mode) (err error) {
//grab the compile lock
t.compileLk.Lock()
defer t.compileLk.Unlock()
//load them in
err = t.updateGlobs(globs, mode)
return
}
//Execute runs the template with the specified context attaching all the block
//definitions in the files that match the given globs sending the output to
//w. Any errors during the compilation of any files that have to be compiled
//(see the discussion on Modes) or during the execution of the template are
//returned.
func (t *Template) Execute(w io.Writer, ctx interface{}, globs ...string) (err error) {
//grab the mode for this execute
mode := <-modeChan
//if we're in dev or its dirty we need to fully recompile
if mode == Development || t.dirty {
//run the compilation
if err = t.runCompilation(globs, mode); err != nil {
return
}
//if we had globs we have to do a restore
if len(globs) > 0 {
defer t.tree.context.restore()
}
} else if len(globs) > 0 {
//it wasn't dirty or in development, but we have globs so set up
//a restore
defer t.tree.context.restore()
//and compile them in
if err = t.lockedUpdateGlobs(globs, mode); err != nil {
return
}
}
//execute!
t.compileLk.RLock()
defer t.compileLk.RUnlock()
return t.tree.Execute(w, ctx)
}
//Parse creates a new Template with the specified file acting as the base
//template.
func Parse(file string) (t *Template) {
t = newTemplate(file)
return
}