|
@@ -0,0 +1,286 @@
|
|
|
+// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
|
+// Use of this source code is governed by a MIT-style
|
|
|
+// license that can be found in the LICENSE file.
|
|
|
+
|
|
|
+// foked from https://github.com/martini-contrib/render/blob/master/render.go
|
|
|
+package middleware
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "html/template"
|
|
|
+ "io"
|
|
|
+ "io/ioutil"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/codegangsta/martini"
|
|
|
+
|
|
|
+ "github.com/gogits/gogs/modules/base"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ ContentType = "Content-Type"
|
|
|
+ ContentLength = "Content-Length"
|
|
|
+ ContentJSON = "application/json"
|
|
|
+ ContentHTML = "text/html"
|
|
|
+ ContentXHTML = "application/xhtml+xml"
|
|
|
+ defaultCharset = "UTF-8"
|
|
|
+)
|
|
|
+
|
|
|
+var helperFuncs = template.FuncMap{
|
|
|
+ "yield": func() (string, error) {
|
|
|
+ return "", fmt.Errorf("yield called with no layout defined")
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+type Delims struct {
|
|
|
+ Left string
|
|
|
+
|
|
|
+ Right string
|
|
|
+}
|
|
|
+
|
|
|
+type RenderOptions struct {
|
|
|
+ Directory string
|
|
|
+
|
|
|
+ Layout string
|
|
|
+
|
|
|
+ Extensions []string
|
|
|
+
|
|
|
+ Funcs []template.FuncMap
|
|
|
+
|
|
|
+ Delims Delims
|
|
|
+
|
|
|
+ Charset string
|
|
|
+
|
|
|
+ IndentJSON bool
|
|
|
+
|
|
|
+ HTMLContentType string
|
|
|
+}
|
|
|
+
|
|
|
+type HTMLOptions struct {
|
|
|
+ Layout string
|
|
|
+}
|
|
|
+
|
|
|
+func Renderer(options ...RenderOptions) martini.Handler {
|
|
|
+ opt := prepareOptions(options)
|
|
|
+ cs := prepareCharset(opt.Charset)
|
|
|
+ t := compile(opt)
|
|
|
+ return func(res http.ResponseWriter, req *http.Request, c martini.Context) {
|
|
|
+ var tc *template.Template
|
|
|
+ if martini.Env == martini.Dev {
|
|
|
+
|
|
|
+ tc = compile(opt)
|
|
|
+ } else {
|
|
|
+
|
|
|
+ tc, _ = t.Clone()
|
|
|
+ }
|
|
|
+
|
|
|
+ rd := &Render{res, req, tc, opt, cs, base.TmplData{}, time.Time{}}
|
|
|
+
|
|
|
+ rd.Data["TmplLoadTimes"] = func() string {
|
|
|
+ if rd.startTime.IsZero() {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ return fmt.Sprint(time.Since(rd.startTime).Nanoseconds()/1e6) + "ms"
|
|
|
+ }
|
|
|
+
|
|
|
+ c.Map(rd.Data)
|
|
|
+ c.Map(rd)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func prepareCharset(charset string) string {
|
|
|
+ if len(charset) != 0 {
|
|
|
+ return "; charset=" + charset
|
|
|
+ }
|
|
|
+
|
|
|
+ return "; charset=" + defaultCharset
|
|
|
+}
|
|
|
+
|
|
|
+func prepareOptions(options []RenderOptions) RenderOptions {
|
|
|
+ var opt RenderOptions
|
|
|
+ if len(options) > 0 {
|
|
|
+ opt = options[0]
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(opt.Directory) == 0 {
|
|
|
+ opt.Directory = "templates"
|
|
|
+ }
|
|
|
+ if len(opt.Extensions) == 0 {
|
|
|
+ opt.Extensions = []string{".tmpl"}
|
|
|
+ }
|
|
|
+ if len(opt.HTMLContentType) == 0 {
|
|
|
+ opt.HTMLContentType = ContentHTML
|
|
|
+ }
|
|
|
+
|
|
|
+ return opt
|
|
|
+}
|
|
|
+
|
|
|
+func compile(options RenderOptions) *template.Template {
|
|
|
+ dir := options.Directory
|
|
|
+ t := template.New(dir)
|
|
|
+ t.Delims(options.Delims.Left, options.Delims.Right)
|
|
|
+
|
|
|
+ template.Must(t.Parse("Martini"))
|
|
|
+
|
|
|
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
+ r, err := filepath.Rel(dir, path)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ ext := filepath.Ext(r)
|
|
|
+ for _, extension := range options.Extensions {
|
|
|
+ if ext == extension {
|
|
|
+
|
|
|
+ buf, err := ioutil.ReadFile(path)
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ name := (r[0 : len(r)-len(ext)])
|
|
|
+ tmpl := t.New(filepath.ToSlash(name))
|
|
|
+
|
|
|
+ for _, funcs := range options.Funcs {
|
|
|
+ tmpl.Funcs(funcs)
|
|
|
+ }
|
|
|
+
|
|
|
+ template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+
|
|
|
+ return t
|
|
|
+}
|
|
|
+
|
|
|
+type Render struct {
|
|
|
+ http.ResponseWriter
|
|
|
+ req *http.Request
|
|
|
+ t *template.Template
|
|
|
+ opt RenderOptions
|
|
|
+ compiledCharset string
|
|
|
+
|
|
|
+ Data base.TmplData
|
|
|
+
|
|
|
+ startTime time.Time
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) JSON(status int, v interface{}) {
|
|
|
+ var result []byte
|
|
|
+ var err error
|
|
|
+ if r.opt.IndentJSON {
|
|
|
+ result, err = json.MarshalIndent(v, "", " ")
|
|
|
+ } else {
|
|
|
+ result, err = json.Marshal(v)
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ http.Error(r, err.Error(), 500)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ r.Header().Set(ContentType, ContentJSON+r.compiledCharset)
|
|
|
+ r.WriteHeader(status)
|
|
|
+ r.Write(result)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) JSONString(v interface{}) (string, error) {
|
|
|
+ var result []byte
|
|
|
+ var err error
|
|
|
+ if r.opt.IndentJSON {
|
|
|
+ result, err = json.MarshalIndent(v, "", " ")
|
|
|
+ } else {
|
|
|
+ result, err = json.Marshal(v)
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ return string(result), nil
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) renderBytes(name string, binding interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
|
|
|
+ opt := r.prepareHTMLOptions(htmlOpt)
|
|
|
+
|
|
|
+ if len(opt.Layout) > 0 {
|
|
|
+ r.addYield(name, binding)
|
|
|
+ name = opt.Layout
|
|
|
+ }
|
|
|
+
|
|
|
+ out, err := r.execute(name, binding)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return out, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) HTML(status int, name string, binding interface{}, htmlOpt ...HTMLOptions) {
|
|
|
+ r.startTime = time.Now()
|
|
|
+
|
|
|
+ out, err := r.renderBytes(name, binding, htmlOpt...)
|
|
|
+ if err != nil {
|
|
|
+ http.Error(r, err.Error(), http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ r.Header().Set(ContentType, r.opt.HTMLContentType+r.compiledCharset)
|
|
|
+ r.WriteHeader(status)
|
|
|
+ io.Copy(r, out)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
|
|
+ if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
|
|
|
+ return "", err
|
|
|
+ } else {
|
|
|
+ return out.String(), nil
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) Error(status int) {
|
|
|
+ r.WriteHeader(status)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) Redirect(location string, status ...int) {
|
|
|
+ code := http.StatusFound
|
|
|
+ if len(status) == 1 {
|
|
|
+ code = status[0]
|
|
|
+ }
|
|
|
+
|
|
|
+ http.Redirect(r, r.req, location, code)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) Template() *template.Template {
|
|
|
+ return r.t
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) execute(name string, binding interface{}) (*bytes.Buffer, error) {
|
|
|
+ buf := new(bytes.Buffer)
|
|
|
+ return buf, r.t.ExecuteTemplate(buf, name, binding)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) addYield(name string, binding interface{}) {
|
|
|
+ funcs := template.FuncMap{
|
|
|
+ "yield": func() (template.HTML, error) {
|
|
|
+ buf, err := r.execute(name, binding)
|
|
|
+
|
|
|
+ return template.HTML(buf.String()), err
|
|
|
+ },
|
|
|
+ }
|
|
|
+ r.t.Funcs(funcs)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
|
|
|
+ if len(htmlOpt) > 0 {
|
|
|
+ return htmlOpt[0]
|
|
|
+ }
|
|
|
+
|
|
|
+ return HTMLOptions{
|
|
|
+ Layout: r.opt.Layout,
|
|
|
+ }
|
|
|
+}
|