X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Ftemplate.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Ftemplate.go;h=c5bd91e84a2451d9bbd7c977ccdadc56482c25d7;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/template.go b/src/foundation/api/revel/template.go new file mode 100644 index 0000000..c5bd91e --- /dev/null +++ b/src/foundation/api/revel/template.go @@ -0,0 +1,477 @@ +// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. +// Revel Framework source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package revel + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "sync" + "sync/atomic" +) + +// ErrorCSSClass httml CSS error class name +var ErrorCSSClass = "hasError" + +// TemplateLoader object handles loading and parsing of templates. +// Everything below the application's views directory is treated as a template. +type TemplateLoader struct { + // Paths to search for templates, in priority order. + paths []string + // load version seed for templates + loadVersionSeed int + // A templateRuntime of looked up template results + runtimeLoader atomic.Value + // Lock to prevent concurrent map writes + templateMutex sync.Mutex +} + +type Template interface { + // The name of the template. + Name() string // Name of template + // The content of the template as a string (Used in error handling). + Content() []string // Content + // Called by the server to render the template out the io.Writer, context contains the view args to be passed to the template. + Render(wr io.Writer, context interface{}) error + // The full path to the file on the disk. + Location() string // Disk location +} + +var invalidSlugPattern = regexp.MustCompile(`[^a-z0-9 _-]`) +var whiteSpacePattern = regexp.MustCompile(`\s+`) +var templateLog = RevelLog.New("section", "template") + +// TemplateOutputArgs returns the result of the template rendered using the passed in arguments. +func TemplateOutputArgs(templatePath string, args map[string]interface{}) (data []byte, err error) { + // Get the Template. + lang, _ := args[CurrentLocaleViewArg].(string) + template, err := MainTemplateLoader.TemplateLang(templatePath, lang) + if err != nil { + return nil, err + } + tr := &RenderTemplateResult{ + Template: template, + ViewArgs: args, + } + b, err := tr.ToBytes() + if err != nil { + return nil, err + } + return b.Bytes(), nil +} + +func NewTemplateLoader(paths []string) *TemplateLoader { + loader := &TemplateLoader{ + paths: paths, + } + return loader +} + +// WatchDir returns true of directory doesn't start with . (dot) +// otherwise false +func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool { + // Watch all directories, except the ones starting with a dot. + return !strings.HasPrefix(info.Name(), ".") +} + +// WatchFile returns true of file doesn't start with . (dot) +// otherwise false +func (loader *TemplateLoader) WatchFile(basename string) bool { + // Watch all files, except the ones starting with a dot. + return !strings.HasPrefix(basename, ".") +} + +// DEPRECATED Use TemplateLang, will be removed in future release +func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) { + runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime) + return runtimeLoader.TemplateLang(name, "") +} + +func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) { + runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime) + return runtimeLoader.TemplateLang(name, lang) +} + +// Refresh method scans the views directory and parses all templates as Go Templates. +// If a template fails to parse, the error is set on the loader. +// (It's awkward to refresh a single Go Template) +func (loader *TemplateLoader) Refresh() (err *Error) { + loader.templateMutex.Lock() + defer loader.templateMutex.Unlock() + + loader.loadVersionSeed++ + runtimeLoader := &templateRuntime{loader: loader, + version: loader.loadVersionSeed, + templateMap: map[string]Template{}} + + templateLog.Debug("Refresh: Refreshing templates from ", "path", loader.paths) + if err = loader.initializeEngines(runtimeLoader, Config.StringDefault("template.engines", GO_TEMPLATE)); err != nil { + return + } + for _, engine := range runtimeLoader.templatesAndEngineList { + engine.Event(TEMPLATE_REFRESH_REQUESTED, nil) + } + RaiseEvent(TEMPLATE_REFRESH_REQUESTED, nil) + defer func() { + for _, engine := range runtimeLoader.templatesAndEngineList { + engine.Event(TEMPLATE_REFRESH_COMPLETED, nil) + } + RaiseEvent(TEMPLATE_REFRESH_COMPLETED, nil) + + // Reset the runtimeLoader + loader.runtimeLoader.Store(runtimeLoader) + }() + + // Resort the paths, make sure the revel path is the last path, + // so anything can override it + revelTemplatePath := filepath.Join(RevelPath, "templates") + // Go through the paths + for i, o := range loader.paths { + if o == revelTemplatePath && i != len(loader.paths)-1 { + loader.paths[i] = loader.paths[len(loader.paths)-1] + loader.paths[len(loader.paths)-1] = revelTemplatePath + } + } + templateLog.Debug("Refresh: Refreshing templates from", "path", loader.paths) + + runtimeLoader.compileError = nil + runtimeLoader.TemplatePaths = map[string]string{} + + for _, basePath := range loader.paths { + // Walk only returns an error if the template loader is completely unusable + // (namely, if one of the TemplateFuncs does not have an acceptable signature). + + // Handling symlinked directories + var fullSrcDir string + f, err := os.Lstat(basePath) + if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink { + fullSrcDir, err = filepath.EvalSymlinks(basePath) + if err != nil { + templateLog.Panic("Refresh: Eval symlinks error ", "error", err) + } + } else { + fullSrcDir = basePath + } + + var templateWalker filepath.WalkFunc + + templateWalker = func(path string, info os.FileInfo, err error) error { + if err != nil { + templateLog.Error("Refresh: error walking templates:", "error", err) + return nil + } + + // Walk into watchable directories + if info.IsDir() { + if !loader.WatchDir(info) { + return filepath.SkipDir + } + return nil + } + + // Only add watchable + if !loader.WatchFile(info.Name()) { + return nil + } + + fileBytes, err := runtimeLoader.findAndAddTemplate(path, fullSrcDir, basePath) + if err != nil { + // Add in this template name to the list of templates unable to be compiled + runtimeLoader.compileErrorNameList = append(runtimeLoader.compileErrorNameList, filepath.ToSlash(path[len(fullSrcDir)+1:])) + } + // Store / report the first error encountered. + if err != nil && runtimeLoader.compileError == nil { + runtimeLoader.compileError, _ = err.(*Error) + + if nil == runtimeLoader.compileError { + _, line, description := ParseTemplateError(err) + + runtimeLoader.compileError = &Error{ + Title: "Template Compilation Error", + Path: path, + Description: description, + Line: line, + SourceLines: strings.Split(string(fileBytes), "\n"), + } + } + templateLog.Errorf("Refresh: Template compilation error (In %s around line %d):\n\t%s", + path, runtimeLoader.compileError.Line, err.Error()) + } else if nil != err { //&& strings.HasPrefix(templateName, "errors/") { + + if compileError, ok := err.(*Error); ok { + templateLog.Errorf("Template compilation error (In %s around line %d):\n\t%s", + path, compileError.Line, err.Error()) + } else { + templateLog.Errorf("Template compilation error (In %s ):\n\t%s", + path, err.Error()) + } + } + return nil + } + + if _, err = os.Lstat(fullSrcDir); os.IsNotExist(err) { + // #1058 Given views/template path is not exists + // so no need to walk, move on to next path + continue + } + + funcErr := Walk(fullSrcDir, templateWalker) + + // If there was an error with the Funcs, set it and return immediately. + if funcErr != nil { + runtimeLoader.compileError = NewErrorFromPanic(funcErr) + return runtimeLoader.compileError + } + } + + // Note: compileError may or may not be set. + return runtimeLoader.compileError +} + +type templateRuntime struct { + loader *TemplateLoader + // load version for templates + version int + // Template data and implementation + templatesAndEngineList []TemplateEngine + // If an error was encountered parsing the templates, it is stored here. + compileError *Error + // A list of the names of the templates with errors + compileErrorNameList []string + // Map from template name to the path from whence it was loaded. + TemplatePaths map[string]string + // A map of looked up template results + templateMap map[string]Template +} + +// Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order +// reads the template file into memory, replaces namespace keys with module (if found +func (runtimeLoader *templateRuntime) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) { + templateName := filepath.ToSlash(path[len(fullSrcDir)+1:]) + // Convert template names to use forward slashes, even on Windows. + if os.PathSeparator == '\\' { + templateName = strings.Replace(templateName, `\`, `/`, -1) // ` + } + + // Check to see if template was found + if place, found := runtimeLoader.TemplatePaths[templateName]; found { + templateLog.Debug("findAndAddTemplate: Not Loading, template is already exists: ", "name", templateName, "old", + place, "new", path) + return + } + + fileBytes, err = ioutil.ReadFile(path) + if err != nil { + templateLog.Error("findAndAddTemplate: Failed reading file:", "path", path, "error", err) + return + } + // Parse template file and replace the "_LOCAL_|" in the template with the module name + // allow for namespaces to be renamed "_LOCAL_(.*?)|" + if module := ModuleFromPath(path, false); module != nil { + fileBytes = namespaceReplace(fileBytes, module) + } + + // if we have an engine picked for this template process it now + baseTemplate := NewBaseTemplate(templateName, path, basePath, fileBytes) + + // Try to find a default engine for the file + for _, engine := range runtimeLoader.templatesAndEngineList { + if engine.Handles(baseTemplate) { + _, err = runtimeLoader.loadIntoEngine(engine, baseTemplate) + return + } + } + + // Try all engines available + var defaultError error + for _, engine := range runtimeLoader.templatesAndEngineList { + if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded { + return + } else { + templateLog.Debugf("findAndAddTemplate: Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr.Error()) + if defaultError == nil { + defaultError = loaderr + } + } + } + + // Assign the error from the first parser + err = defaultError + + // No engines could be found return the err + if err == nil { + err = fmt.Errorf("Failed to parse template file using engines %s", path) + } + + return +} + +func (runtimeLoader *templateRuntime) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) { + if loadedTemplate, found := runtimeLoader.templateMap[baseTemplate.TemplateName]; found { + // Duplicate template found in map + templateLog.Debug("template already exists in map: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:", + loadedTemplate.Location(), "\r\n\tnew file:", baseTemplate.FilePath) + return + } + + if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil { + // Duplicate template found for engine + templateLog.Debug("loadIntoEngine: template already exists: ", "template", baseTemplate.TemplateName, "inengine ", engine.Name(), "old", + loadedTemplate.Location(), "new", baseTemplate.FilePath) + loaded = true + return + } + if err = engine.ParseAndAdd(baseTemplate); err == nil { + if tmpl := engine.Lookup(baseTemplate.TemplateName); tmpl != nil { + runtimeLoader.templateMap[baseTemplate.TemplateName] = tmpl + } + runtimeLoader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath + templateLog.Debugf("loadIntoEngine:Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath) + loaded = true + } else { + templateLog.Debug("loadIntoEngine: Engine failed to compile", "engine", engine.Name(), "file", baseTemplate.FilePath, "error", err) + } + return +} + +// Parse the line, and description from an error message like: +// html/template:Application/Register.html:36: no such template "footer.html" +func ParseTemplateError(err error) (templateName string, line int, description string) { + if e, ok := err.(*Error); ok { + return "", e.Line, e.Description + } + + description = err.Error() + i := regexp.MustCompile(`:\d+:`).FindStringIndex(description) + if i != nil { + line, err = strconv.Atoi(description[i[0]+1 : i[1]-1]) + if err != nil { + templateLog.Error("ParseTemplateError: Failed to parse line number from error message:", "error", err) + } + templateName = description[:i[0]] + if colon := strings.Index(templateName, ":"); colon != -1 { + templateName = templateName[colon+1:] + } + templateName = strings.TrimSpace(templateName) + description = description[i[1]+1:] + } + return templateName, line, description +} + +// Template returns the Template with the given name. The name is the template's path +// relative to a template loader root. +// +// An Error is returned if there was any problem with any of the templates. (In +// this case, if a template is returned, it may still be usable.) +func (runtimeLoader *templateRuntime) TemplateLang(name, lang string) (tmpl Template, err error) { + if runtimeLoader.compileError != nil { + for _, errName := range runtimeLoader.compileErrorNameList { + if name == errName { + return nil, runtimeLoader.compileError + } + } + } + + // Fetch the template from the map + tmpl = runtimeLoader.templateLoad(name, lang) + if tmpl == nil { + err = fmt.Errorf("Template %s not found.", name) + } + + return +} + +// Load and also updates map if name is not found (to speed up next lookup) +func (runtimeLoader *templateRuntime) templateLoad(name, lang string) (tmpl Template) { + langName := name + found := false + if lang != "" { + // Look up and return the template. + langName = name + "." + lang + tmpl, found = runtimeLoader.templateMap[langName] + if found { + return + } + tmpl, found = runtimeLoader.templateMap[name] + } else { + tmpl, found = runtimeLoader.templateMap[name] + if found { + return + } + } + + if !found { + // Neither name is found + // Look up and return the template. + for _, engine := range runtimeLoader.templatesAndEngineList { + if tmpl = engine.Lookup(langName); tmpl != nil { + found = true + break + } + if tmpl = engine.Lookup(name); tmpl != nil { + found = true + break + } + } + if !found { + return + } + } + + // If we found anything store it in the map, we need to copy so we do not + // run into concurrency issues + runtimeLoader.loader.templateMutex.Lock() + defer runtimeLoader.loader.templateMutex.Unlock() + + // In case another thread has loaded the map, reload the atomic value and check + newRuntimeLoader := runtimeLoader.loader.runtimeLoader.Load().(*templateRuntime) + if newRuntimeLoader.version != runtimeLoader.version { + return + } + + newTemplateMap := map[string]Template{} + for k, v := range newRuntimeLoader.templateMap { + newTemplateMap[k] = v + } + newTemplateMap[langName] = tmpl + if _, found := newTemplateMap[name]; !found { + newTemplateMap[name] = tmpl + } + runtimeCopy := &templateRuntime{} + *runtimeCopy = *newRuntimeLoader + runtimeCopy.templateMap = newTemplateMap + + // Set the atomic value + runtimeLoader.loader.runtimeLoader.Store(runtimeCopy) + return +} + +func (i *TemplateView) Location() string { + return i.FilePath +} + +func (i *TemplateView) Content() (content []string) { + if i.FileBytes != nil { + // Parse the bytes + buffer := bytes.NewBuffer(i.FileBytes) + reader := bufio.NewScanner(buffer) + for reader.Scan() { + content = append(content, string(reader.Bytes())) + } + } + + return content +} + +func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView { + return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath} +}