Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / template.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package revel
6
7 import (
8         "bufio"
9         "bytes"
10         "fmt"
11         "io"
12         "io/ioutil"
13         "os"
14         "path/filepath"
15         "regexp"
16         "strconv"
17         "strings"
18         "sync"
19         "sync/atomic"
20 )
21
22 // ErrorCSSClass httml CSS error class name
23 var ErrorCSSClass = "hasError"
24
25 // TemplateLoader object handles loading and parsing of templates.
26 // Everything below the application's views directory is treated as a template.
27 type TemplateLoader struct {
28         // Paths to search for templates, in priority order.
29         paths []string
30         // load version seed for templates
31         loadVersionSeed int
32         // A templateRuntime of looked up template results
33         runtimeLoader atomic.Value
34         // Lock to prevent concurrent map writes
35         templateMutex sync.Mutex
36 }
37
38 type Template interface {
39         // The name of the template.
40         Name() string // Name of template
41         // The content of the template as a string (Used in error handling).
42         Content() []string // Content
43         // Called by the server to render the template out the io.Writer, context contains the view args to be passed to the template.
44         Render(wr io.Writer, context interface{}) error
45         // The full path to the file on the disk.
46         Location() string // Disk location
47 }
48
49 var invalidSlugPattern = regexp.MustCompile(`[^a-z0-9 _-]`)
50 var whiteSpacePattern = regexp.MustCompile(`\s+`)
51 var templateLog = RevelLog.New("section", "template")
52
53 // TemplateOutputArgs returns the result of the template rendered using the passed in arguments.
54 func TemplateOutputArgs(templatePath string, args map[string]interface{}) (data []byte, err error) {
55         // Get the Template.
56         lang, _ := args[CurrentLocaleViewArg].(string)
57         template, err := MainTemplateLoader.TemplateLang(templatePath, lang)
58         if err != nil {
59                 return nil, err
60         }
61         tr := &RenderTemplateResult{
62                 Template: template,
63                 ViewArgs: args,
64         }
65         b, err := tr.ToBytes()
66         if err != nil {
67                 return nil, err
68         }
69         return b.Bytes(), nil
70 }
71
72 func NewTemplateLoader(paths []string) *TemplateLoader {
73         loader := &TemplateLoader{
74                 paths: paths,
75         }
76         return loader
77 }
78
79 // WatchDir returns true of directory doesn't start with . (dot)
80 // otherwise false
81 func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool {
82         // Watch all directories, except the ones starting with a dot.
83         return !strings.HasPrefix(info.Name(), ".")
84 }
85
86 // WatchFile returns true of file doesn't start with . (dot)
87 // otherwise false
88 func (loader *TemplateLoader) WatchFile(basename string) bool {
89         // Watch all files, except the ones starting with a dot.
90         return !strings.HasPrefix(basename, ".")
91 }
92
93 // DEPRECATED Use TemplateLang, will be removed in future release
94 func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) {
95         runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime)
96         return runtimeLoader.TemplateLang(name, "")
97 }
98
99 func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) {
100         runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime)
101         return runtimeLoader.TemplateLang(name, lang)
102 }
103
104 // Refresh method scans the views directory and parses all templates as Go Templates.
105 // If a template fails to parse, the error is set on the loader.
106 // (It's awkward to refresh a single Go Template)
107 func (loader *TemplateLoader) Refresh() (err *Error) {
108         loader.templateMutex.Lock()
109         defer loader.templateMutex.Unlock()
110
111         loader.loadVersionSeed++
112         runtimeLoader := &templateRuntime{loader: loader,
113                 version:     loader.loadVersionSeed,
114                 templateMap: map[string]Template{}}
115
116         templateLog.Debug("Refresh: Refreshing templates from ", "path", loader.paths)
117         if err = loader.initializeEngines(runtimeLoader, Config.StringDefault("template.engines", GO_TEMPLATE)); err != nil {
118                 return
119         }
120         for _, engine := range runtimeLoader.templatesAndEngineList {
121                 engine.Event(TEMPLATE_REFRESH_REQUESTED, nil)
122         }
123         RaiseEvent(TEMPLATE_REFRESH_REQUESTED, nil)
124         defer func() {
125                 for _, engine := range runtimeLoader.templatesAndEngineList {
126                         engine.Event(TEMPLATE_REFRESH_COMPLETED, nil)
127                 }
128                 RaiseEvent(TEMPLATE_REFRESH_COMPLETED, nil)
129
130                 // Reset the runtimeLoader
131                 loader.runtimeLoader.Store(runtimeLoader)
132         }()
133
134         // Resort the paths, make sure the revel path is the last path,
135         // so anything can override it
136         revelTemplatePath := filepath.Join(RevelPath, "templates")
137         // Go through the paths
138         for i, o := range loader.paths {
139                 if o == revelTemplatePath && i != len(loader.paths)-1 {
140                         loader.paths[i] = loader.paths[len(loader.paths)-1]
141                         loader.paths[len(loader.paths)-1] = revelTemplatePath
142                 }
143         }
144         templateLog.Debug("Refresh: Refreshing templates from", "path", loader.paths)
145
146         runtimeLoader.compileError = nil
147         runtimeLoader.TemplatePaths = map[string]string{}
148
149         for _, basePath := range loader.paths {
150                 // Walk only returns an error if the template loader is completely unusable
151                 // (namely, if one of the TemplateFuncs does not have an acceptable signature).
152
153                 // Handling symlinked directories
154                 var fullSrcDir string
155                 f, err := os.Lstat(basePath)
156                 if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
157                         fullSrcDir, err = filepath.EvalSymlinks(basePath)
158                         if err != nil {
159                                 templateLog.Panic("Refresh: Eval symlinks error ", "error", err)
160                         }
161                 } else {
162                         fullSrcDir = basePath
163                 }
164
165                 var templateWalker filepath.WalkFunc
166
167                 templateWalker = func(path string, info os.FileInfo, err error) error {
168                         if err != nil {
169                                 templateLog.Error("Refresh: error walking templates:", "error", err)
170                                 return nil
171                         }
172
173                         // Walk into watchable directories
174                         if info.IsDir() {
175                                 if !loader.WatchDir(info) {
176                                         return filepath.SkipDir
177                                 }
178                                 return nil
179                         }
180
181                         // Only add watchable
182                         if !loader.WatchFile(info.Name()) {
183                                 return nil
184                         }
185
186                         fileBytes, err := runtimeLoader.findAndAddTemplate(path, fullSrcDir, basePath)
187                         if err != nil {
188                                 // Add in this template name to the list of templates unable to be compiled
189                                 runtimeLoader.compileErrorNameList = append(runtimeLoader.compileErrorNameList, filepath.ToSlash(path[len(fullSrcDir)+1:]))
190                         }
191                         // Store / report the first error encountered.
192                         if err != nil && runtimeLoader.compileError == nil {
193                                 runtimeLoader.compileError, _ = err.(*Error)
194
195                                 if nil == runtimeLoader.compileError {
196                                         _, line, description := ParseTemplateError(err)
197
198                                         runtimeLoader.compileError = &Error{
199                                                 Title:       "Template Compilation Error",
200                                                 Path:        path,
201                                                 Description: description,
202                                                 Line:        line,
203                                                 SourceLines: strings.Split(string(fileBytes), "\n"),
204                                         }
205                                 }
206                                 templateLog.Errorf("Refresh: Template compilation error (In %s around line %d):\n\t%s",
207                                         path, runtimeLoader.compileError.Line, err.Error())
208                         } else if nil != err { //&& strings.HasPrefix(templateName, "errors/") {
209
210                                 if compileError, ok := err.(*Error); ok {
211                                         templateLog.Errorf("Template compilation error (In %s around line %d):\n\t%s",
212                                                 path, compileError.Line, err.Error())
213                                 } else {
214                                         templateLog.Errorf("Template compilation error (In %s ):\n\t%s",
215                                                 path, err.Error())
216                                 }
217                         }
218                         return nil
219                 }
220
221                 if _, err = os.Lstat(fullSrcDir); os.IsNotExist(err) {
222                         // #1058 Given views/template path is not exists
223                         // so no need to walk, move on to next path
224                         continue
225                 }
226
227                 funcErr := Walk(fullSrcDir, templateWalker)
228
229                 // If there was an error with the Funcs, set it and return immediately.
230                 if funcErr != nil {
231                         runtimeLoader.compileError = NewErrorFromPanic(funcErr)
232                         return runtimeLoader.compileError
233                 }
234         }
235
236         // Note: compileError may or may not be set.
237         return runtimeLoader.compileError
238 }
239
240 type templateRuntime struct {
241         loader *TemplateLoader
242         // load version for templates
243         version int
244         // Template data and implementation
245         templatesAndEngineList []TemplateEngine
246         // If an error was encountered parsing the templates, it is stored here.
247         compileError *Error
248         // A list of the names of the templates with errors
249         compileErrorNameList []string
250         // Map from template name to the path from whence it was loaded.
251         TemplatePaths map[string]string
252         // A map of looked up template results
253         templateMap map[string]Template
254 }
255
256 // Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order
257 // reads the template file into memory, replaces namespace keys with module (if found
258 func (runtimeLoader *templateRuntime) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) {
259         templateName := filepath.ToSlash(path[len(fullSrcDir)+1:])
260         // Convert template names to use forward slashes, even on Windows.
261         if os.PathSeparator == '\\' {
262                 templateName = strings.Replace(templateName, `\`, `/`, -1) // `
263         }
264
265         // Check to see if template was found
266         if place, found := runtimeLoader.TemplatePaths[templateName]; found {
267                 templateLog.Debug("findAndAddTemplate: Not Loading, template is already exists: ", "name", templateName, "old",
268                         place, "new", path)
269                 return
270         }
271
272         fileBytes, err = ioutil.ReadFile(path)
273         if err != nil {
274                 templateLog.Error("findAndAddTemplate: Failed reading file:", "path", path, "error", err)
275                 return
276         }
277         // Parse template file and replace the "_LOCAL_|" in the template with the module name
278         // allow for namespaces to be renamed "_LOCAL_(.*?)|"
279         if module := ModuleFromPath(path, false); module != nil {
280                 fileBytes = namespaceReplace(fileBytes, module)
281         }
282
283         // if we have an engine picked for this template process it now
284         baseTemplate := NewBaseTemplate(templateName, path, basePath, fileBytes)
285
286         // Try to find a default engine for the file
287         for _, engine := range runtimeLoader.templatesAndEngineList {
288                 if engine.Handles(baseTemplate) {
289                         _, err = runtimeLoader.loadIntoEngine(engine, baseTemplate)
290                         return
291                 }
292         }
293
294         // Try all engines available
295         var defaultError error
296         for _, engine := range runtimeLoader.templatesAndEngineList {
297                 if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded {
298                         return
299                 } else {
300                         templateLog.Debugf("findAndAddTemplate: Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr.Error())
301                         if defaultError == nil {
302                                 defaultError = loaderr
303                         }
304                 }
305         }
306
307         // Assign the error from the first parser
308         err = defaultError
309
310         // No engines could be found return the err
311         if err == nil {
312                 err = fmt.Errorf("Failed to parse template file using engines %s", path)
313         }
314
315         return
316 }
317
318 func (runtimeLoader *templateRuntime) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) {
319         if loadedTemplate, found := runtimeLoader.templateMap[baseTemplate.TemplateName]; found {
320                 // Duplicate template found in map
321                 templateLog.Debug("template already exists in map: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:",
322                         loadedTemplate.Location(), "\r\n\tnew file:", baseTemplate.FilePath)
323                 return
324         }
325
326         if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil {
327                 // Duplicate template found for engine
328                 templateLog.Debug("loadIntoEngine: template already exists: ", "template", baseTemplate.TemplateName, "inengine ", engine.Name(), "old",
329                         loadedTemplate.Location(), "new", baseTemplate.FilePath)
330                 loaded = true
331                 return
332         }
333         if err = engine.ParseAndAdd(baseTemplate); err == nil {
334                 if tmpl := engine.Lookup(baseTemplate.TemplateName); tmpl != nil {
335                         runtimeLoader.templateMap[baseTemplate.TemplateName] = tmpl
336                 }
337                 runtimeLoader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath
338                 templateLog.Debugf("loadIntoEngine:Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath)
339                 loaded = true
340         } else {
341                 templateLog.Debug("loadIntoEngine: Engine failed to compile", "engine", engine.Name(), "file", baseTemplate.FilePath, "error", err)
342         }
343         return
344 }
345
346 // Parse the line, and description from an error message like:
347 // html/template:Application/Register.html:36: no such template "footer.html"
348 func ParseTemplateError(err error) (templateName string, line int, description string) {
349         if e, ok := err.(*Error); ok {
350                 return "", e.Line, e.Description
351         }
352
353         description = err.Error()
354         i := regexp.MustCompile(`:\d+:`).FindStringIndex(description)
355         if i != nil {
356                 line, err = strconv.Atoi(description[i[0]+1 : i[1]-1])
357                 if err != nil {
358                         templateLog.Error("ParseTemplateError: Failed to parse line number from error message:", "error", err)
359                 }
360                 templateName = description[:i[0]]
361                 if colon := strings.Index(templateName, ":"); colon != -1 {
362                         templateName = templateName[colon+1:]
363                 }
364                 templateName = strings.TrimSpace(templateName)
365                 description = description[i[1]+1:]
366         }
367         return templateName, line, description
368 }
369
370 // Template returns the Template with the given name.  The name is the template's path
371 // relative to a template loader root.
372 //
373 // An Error is returned if there was any problem with any of the templates.  (In
374 // this case, if a template is returned, it may still be usable.)
375 func (runtimeLoader *templateRuntime) TemplateLang(name, lang string) (tmpl Template, err error) {
376         if runtimeLoader.compileError != nil {
377                 for _, errName := range runtimeLoader.compileErrorNameList {
378                         if name == errName {
379                                 return nil, runtimeLoader.compileError
380                         }
381                 }
382         }
383
384         // Fetch the template from the map
385         tmpl = runtimeLoader.templateLoad(name, lang)
386         if tmpl == nil {
387                 err = fmt.Errorf("Template %s not found.", name)
388         }
389
390         return
391 }
392
393 // Load and also updates map if name is not found (to speed up next lookup)
394 func (runtimeLoader *templateRuntime) templateLoad(name, lang string) (tmpl Template) {
395         langName := name
396         found := false
397         if lang != "" {
398                 // Look up and return the template.
399                 langName = name + "." + lang
400                 tmpl, found = runtimeLoader.templateMap[langName]
401                 if found {
402                         return
403                 }
404                 tmpl, found = runtimeLoader.templateMap[name]
405         } else {
406                 tmpl, found = runtimeLoader.templateMap[name]
407                 if found {
408                         return
409                 }
410         }
411
412         if !found {
413                 // Neither name is found
414                 // Look up and return the template.
415                 for _, engine := range runtimeLoader.templatesAndEngineList {
416                         if tmpl = engine.Lookup(langName); tmpl != nil {
417                                 found = true
418                                 break
419                         }
420                         if tmpl = engine.Lookup(name); tmpl != nil {
421                                 found = true
422                                 break
423                         }
424                 }
425                 if !found {
426                         return
427                 }
428         }
429
430         // If we found anything store it in the map, we need to copy so we do not
431         // run into concurrency issues
432         runtimeLoader.loader.templateMutex.Lock()
433         defer runtimeLoader.loader.templateMutex.Unlock()
434
435         // In case another thread has loaded the map, reload the atomic value and check
436         newRuntimeLoader := runtimeLoader.loader.runtimeLoader.Load().(*templateRuntime)
437         if newRuntimeLoader.version != runtimeLoader.version {
438                 return
439         }
440
441         newTemplateMap := map[string]Template{}
442         for k, v := range newRuntimeLoader.templateMap {
443                 newTemplateMap[k] = v
444         }
445         newTemplateMap[langName] = tmpl
446         if _, found := newTemplateMap[name]; !found {
447                 newTemplateMap[name] = tmpl
448         }
449         runtimeCopy := &templateRuntime{}
450         *runtimeCopy = *newRuntimeLoader
451         runtimeCopy.templateMap = newTemplateMap
452
453         // Set the atomic value
454         runtimeLoader.loader.runtimeLoader.Store(runtimeCopy)
455         return
456 }
457
458 func (i *TemplateView) Location() string {
459         return i.FilePath
460 }
461
462 func (i *TemplateView) Content() (content []string) {
463         if i.FileBytes != nil {
464                 // Parse the bytes
465                 buffer := bytes.NewBuffer(i.FileBytes)
466                 reader := bufio.NewScanner(buffer)
467                 for reader.Scan() {
468                         content = append(content, string(reader.Bytes()))
469                 }
470         }
471
472         return content
473 }
474
475 func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView {
476         return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath}
477 }