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

render.go 5.6KB

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. }