123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- package renderer
- import (
- "encoding/json"
- "fmt"
- "html/template"
- "log"
- "net/http"
- "net/url"
- "path"
- "path/filepath"
- "reflect"
- "strings"
- "github.com/gorilla/schema"
- )
- type Renderer interface {
- Render(http.ResponseWriter, *http.Request, interface{}, ...url.Values) error
- }
- type JSONRenderer struct{}
- type htmlTemplateData struct {
- Data interface{}
- Options url.Values
- }
- type JsonResponse struct {
- Result []byte
- Error []byte
- }
- var (
- currRenderer Renderer
- Render map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values)
- funcMap = template.FuncMap{
- "query": query,
- }
- )
- func init() {
- htmlRenderer, err := NewHTMLRenderer("templates/")
- if err != nil {
- panic(err)
- }
- jsonRenderer, err := NewJSONRenderer()
- if err != nil {
- panic(err)
- }
- Render = make(map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values))
- Render["html"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- htmlRenderer.Render(w, r, data, options...)
- }
- Render["json"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
- jsonRenderer.Render(w, r, data, options...)
- }
- }
- func query(values ...string) template.URL {
- var urlValues url.Values
- urlValues = make(url.Values)
- urlValues.Set("format", "html")
- for i := 0; i < len(values); i += 2 {
- urlValues.Add(values[i], values[i+1])
- }
- return template.URL(urlValues.Encode())
- }
- // 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, error) {
- return &JSONRenderer{}, nil
- }
- func (rend *JSONRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- if isErrorType(data) {
- j, err := json.Marshal(JsonResponse{nil, []byte(data.(error).Error())})
- if err != nil {
- return err
- }
- w.WriteHeader(http.StatusInternalServerError)
- w.Write(j)
- } else {
- result, err := json.Marshal(data)
- if err != nil {
- return err
- }
- response, err := json.Marshal(JsonResponse{result, nil})
- if err != nil {
- return err
- }
- w.Write(response)
- }
- 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.New(tplName)
- r.templates[tplName] = template.Must(r.templates[tplName].Funcs(funcMap).ParseFiles(files...))
- }
- return r, nil
- }
- func Use(r Renderer) {
- currRenderer = r
- }
- func (rend *HTMLRenderer) writeError(w http.ResponseWriter, r *http.Request, err error) {
- log.Println(rend.templates)
- 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 ...url.Values) {
- if data != nil && isErrorType(data) {
- rend.writeError(w, r, data.(error))
- } else {
- t, ok := rend.templates[options[0]["tpl_content"][0]]
- if !ok {
- rend.writeError(w, r, fmt.Errorf("Template %s not found", options[0]["tpl_content"][0]))
- }
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- err := t.ExecuteTemplate(w, options[0]["tpl_layout"][0], &htmlTemplateData{data, options[0]})
- if err != nil {
- rend.writeError(w, r, err)
- }
- }
- }
- func Decode(dst interface{}, r *http.Request) error {
- switch r.Header.Get("Content-type") {
- case "application/x-www-form-urlencoded":
- if err := r.ParseForm(); err != nil {
- return err
- }
- decoder := schema.NewDecoder()
- if err := decoder.Decode(dst, r.PostForm); err != nil {
- return err
- }
- case "application/json; charset=utf-8":
- decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&dst)
- if err != nil {
- return err
- }
- }
- return nil
- }
|