package renderer import ( "encoding/json" "fmt" "html/template" "net/http" "path" "path/filepath" "reflect" "strings" ) type Renderer interface { Render(http.ResponseWriter, *http.Request, interface{}, ...string) error } type JSONRenderer struct{} var ( currRenderer Renderer Render map[string]func(http.ResponseWriter, *http.Request, interface{}, ...string) ) func init() { htmlRenderer, err := NewHTMLRenderer("templates/") if err != nil { panic(err) } Render = make(map[string]func(http.ResponseWriter, *http.Request, interface{}, ...string)) Render["html"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...string) { htmlRenderer.Render(w, r, data, options...) } } // FIXME: Not safe. We should check if it responds to the error interface instead. func isErrorType(data interface{}) bool { return strings.Contains(strings.ToLower(reflect.TypeOf(data).String()), "error") } func NewJSONRenderer() *JSONRenderer { return &JSONRenderer{} } func (r *JSONRenderer) Render(w http.ResponseWriter, layout string, name string, data interface{}) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") j, err := json.Marshal(data) if err != nil { return err } w.Write(j) return nil } type HTMLRenderer struct { TemplatePath string templates map[string]*template.Template } func NewHTMLRenderer(templatePath string) (*HTMLRenderer, error) { r := &HTMLRenderer{ TemplatePath: templatePath, templates: make(map[string]*template.Template), } fns, err := filepath.Glob(filepath.Join(templatePath, "*.tpl")) if err != nil { panic(err) } lfns, err := filepath.Glob(filepath.Join(templatePath, "layout", "*.tpl")) if err != nil { panic(err) } for _, fn := range fns { tplName := filepath.Base(fn) tplName = strings.TrimSuffix(tplName, path.Ext(tplName)) tplName = strings.TrimSuffix(tplName, path.Ext(tplName)) files := append(lfns, fn) if err != nil { return nil, err } r.templates[tplName] = template.Must(r.templates[tplName].ParseFiles(files...)) if err != nil { return nil, err } } return r, nil } func Use(r Renderer) { currRenderer = r } func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, err error) { t, ok := rend.templates["error"] if !ok { panic(fmt.Errorf("Error template not found! Can't proceed, sorry.")) } w.Header().Set("Content-Type", "text/html; charset=utf-8") e := t.ExecuteTemplate(w, "base", err) if e != nil { panic(e) } } func (rend *HTMLRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...string) { if isErrorType(data) { rend.writeError(w, r, data.(error)) } else { t, ok := rend.templates[options[1]] if !ok { rend.writeError(w, r, fmt.Errorf("Template %s not found", options[1])) } w.Header().Set("Content-Type", "text/html; charset=utf-8") err := t.ExecuteTemplate(w, options[0], data) if err != nil { rend.writeError(w, r, err) } } }