github.com/woremacx/kocha@v0.7.1-0.20150731103243-a5889322afc9/template.go (about) 1 package kocha 2 3 import ( 4 "bytes" 5 "fmt" 6 "html/template" 7 "io" 8 "os" 9 "path/filepath" 10 "reflect" 11 "strconv" 12 "strings" 13 14 "github.com/woremacx/kocha/util" 15 ) 16 17 const ( 18 LayoutDir = "layout" 19 ErrorTemplateDir = "error" 20 21 layoutPath = LayoutDir + string(filepath.Separator) 22 ) 23 24 // TemplatePathInfo represents an information of template paths. 25 type TemplatePathInfo struct { 26 Name string // name of the application. 27 Paths []string // directory paths of the template files. 28 } 29 30 type templateKey struct { 31 appName string 32 name string 33 format string 34 isLayout bool 35 } 36 37 func (k templateKey) String() string { 38 p := k.name 39 if k.isLayout { 40 p = filepath.Join(LayoutDir, p) 41 } 42 return fmt.Sprintf("%s:%s.%s", k.appName, p, k.format) 43 } 44 45 // Template represents the templates information. 46 type Template struct { 47 PathInfo TemplatePathInfo // information of location of template paths. 48 FuncMap TemplateFuncMap // same as template.FuncMap. 49 LeftDelim string // left action delimiter. 50 RightDelim string // right action delimiter. 51 52 m map[templateKey]*template.Template 53 app *Application 54 } 55 56 // Get gets a parsed template. 57 func (t *Template) Get(appName, layout, name, format string) (*template.Template, error) { 58 key := templateKey{ 59 appName: appName, 60 format: format, 61 isLayout: layout != "", 62 } 63 if key.isLayout { 64 key.name = layout 65 } else { 66 key.name = name 67 } 68 tmpl, exists := t.m[key] 69 if !exists { 70 return nil, fmt.Errorf("kocha: template not found: %s", key) 71 } 72 return tmpl, nil 73 } 74 75 func (t *Template) build(app *Application) (*Template, error) { 76 if t == nil { 77 t = &Template{} 78 } 79 t.app = app 80 if t.LeftDelim == "" { 81 t.LeftDelim = "{{" 82 } 83 if t.RightDelim == "" { 84 t.RightDelim = "}}" 85 } 86 t, err := t.buildFuncMap() 87 if err != nil { 88 return nil, err 89 } 90 t, err = t.buildTemplateMap() 91 if err != nil { 92 return nil, err 93 } 94 return t, nil 95 } 96 97 func (t *Template) buildFuncMap() (*Template, error) { 98 m := TemplateFuncMap{ 99 "yield": t.yield, 100 "in": t.in, 101 "url": t.url, 102 "nl2br": t.nl2br, 103 "raw": t.raw, 104 "invoke_template": t.invokeTemplate, 105 "flash": t.flash, 106 "join": t.join, 107 } 108 for name, fn := range t.FuncMap { 109 m[name] = fn 110 } 111 t.FuncMap = m 112 return t, nil 113 } 114 115 // buildTemplateMap returns templateMap constructed from templateSet. 116 func (t *Template) buildTemplateMap() (*Template, error) { 117 info := t.PathInfo 118 var templatePaths map[string]map[string]map[string]string 119 if data := t.app.ResourceSet.Get("_kocha_template_paths"); data != nil { 120 if paths, ok := data.(map[string]map[string]map[string]string); ok { 121 templatePaths = paths 122 } 123 } 124 if templatePaths == nil { 125 templatePaths = map[string]map[string]map[string]string{ 126 info.Name: make(map[string]map[string]string), 127 } 128 for _, rootPath := range info.Paths { 129 if err := t.collectTemplatePaths(templatePaths[info.Name], rootPath); err != nil { 130 return nil, err 131 } 132 } 133 t.app.ResourceSet.Add("_kocha_template_paths", templatePaths) 134 } 135 t.m = map[templateKey]*template.Template{} 136 l := len(t.LeftDelim) + len("$ := .Data") + len(t.RightDelim) 137 buf := bytes.NewBuffer(append(append(append(make([]byte, 0, l), t.LeftDelim...), "$ := .Data"...), t.RightDelim...)) 138 for appName, templates := range templatePaths { 139 if err := t.buildAppTemplateSet(buf, l, t.m, appName, templates); err != nil { 140 return nil, err 141 } 142 } 143 return t, nil 144 } 145 146 // TemplateFuncMap is an alias of templete.FuncMap. 147 type TemplateFuncMap template.FuncMap 148 149 func (t *Template) collectTemplatePaths(templatePaths map[string]map[string]string, templateDir string) error { 150 return filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error { 151 if err != nil { 152 return err 153 } 154 if info.IsDir() { 155 return nil 156 } 157 baseName, err := filepath.Rel(templateDir, path) 158 if err != nil { 159 return err 160 } 161 name := strings.TrimSuffix(baseName, util.TemplateSuffix) 162 ext := filepath.Ext(name) 163 if _, exists := templatePaths[ext]; !exists { 164 templatePaths[ext] = make(map[string]string) 165 } 166 templatePaths[ext][name] = path 167 return nil 168 }) 169 } 170 171 func (t *Template) buildAppTemplateSet(buf *bytes.Buffer, l int, m map[templateKey]*template.Template, appName string, templates map[string]map[string]string) error { 172 for ext, templateInfos := range templates { 173 tmpl := template.New("") 174 for name, path := range templateInfos { 175 buf.Truncate(l) 176 var body string 177 if data := t.app.ResourceSet.Get(path); data != nil { 178 if b, ok := data.(string); ok { 179 buf.WriteString(b) 180 body = buf.String() 181 } 182 } else { 183 f, err := os.Open(path) 184 if err != nil { 185 return err 186 } 187 _, err = io.Copy(buf, f) 188 f.Close() 189 if err != nil { 190 return err 191 } 192 body = buf.String() 193 t.app.ResourceSet.Add(path, body) 194 } 195 if _, err := tmpl.New(name).Delims(t.LeftDelim, t.RightDelim).Funcs(template.FuncMap(t.FuncMap)).Parse(body); err != nil { 196 return err 197 } 198 } 199 for _, t := range tmpl.Templates() { 200 key := templateKey{ 201 appName: appName, 202 name: strings.TrimSuffix(t.Name(), ext), 203 format: ext[1:], // truncate the leading dot. 204 } 205 if strings.HasPrefix(key.name, layoutPath) { 206 key.isLayout = true 207 key.name = key.name[len(layoutPath):] 208 } 209 m[key] = t 210 } 211 } 212 return nil 213 } 214 215 func (t *Template) yield(c *Context) (template.HTML, error) { 216 tmpl, err := t.Get(t.app.Config.AppName, "", c.Name, c.Format) 217 if err != nil { 218 return "", err 219 } 220 buf := bufPool.Get().(*bytes.Buffer) 221 defer func() { 222 buf.Reset() 223 bufPool.Put(buf) 224 }() 225 if err := tmpl.Execute(buf, c); err != nil { 226 return "", err 227 } 228 return template.HTML(buf.String()), nil 229 } 230 231 // in is for "in" template function. 232 func (t *Template) in(a, b interface{}) (bool, error) { 233 v := reflect.ValueOf(a) 234 switch v.Kind() { 235 case reflect.Slice, reflect.Array, reflect.String: 236 if v.IsNil() { 237 return false, nil 238 } 239 for i := 0; i < v.Len(); i++ { 240 if v.Index(i).Interface() == b { 241 return true, nil 242 } 243 } 244 default: 245 return false, fmt.Errorf("valid types are slice, array and string, got `%s'", v.Kind()) 246 } 247 return false, nil 248 } 249 250 // url is for "url" template function. 251 func (t *Template) url(name string, v ...interface{}) (string, error) { 252 return t.app.Router.Reverse(name, v...) 253 } 254 255 // nl2br is for "nl2br" template function. 256 func (t *Template) nl2br(text string) template.HTML { 257 return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1)) 258 } 259 260 // raw is for "raw" template function. 261 func (t *Template) raw(text string) template.HTML { 262 return template.HTML(text) 263 } 264 265 // invokeTemplate is for "invoke_template" template function. 266 func (t *Template) invokeTemplate(unit Unit, tmplName, defTmplName string, ctx ...*Context) (html template.HTML, err error) { 267 var c *Context 268 switch len(ctx) { 269 case 0: // do nothing. 270 case 1: 271 c = ctx[0] 272 default: 273 return "", fmt.Errorf("number of context must be 0 or 1") 274 } 275 t.app.Invoke(unit, func() { 276 if html, err = t.readPartialTemplate(tmplName, c); err != nil { 277 // TODO: logging error. 278 panic(ErrInvokeDefault) 279 } 280 }, func() { 281 html, err = t.readPartialTemplate(defTmplName, c) 282 }) 283 return html, err 284 } 285 286 // flash is for "flash" template function. 287 // This is a shorthand for {{.Flash.Get "success"}} in template. 288 func (t *Template) flash(c *Context, key string) string { 289 return c.Flash.Get(key) 290 } 291 292 // join is for "join" template function. 293 func (t *Template) join(a interface{}, sep string) (string, error) { 294 v := reflect.ValueOf(a) 295 switch v.Kind() { 296 case reflect.Slice, reflect.Array: 297 // do nothing. 298 default: 299 return "", fmt.Errorf("valid types of first argument are slice or array, got `%s'", v.Kind()) 300 } 301 if v.Len() == 0 { 302 return "", nil 303 } 304 buf := append(make([]byte, 0, v.Len()*2-1), fmt.Sprint(v.Index(0).Interface())...) 305 for i := 1; i < v.Len(); i++ { 306 buf = append(append(buf, sep...), fmt.Sprint(v.Index(i).Interface())...) 307 } 308 return string(buf), nil 309 } 310 311 func (t *Template) readPartialTemplate(name string, c *Context) (template.HTML, error) { 312 tmpl, err := t.Get(t.app.Config.AppName, "", name, "html") 313 if err != nil { 314 return "", err 315 } 316 buf := bufPool.Get().(*bytes.Buffer) 317 defer func() { 318 buf.Reset() 319 bufPool.Put(buf) 320 }() 321 if err := tmpl.Execute(buf, c); err != nil { 322 return "", err 323 } 324 return template.HTML(buf.String()), nil 325 } 326 327 func errorTemplateName(code int) string { 328 return filepath.Join(ErrorTemplateDir, strconv.Itoa(code)) 329 }