You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

render.go 5.8 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. // foked from https://github.com/martini-contrib/render/blob/master/render.go
  5. package middleware
  6. import (
  7. "bytes"
  8. "encoding/json"
  9. "fmt"
  10. "html/template"
  11. "io"
  12. "io/ioutil"
  13. "net/http"
  14. "os"
  15. "path/filepath"
  16. "time"
  17. "github.com/go-martini/martini"
  18. "github.com/gogits/gogs/modules/base"
  19. )
  20. const (
  21. ContentType = "Content-Type"
  22. ContentLength = "Content-Length"
  23. ContentJSON = "application/json"
  24. ContentHTML = "text/html"
  25. ContentXHTML = "application/xhtml+xml"
  26. defaultCharset = "UTF-8"
  27. )
  28. var helperFuncs = template.FuncMap{
  29. "yield": func() (string, error) {
  30. return "", fmt.Errorf("yield called with no layout defined")
  31. },
  32. }
  33. type Delims struct {
  34. Left string
  35. Right string
  36. }
  37. type RenderOptions struct {
  38. Directory string
  39. Layout string
  40. Extensions []string
  41. Funcs []template.FuncMap
  42. Delims Delims
  43. Charset string
  44. IndentJSON bool
  45. HTMLContentType string
  46. }
  47. type HTMLOptions struct {
  48. Layout string
  49. }
  50. func Renderer(options ...RenderOptions) martini.Handler {
  51. opt := prepareOptions(options)
  52. cs := prepareCharset(opt.Charset)
  53. t := compile(opt)
  54. return func(res http.ResponseWriter, req *http.Request, c martini.Context) {
  55. var tc *template.Template
  56. if martini.Env == martini.Dev {
  57. tc = compile(opt)
  58. } else {
  59. tc, _ = t.Clone()
  60. }
  61. rd := &Render{res, req, tc, opt, cs, base.TmplData{}, time.Time{}}
  62. rd.Data["TmplLoadTimes"] = func() string {
  63. if rd.startTime.IsZero() {
  64. return ""
  65. }
  66. return fmt.Sprint(time.Since(rd.startTime).Nanoseconds()/1e6) + "ms"
  67. }
  68. c.Map(rd.Data)
  69. c.Map(rd)
  70. }
  71. }
  72. func prepareCharset(charset string) string {
  73. if len(charset) != 0 {
  74. return "; charset=" + charset
  75. }
  76. return "; charset=" + defaultCharset
  77. }
  78. func prepareOptions(options []RenderOptions) RenderOptions {
  79. var opt RenderOptions
  80. if len(options) > 0 {
  81. opt = options[0]
  82. }
  83. if len(opt.Directory) == 0 {
  84. opt.Directory = "templates"
  85. }
  86. if len(opt.Extensions) == 0 {
  87. opt.Extensions = []string{".tmpl"}
  88. }
  89. if len(opt.HTMLContentType) == 0 {
  90. opt.HTMLContentType = ContentHTML
  91. }
  92. return opt
  93. }
  94. func compile(options RenderOptions) *template.Template {
  95. dir := options.Directory
  96. t := template.New(dir)
  97. t.Delims(options.Delims.Left, options.Delims.Right)
  98. template.Must(t.Parse("Martini"))
  99. filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
  100. r, err := filepath.Rel(dir, path)
  101. if err != nil {
  102. return err
  103. }
  104. ext := filepath.Ext(r)
  105. for _, extension := range options.Extensions {
  106. if ext == extension {
  107. buf, err := ioutil.ReadFile(path)
  108. if err != nil {
  109. panic(err)
  110. }
  111. name := (r[0 : len(r)-len(ext)])
  112. tmpl := t.New(filepath.ToSlash(name))
  113. for _, funcs := range options.Funcs {
  114. tmpl = tmpl.Funcs(funcs)
  115. }
  116. template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
  117. break
  118. }
  119. }
  120. return nil
  121. })
  122. return t
  123. }
  124. type Render struct {
  125. http.ResponseWriter
  126. req *http.Request
  127. t *template.Template
  128. opt RenderOptions
  129. compiledCharset string
  130. Data base.TmplData
  131. startTime time.Time
  132. }
  133. func (r *Render) JSON(status int, v interface{}) {
  134. var result []byte
  135. var err error
  136. if r.opt.IndentJSON {
  137. result, err = json.MarshalIndent(v, "", " ")
  138. } else {
  139. result, err = json.Marshal(v)
  140. }
  141. if err != nil {
  142. http.Error(r, err.Error(), 500)
  143. return
  144. }
  145. r.Header().Set(ContentType, ContentJSON+r.compiledCharset)
  146. r.WriteHeader(status)
  147. r.Write(result)
  148. }
  149. func (r *Render) JSONString(v interface{}) (string, error) {
  150. var result []byte
  151. var err error
  152. if r.opt.IndentJSON {
  153. result, err = json.MarshalIndent(v, "", " ")
  154. } else {
  155. result, err = json.Marshal(v)
  156. }
  157. if err != nil {
  158. return "", err
  159. }
  160. return string(result), nil
  161. }
  162. func (r *Render) renderBytes(name string, binding interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
  163. opt := r.prepareHTMLOptions(htmlOpt)
  164. if len(opt.Layout) > 0 {
  165. r.addYield(name, binding)
  166. name = opt.Layout
  167. }
  168. out, err := r.execute(name, binding)
  169. if err != nil {
  170. return nil, err
  171. }
  172. return out, nil
  173. }
  174. func (r *Render) HTML(status int, name string, binding interface{}, htmlOpt ...HTMLOptions) {
  175. r.startTime = time.Now()
  176. out, err := r.renderBytes(name, binding, htmlOpt...)
  177. if err != nil {
  178. http.Error(r, err.Error(), http.StatusInternalServerError)
  179. return
  180. }
  181. r.Header().Set(ContentType, r.opt.HTMLContentType+r.compiledCharset)
  182. r.WriteHeader(status)
  183. io.Copy(r, out)
  184. }
  185. func (r *Render) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
  186. if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
  187. return "", err
  188. } else {
  189. return out.String(), nil
  190. }
  191. }
  192. func (r *Render) Error(status int, message ...string) {
  193. r.WriteHeader(status)
  194. if len(message) > 0 {
  195. r.Write([]byte(message[0]))
  196. }
  197. }
  198. func (r *Render) Redirect(location string, status ...int) {
  199. code := http.StatusFound
  200. if len(status) == 1 {
  201. code = status[0]
  202. }
  203. http.Redirect(r, r.req, location, code)
  204. }
  205. func (r *Render) Template() *template.Template {
  206. return r.t
  207. }
  208. func (r *Render) execute(name string, binding interface{}) (*bytes.Buffer, error) {
  209. buf := new(bytes.Buffer)
  210. return buf, r.t.ExecuteTemplate(buf, name, binding)
  211. }
  212. func (r *Render) addYield(name string, binding interface{}) {
  213. funcs := template.FuncMap{
  214. "yield": func() (template.HTML, error) {
  215. buf, err := r.execute(name, binding)
  216. return template.HTML(buf.String()), err
  217. },
  218. }
  219. r.t.Funcs(funcs)
  220. }
  221. func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
  222. if len(htmlOpt) > 0 {
  223. return htmlOpt[0]
  224. }
  225. return HTMLOptions{
  226. Layout: r.opt.Layout,
  227. }
  228. }