@@ -16,6 +16,7 @@ import (
16
16
"strconv"
17
17
"strings"
18
18
"sync"
19
+ "sync/atomic"
19
20
)
20
21
21
22
// ErrorCSSClass httml CSS error class name
@@ -24,16 +25,12 @@ var ErrorCSSClass = "hasError"
24
25
// TemplateLoader object handles loading and parsing of templates.
25
26
// Everything below the application's views directory is treated as a template.
26
27
type TemplateLoader struct {
27
- // Template data and implementation
28
- templatesAndEngineList []TemplateEngine
29
- // If an error was encountered parsing the templates, it is stored here.
30
- compileError * Error
31
28
// Paths to search for templates, in priority order.
32
29
paths []string
33
- // Map from template name to the path from whence it was loaded.
34
- TemplatePaths map [ string ] string
35
- // A map of looked up template results
36
- TemplateMap map [ string ] Template
30
+ // load version seed for templates
31
+ loadVersionSeed int
32
+ // A templateRuntime of looked up template results
33
+ runtimeLoader atomic. Value
37
34
// Lock to prevent concurrent map writes
38
35
templateMutex sync.Mutex
39
36
}
@@ -55,34 +52,65 @@ var whiteSpacePattern = regexp.MustCompile(`\s+`)
55
52
func NewTemplateLoader (paths []string ) * TemplateLoader {
56
53
loader := & TemplateLoader {
57
54
paths : paths ,
58
- templateMutex : sync.Mutex {},
59
55
}
60
56
return loader
61
57
}
62
58
59
+ // WatchDir returns true of directory doesn't start with . (dot)
60
+ // otherwise false
61
+ func (loader * TemplateLoader ) WatchDir (info os.FileInfo ) bool {
62
+ // Watch all directories, except the ones starting with a dot.
63
+ return ! strings .HasPrefix (info .Name (), "." )
64
+ }
65
+
66
+ // WatchFile returns true of file doesn't start with . (dot)
67
+ // otherwise false
68
+ func (loader * TemplateLoader ) WatchFile (basename string ) bool {
69
+ // Watch all files, except the ones starting with a dot.
70
+ return ! strings .HasPrefix (basename , "." )
71
+ }
72
+
73
+ // DEPRECATED Use TemplateLang, will be removed in future release
74
+ func (loader * TemplateLoader ) Template (name string ) (tmpl Template , err error ) {
75
+ runtimeLoader := loader .runtimeLoader .Load ().(* templateRuntime )
76
+ return runtimeLoader .TemplateLang (name , "" )
77
+ }
78
+
79
+ func (loader * TemplateLoader ) TemplateLang (name , lang string ) (tmpl Template , err error ) {
80
+ runtimeLoader := loader .runtimeLoader .Load ().(* templateRuntime )
81
+ return runtimeLoader .TemplateLang (name , lang )
82
+ }
83
+
63
84
// Refresh method scans the views directory and parses all templates as Go Templates.
64
85
// If a template fails to parse, the error is set on the loader.
65
86
// (It's awkward to refresh a single Go Template)
66
87
func (loader * TemplateLoader ) Refresh () (err * Error ) {
88
+ loader .templateMutex .Lock ()
89
+ defer loader .templateMutex .Unlock ()
90
+
91
+ loader .loadVersionSeed ++
92
+ runtimeLoader := & templateRuntime {loader : loader ,
93
+ version : loader .loadVersionSeed ,
94
+ templateMap : map [string ]Template {}}
95
+
67
96
TRACE .Printf ("Refreshing templates from %s" , loader .paths )
68
- if len (loader .templatesAndEngineList ) == 0 {
69
- if err = loader .InitializeEngines (GO_TEMPLATE ); err != nil {
70
- return
71
- }
97
+ if err = loader .initializeEngines (runtimeLoader , GO_TEMPLATE ); err != nil {
98
+ return
72
99
}
73
- for _ , engine := range loader .templatesAndEngineList {
100
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
74
101
engine .Event (TEMPLATE_REFRESH_REQUESTED , nil )
75
102
}
76
103
fireEvent (TEMPLATE_REFRESH_REQUESTED , nil )
77
104
defer func () {
78
- for _ , engine := range loader .templatesAndEngineList {
105
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
79
106
engine .Event (TEMPLATE_REFRESH_COMPLETED , nil )
80
107
}
81
108
fireEvent (TEMPLATE_REFRESH_COMPLETED , nil )
82
- // Reset the TemplateMap, we don't prepopulate the map because
83
- loader .TemplateMap = map [string ]Template {}
84
109
110
+ // Reset the runtimeLoader
111
+ loader .runtimeLoader .Store (runtimeLoader )
85
112
}()
113
+
86
114
// Resort the paths, make sure the revel path is the last path,
87
115
// so anything can override it
88
116
revelTemplatePath := filepath .Join (RevelPath , "templates" )
@@ -95,8 +123,8 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
95
123
}
96
124
TRACE .Printf ("Refreshing templates from %s" , loader .paths )
97
125
98
- loader .compileError = nil
99
- loader .TemplatePaths = map [string ]string {}
126
+ runtimeLoader .compileError = nil
127
+ runtimeLoader .TemplatePaths = map [string ]string {}
100
128
101
129
for _ , basePath := range loader .paths {
102
130
// Walk only returns an error if the template loader is completely unusable
@@ -134,14 +162,14 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
134
162
return nil
135
163
}
136
164
137
- fileBytes , err := loader .findAndAddTemplate (path , fullSrcDir , basePath )
165
+ fileBytes , err := runtimeLoader .findAndAddTemplate (path , fullSrcDir , basePath )
138
166
139
167
// Store / report the first error encountered.
140
- if err != nil && loader .compileError == nil {
141
- loader .compileError , _ = err .(* Error )
142
- if nil == loader .compileError {
168
+ if err != nil && runtimeLoader .compileError == nil {
169
+ runtimeLoader .compileError , _ = err .(* Error )
170
+ if nil == runtimeLoader .compileError {
143
171
_ , line , description := ParseTemplateError (err )
144
- loader .compileError = & Error {
172
+ runtimeLoader .compileError = & Error {
145
173
Title : "Template Compilation Error" ,
146
174
Path : path ,
147
175
Description : description ,
@@ -150,7 +178,7 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
150
178
}
151
179
}
152
180
ERROR .Printf ("Template compilation error (In %s around line %d):\n \t %s" ,
153
- path , loader .compileError .Line , err .Error ())
181
+ path , runtimeLoader .compileError .Line , err .Error ())
154
182
} else if nil != err { //&& strings.HasPrefix(templateName, "errors/") {
155
183
156
184
if compileError , ok := err .(* Error ); ok {
@@ -174,26 +202,40 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
174
202
175
203
// If there was an error with the Funcs, set it and return immediately.
176
204
if funcErr != nil {
177
- loader .compileError = funcErr .(* Error )
178
- return loader .compileError
205
+ runtimeLoader .compileError = funcErr .(* Error )
206
+ return runtimeLoader .compileError
179
207
}
180
208
}
181
209
182
210
// Note: compileError may or may not be set.
183
- return loader .compileError
211
+ return runtimeLoader .compileError
212
+ }
213
+
214
+ type templateRuntime struct {
215
+ loader * TemplateLoader
216
+ // load version for templates
217
+ version int
218
+ // Template data and implementation
219
+ templatesAndEngineList []TemplateEngine
220
+ // If an error was encountered parsing the templates, it is stored here.
221
+ compileError * Error
222
+ // Map from template name to the path from whence it was loaded.
223
+ TemplatePaths map [string ]string
224
+ // A map of looked up template results
225
+ templateMap map [string ]Template
184
226
}
185
227
186
228
// Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order
187
229
// reads the template file into memory, replaces namespace keys with module (if found
188
- func (loader * TemplateLoader ) findAndAddTemplate (path , fullSrcDir , basePath string ) (fileBytes []byte , err error ) {
230
+ func (runtimeLoader * templateRuntime ) findAndAddTemplate (path , fullSrcDir , basePath string ) (fileBytes []byte , err error ) {
189
231
templateName := filepath .ToSlash (path [len (fullSrcDir )+ 1 :])
190
232
// Convert template names to use forward slashes, even on Windows.
191
233
if os .PathSeparator == '\\' {
192
234
templateName = strings .Replace (templateName , `\` , `/` , - 1 ) // `
193
235
}
194
236
195
237
// Check to see if template was found
196
- if place , found := loader .TemplatePaths [templateName ]; found {
238
+ if place , found := runtimeLoader .TemplatePaths [templateName ]; found {
197
239
TRACE .Println ("Not Loading, template is already exists: " , templateName , "\r \n \t old file:" ,
198
240
place , "\r \n \t new file:" , path )
199
241
return
@@ -206,25 +248,25 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
206
248
}
207
249
// Parse template file and replace the "_RNS_|" in the template with the module name
208
250
// allow for namespaces to be renamed "_RNS_(.*?)|"
209
- if module := ModuleFromPath (path , false );module != nil {
251
+ if module := ModuleFromPath (path , false ); module != nil {
210
252
fileBytes = namespaceReplace (fileBytes , module )
211
253
}
212
254
213
255
// if we have an engine picked for this template process it now
214
256
baseTemplate := NewBaseTemplate (templateName , path , basePath , fileBytes )
215
257
216
258
// Try to find a default engine for the file
217
- for _ , engine := range loader .templatesAndEngineList {
259
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
218
260
if engine .Handles (baseTemplate ) {
219
- _ , err = loader .loadIntoEngine (engine , baseTemplate )
261
+ _ , err = runtimeLoader .loadIntoEngine (engine , baseTemplate )
220
262
return
221
263
}
222
264
}
223
265
224
266
// Try all engines available
225
267
var defaultError error
226
- for _ , engine := range loader .templatesAndEngineList {
227
- if loaded , loaderr := loader .loadIntoEngine (engine , baseTemplate ); loaded {
268
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
269
+ if loaded , loaderr := runtimeLoader .loadIntoEngine (engine , baseTemplate ); loaded {
228
270
return
229
271
} else {
230
272
TRACE .Printf ("Engine '%s' unable to compile %s %s" , engine .Name (), path , loaderr )
@@ -245,7 +287,14 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
245
287
return
246
288
}
247
289
248
- func (loader * TemplateLoader ) loadIntoEngine (engine TemplateEngine , baseTemplate * TemplateView ) (loaded bool , err error ) {
290
+ func (runtimeLoader * templateRuntime ) loadIntoEngine (engine TemplateEngine , baseTemplate * TemplateView ) (loaded bool , err error ) {
291
+ if loadedTemplate , found := runtimeLoader .templateMap [baseTemplate .TemplateName ]; found {
292
+ // Duplicate template found in map
293
+ TRACE .Println ("template already exists in map: " , baseTemplate .TemplateName , " in engine " , engine .Name (), "\r \n \t old file:" ,
294
+ loadedTemplate .Location (), "\r \n \t new file:" , baseTemplate .FilePath )
295
+ return
296
+ }
297
+
249
298
if loadedTemplate := engine .Lookup (baseTemplate .TemplateName ); loadedTemplate != nil {
250
299
// Duplicate template found for engine
251
300
TRACE .Println ("template already exists: " , baseTemplate .TemplateName , " in engine " , engine .Name (), "\r \n \t old file:" ,
@@ -254,7 +303,10 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
254
303
return
255
304
}
256
305
if err = engine .ParseAndAdd (baseTemplate ); err == nil {
257
- loader .TemplatePaths [baseTemplate .TemplateName ] = baseTemplate .FilePath
306
+ if tmpl := engine .Lookup (baseTemplate .TemplateName ); tmpl != nil {
307
+ runtimeLoader .templateMap [baseTemplate .TemplateName ] = tmpl
308
+ }
309
+ runtimeLoader .TemplatePaths [baseTemplate .TemplateName ] = baseTemplate .FilePath
258
310
TRACE .Printf ("Engine '%s' compiled %s" , engine .Name (), baseTemplate .FilePath )
259
311
loaded = true
260
312
} else {
@@ -263,20 +315,6 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
263
315
return
264
316
}
265
317
266
- // WatchDir returns true of directory doesn't start with . (dot)
267
- // otherwise false
268
- func (loader * TemplateLoader ) WatchDir (info os.FileInfo ) bool {
269
- // Watch all directories, except the ones starting with a dot.
270
- return ! strings .HasPrefix (info .Name (), "." )
271
- }
272
-
273
- // WatchFile returns true of file doesn't start with . (dot)
274
- // otherwise false
275
- func (loader * TemplateLoader ) WatchFile (basename string ) bool {
276
- // Watch all files, except the ones starting with a dot.
277
- return ! strings .HasPrefix (basename , "." )
278
- }
279
-
280
318
// Parse the line, and description from an error message like:
281
319
// html/template:Application/Register.html:36: no such template "footer.html"
282
320
func ParseTemplateError (err error ) (templateName string , line int , description string ) {
@@ -301,56 +339,94 @@ func ParseTemplateError(err error) (templateName string, line int, description s
301
339
return templateName , line , description
302
340
}
303
341
304
- // DEPRECATED Use TemplateLang, will be removed in future release
305
- func (loader * TemplateLoader ) Template (name string ) (tmpl Template , err error ) {
306
- return loader .TemplateLang (name , "" )
307
- }
308
342
// Template returns the Template with the given name. The name is the template's path
309
343
// relative to a template loader root.
310
344
//
311
345
// An Error is returned if there was any problem with any of the templates. (In
312
346
// this case, if a template is returned, it may still be usable.)
313
- func (loader * TemplateLoader ) TemplateLang (name , lang string ) (tmpl Template , err error ) {
314
- if loader .compileError != nil {
315
- return nil , loader .compileError
316
- }
317
- // Attempt to load a localized template first.
318
- if lang != "" {
319
- // Look up and return the template.
320
- tmpl = loader .templateLoad (name + "." + lang )
321
- }
322
- // Return non localized version
323
- if tmpl == nil {
324
- tmpl = loader .templateLoad (name )
347
+ func (runtimeLoader * templateRuntime ) TemplateLang (name , lang string ) (tmpl Template , err error ) {
348
+ if runtimeLoader .compileError != nil {
349
+ return nil , runtimeLoader .compileError
325
350
}
326
351
327
- if tmpl == nil && err == nil {
352
+ // Fetch the template from the map
353
+ tmpl = runtimeLoader .templateLoad (name , lang )
354
+ if tmpl == nil {
328
355
err = fmt .Errorf ("Template %s not found." , name )
329
356
}
330
357
331
358
return
332
359
}
333
360
334
- func (loader * TemplateLoader ) templateLoad (name string ) (tmpl Template ) {
335
- if t ,found := loader .TemplateMap [name ];! found && t != nil {
336
- tmpl = t
361
+ // Load and also updates map if name is not found (to speed up next lookup)
362
+ func (runtimeLoader * templateRuntime ) templateLoad (name , lang string ) (tmpl Template ) {
363
+ langName := name
364
+ found := false
365
+ if lang != "" {
366
+ // Look up and return the template.
367
+ langName = name + "." + lang
368
+ tmpl , found = runtimeLoader .templateMap [langName ]
369
+ if found {
370
+ return
371
+ }
372
+ tmpl , found = runtimeLoader .templateMap [name ]
337
373
} else {
374
+ tmpl , found = runtimeLoader .templateMap [name ]
375
+ if found {
376
+ return
377
+ }
378
+ }
379
+
380
+ if ! found {
381
+ // Neither name is found
338
382
// Look up and return the template.
339
- for _ , engine := range loader .templatesAndEngineList {
383
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
384
+ if tmpl = engine .Lookup (langName ); tmpl != nil {
385
+ found = true
386
+ break
387
+ }
340
388
if tmpl = engine .Lookup (name ); tmpl != nil {
341
- loader .templateMutex .Lock ()
342
- defer loader .templateMutex .Unlock ()
343
- loader .TemplateMap [name ] = tmpl
389
+ found = true
344
390
break
345
391
}
346
392
}
393
+ if ! found {
394
+ return
395
+ }
347
396
}
397
+
398
+ // If we found anything store it in the map, we need to copy so we do not
399
+ // run into concurrency issues
400
+ runtimeLoader .loader .templateMutex .Lock ()
401
+ defer runtimeLoader .loader .templateMutex .Unlock ()
402
+
403
+ // In case another thread has loaded the map, reload the atomic value and check
404
+ newRuntimeLoader := runtimeLoader .loader .runtimeLoader .Load ().(* templateRuntime )
405
+ if newRuntimeLoader .version != runtimeLoader .version {
406
+ return
407
+ }
408
+
409
+ newTemplateMap := map [string ]Template {}
410
+ for k , v := range newRuntimeLoader .templateMap {
411
+ newTemplateMap [k ] = v
412
+ }
413
+ newTemplateMap [langName ] = tmpl
414
+ if _ , found := newTemplateMap [name ]; ! found {
415
+ newTemplateMap [name ] = tmpl
416
+ }
417
+ runtimeCopy := & templateRuntime {}
418
+ * runtimeCopy = * newRuntimeLoader
419
+ runtimeCopy .templateMap = newTemplateMap
420
+
421
+ // Set the atomic value
422
+ runtimeLoader .loader .runtimeLoader .Store (runtimeCopy )
348
423
return
349
424
}
350
425
351
426
func (i * TemplateView ) Location () string {
352
427
return i .FilePath
353
428
}
429
+
354
430
func (i * TemplateView ) Content () (content []string ) {
355
431
if i .FileBytes != nil {
356
432
// Parse the bytes
@@ -362,7 +438,7 @@ func (i *TemplateView) Content() (content []string) {
362
438
}
363
439
return nil
364
440
}
441
+
365
442
func NewBaseTemplate (templateName , filePath , basePath string , fileBytes []byte ) * TemplateView {
366
443
return & TemplateView {TemplateName : templateName , FilePath : filePath , FileBytes : fileBytes , BasePath : basePath }
367
444
}
368
-
0 commit comments