Template
package revel
var ERROR_CLASS = "hasError"
type TemplateLoader struct {
}
type Template interface {
Name() string
Content() []string
Render(wr io.Writer, arg interface{}) error
}
var (
TemplateFuncs = map[string]interface{}{
"url": ReverseUrl,
"eq": Equal,
"set": func(renderArgs map[string]interface{}, key string, value interface{}) template.HTML {
renderArgs[key] = value
return template.HTML("")
},
"append": func(renderArgs map[string]interface{}, key string, value interface{}) template.HTML {
if renderArgs[key] == nil {
renderArgs[key] = []interface{}{value}
} else {
renderArgs[key] = append(renderArgs[key].([]interface{}), value)
}
return template.HTML("")
},
"field": NewField,
"option": func(f *Field, val, label string) template.HTML {
selected := ""
if f.Flash() == val {
selected = " selected"
}
return template.HTML(fmt.Sprintf(`<option value="%s"%s>%s</option>`,
html.EscapeString(val), selected, html.EscapeString(label)))
},
"radio": func(f *Field, val string) template.HTML {
checked := ""
if f.Flash() == val {
checked = " checked"
}
return template.HTML(fmt.Sprintf(`<input type="radio" name="%s" value="%s"%s>`,
html.EscapeString(f.Name), html.EscapeString(val), checked))
},
"checkbox": func(f *Field, val string) template.HTML {
checked := ""
if f.Flash() == val {
checked = " checked"
}
return template.HTML(fmt.Sprintf(`<input type="checkbox" name="%s" value="%s"%s>`,
html.EscapeString(f.Name), html.EscapeString(val), checked))
},
"pad": func(str string, width int) template.HTML {
if len(str) >= width {
return template.HTML(html.EscapeString(str))
}
return template.HTML(html.EscapeString(str) + strings.Repeat(" ", width-len(str)))
},
"errorClass": func(name string, renderArgs map[string]interface{}) template.HTML {
errorMap, ok := renderArgs["errors"].(map[string]*ValidationError)
if !ok || errorMap == nil {
WARN.Println("Called 'errorClass' without 'errors' in the render args.")
return template.HTML("")
}
valError, ok := errorMap[name]
if !ok || valError == nil {
return template.HTML("")
}
return template.HTML(ERROR_CLASS)
},
"msg": func(renderArgs map[string]interface{}, message string, args ...interface{}) template.HTML {
return template.HTML(Message(renderArgs[CurrentLocaleRenderArg].(string), message, args...))
},
"nl2br": func(text string) template.HTML {
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
},
"raw": func(text string) template.HTML {
return template.HTML(text)
},
"pluralize": func(items interface{}, pluralOverrides ...string) string {
singular, plural := "", "s"
if len(pluralOverrides) >= 1 {
singular = pluralOverrides[0]
if len(pluralOverrides) == 2 {
plural = pluralOverrides[1]
}
}
switch v := reflect.ValueOf(items); v.Kind() {
case reflect.Int:
if items.(int) != 1 {
return plural
}
case reflect.Slice:
if v.Len() != 1 {
return plural
}
default:
ERROR.Println("pluralize: unexpected type: ", v)
}
return singular
},
"date": func(date time.Time) string {
return date.Format(DateFormat)
},
"datetime": func(date time.Time) string {
return date.Format(DateTimeFormat)
},
"slug": Slug,
}
)
func NewTemplateLoader(paths []string) *TemplateLoader {
loader := &TemplateLoader{
paths: paths,
}
return loader
}
func (loader *TemplateLoader) Refresh() *Error {
TRACE.Printf("Refreshing templates from %s", loader.paths)
loader.compileError = nil
loader.templatePaths = map[string]string{}
var splitDelims []string
if TemplateDelims != "" {
splitDelims = strings.Split(TemplateDelims, " ")
if len(splitDelims) != 2 {
log.Fatalln("app.conf: Incorrect format for template.delimiters")
}
}
var templateSet *template.Template = nil
for _, basePath := range loader.paths {
funcErr := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
ERROR.Println("error walking templates:", err)
return nil
}
if info.IsDir() {
if !loader.WatchDir(info) {
return filepath.SkipDir
}
return nil
}
if !loader.WatchFile(info.Name()) {
return nil
}
var fileStr string
addTemplate := func(templateName string) (err error) {
if os.PathSeparator == '\\' {
templateName = strings.Replace(templateName, `\`, `/`, -1)
}
if _, ok := loader.templatePaths[templateName]; ok {
return nil
}
loader.templatePaths[templateName] = path
if fileStr == "" {
fileBytes, err := ioutil.ReadFile(path)
if err != nil {
ERROR.Println("Failed reading file:", path)
return nil
}
fileStr = string(fileBytes)
}
if templateSet == nil {
var funcError *Error
func() {
defer func() {
if err := recover(); err != nil {
funcError = &Error{
Title: "Panic (Template Loader)",
Description: fmt.Sprintln(err),
}
}
}()
templateSet = template.New(templateName).Funcs(TemplateFuncs)
if splitDelims != nil && basePath == ViewsPath {
templateSet.Delims(splitDelims[0], splitDelims[1])
} else {
templateSet.Delims("", "")
}
_, err = templateSet.Parse(fileStr)
}()
if funcError != nil {
return funcError
}
} else {
if splitDelims != nil && basePath == ViewsPath {
templateSet.Delims(splitDelims[0], splitDelims[1])
} else {
templateSet.Delims("", "")
}
_, err = templateSet.New(templateName).Parse(fileStr)
}
return err
}
templateName := path[len(basePath)+1:]
lowerCaseTemplateName := strings.ToLower(templateName)
err = addTemplate(templateName)
err = addTemplate(lowerCaseTemplateName)
if err != nil && loader.compileError == nil {
_, line, description := parseTemplateError(err)
loader.compileError = &Error{
Title: "Template Compilation Error",
Path: templateName,
Description: description,
Line: line,
SourceLines: strings.Split(fileStr, "\n"),
}
ERROR.Printf("Template compilation error (In %s around line %d):\n%s",
templateName, line, description)
}
return nil
})
if funcErr != nil {
loader.compileError = funcErr.(*Error)
return loader.compileError
}
}
loader.templateSet = templateSet
return loader.compileError
}
func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool {
return !strings.HasPrefix(info.Name(), ".")
}
func (loader *TemplateLoader) WatchFile(basename string) bool {
return !strings.HasPrefix(basename, ".")
}
func (loader *TemplateLoader) Template(name string) (Template, error) {
name = strings.ToLower(name)
tmpl := loader.templateSet.Lookup(name)
var err error
if loader.compileError != nil {
err = loader.compileError
}
if tmpl == nil && err == nil {
return nil, fmt.Errorf("Template %s not found.", name)
}
return GoTemplate{tmpl, loader}, err
}
type GoTemplate struct {
*template.Template
}
func (gotmpl GoTemplate) Render(wr io.Writer, arg interface{}) error {
return gotmpl.Execute(wr, arg)
}
func (gotmpl GoTemplate) Content() []string {
content, _ := ReadLines(gotmpl.loader.templatePaths[gotmpl.Name()])
return content
}
func ReverseUrl(args ...interface{}) (string, error) {
if len(args) == 0 {
return "", fmt.Errorf("no arguments provided to reverse route")
}
action := args[0].(string)
actionSplit := strings.Split(action, ".")
if len(actionSplit) != 2 {
return "", fmt.Errorf("reversing '%s', expected 'Controller.Action'", action)
}
var c Controller
if err := c.SetAction(actionSplit[0], actionSplit[1]); err != nil {
return "", fmt.Errorf("reversing %s: %s", action, err)
}
argsByName := make(map[string]string)
for i, argValue := range args[1:] {
Unbind(argsByName, c.MethodType.Args[i].Name, argValue)
}
return MainRouter.Reverse(args[0].(string), argsByName).Url, nil
}
func Slug(text string) string {
separator := "-"
text = strings.ToLower(text)
text = invalidSlugPattern.ReplaceAllString(text, "")
text = whiteSpacePattern.ReplaceAllString(text, separator)
text = strings.Trim(text, separator)
return text
}