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 19 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. // Copyright 2013 Martini Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. package macaron
  16. import (
  17. "bytes"
  18. "encoding/json"
  19. "encoding/xml"
  20. "fmt"
  21. "html/template"
  22. "io"
  23. "io/ioutil"
  24. "net/http"
  25. "os"
  26. "path"
  27. "path/filepath"
  28. "strings"
  29. "sync"
  30. "time"
  31. "github.com/unknwon/com"
  32. )
  33. const (
  34. _CONTENT_TYPE = "Content-Type"
  35. _CONTENT_BINARY = "application/octet-stream"
  36. _CONTENT_JSON = "application/json"
  37. _CONTENT_HTML = "text/html"
  38. _CONTENT_PLAIN = "text/plain"
  39. _CONTENT_XHTML = "application/xhtml+xml"
  40. _CONTENT_XML = "text/xml"
  41. _DEFAULT_CHARSET = "UTF-8"
  42. )
  43. var (
  44. // Provides a temporary buffer to execute templates into and catch errors.
  45. bufpool = sync.Pool{
  46. New: func() interface{} { return new(bytes.Buffer) },
  47. }
  48. // Included helper functions for use when rendering html
  49. helperFuncs = template.FuncMap{
  50. "yield": func() (string, error) {
  51. return "", fmt.Errorf("yield called with no layout defined")
  52. },
  53. "current": func() (string, error) {
  54. return "", nil
  55. },
  56. }
  57. )
  58. type (
  59. // TemplateFile represents a interface of template file that has name and can be read.
  60. TemplateFile interface {
  61. Name() string
  62. Data() []byte
  63. Ext() string
  64. }
  65. // TemplateFileSystem represents a interface of template file system that able to list all files.
  66. TemplateFileSystem interface {
  67. ListFiles() []TemplateFile
  68. Get(string) (io.Reader, error)
  69. }
  70. // Delims represents a set of Left and Right delimiters for HTML template rendering
  71. Delims struct {
  72. // Left delimiter, defaults to {{
  73. Left string
  74. // Right delimiter, defaults to }}
  75. Right string
  76. }
  77. // RenderOptions represents a struct for specifying configuration options for the Render middleware.
  78. RenderOptions struct {
  79. // Directory to load templates. Default is "templates".
  80. Directory string
  81. // Addtional directories to overwite templates.
  82. AppendDirectories []string
  83. // Layout template name. Will not render a layout if "". Default is to "".
  84. Layout string
  85. // Extensions to parse template files from. Defaults are [".tmpl", ".html"].
  86. Extensions []string
  87. // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
  88. Funcs []template.FuncMap
  89. // Delims sets the action delimiters to the specified strings in the Delims struct.
  90. Delims Delims
  91. // Appends the given charset to the Content-Type header. Default is "UTF-8".
  92. Charset string
  93. // Outputs human readable JSON.
  94. IndentJSON bool
  95. // Outputs human readable XML.
  96. IndentXML bool
  97. // Prefixes the JSON output with the given bytes.
  98. PrefixJSON []byte
  99. // Prefixes the XML output with the given bytes.
  100. PrefixXML []byte
  101. // Allows changing of output to XHTML instead of HTML. Default is "text/html"
  102. HTMLContentType string
  103. // TemplateFileSystem is the interface for supporting any implmentation of template file system.
  104. TemplateFileSystem
  105. }
  106. // HTMLOptions is a struct for overriding some rendering Options for specific HTML call
  107. HTMLOptions struct {
  108. // Layout template name. Overrides Options.Layout.
  109. Layout string
  110. }
  111. Render interface {
  112. http.ResponseWriter
  113. SetResponseWriter(http.ResponseWriter)
  114. JSON(int, interface{})
  115. JSONString(interface{}) (string, error)
  116. RawData(int, []byte) // Serve content as binary
  117. PlainText(int, []byte) // Serve content as plain text
  118. HTML(int, string, interface{}, ...HTMLOptions)
  119. HTMLSet(int, string, string, interface{}, ...HTMLOptions)
  120. HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
  121. HTMLString(string, interface{}, ...HTMLOptions) (string, error)
  122. HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
  123. HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
  124. XML(int, interface{})
  125. Error(int, ...string)
  126. Status(int)
  127. SetTemplatePath(string, string)
  128. HasTemplateSet(string) bool
  129. }
  130. )
  131. // TplFile implements TemplateFile interface.
  132. type TplFile struct {
  133. name string
  134. data []byte
  135. ext string
  136. }
  137. // NewTplFile cerates new template file with given name and data.
  138. func NewTplFile(name string, data []byte, ext string) *TplFile {
  139. return &TplFile{name, data, ext}
  140. }
  141. func (f *TplFile) Name() string {
  142. return f.name
  143. }
  144. func (f *TplFile) Data() []byte {
  145. return f.data
  146. }
  147. func (f *TplFile) Ext() string {
  148. return f.ext
  149. }
  150. // TplFileSystem implements TemplateFileSystem interface.
  151. type TplFileSystem struct {
  152. files []TemplateFile
  153. }
  154. // NewTemplateFileSystem creates new template file system with given options.
  155. func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
  156. fs := TplFileSystem{}
  157. fs.files = make([]TemplateFile, 0, 10)
  158. // Directories are composed in reverse order because later one overwrites previous ones,
  159. // so once found, we can directly jump out of the loop.
  160. dirs := make([]string, 0, len(opt.AppendDirectories)+1)
  161. for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {
  162. dirs = append(dirs, opt.AppendDirectories[i])
  163. }
  164. dirs = append(dirs, opt.Directory)
  165. var err error
  166. for i := range dirs {
  167. // Skip ones that does not exists for symlink test,
  168. // but allow non-symlink ones added after start.
  169. if !com.IsExist(dirs[i]) {
  170. continue
  171. }
  172. dirs[i], err = filepath.EvalSymlinks(dirs[i])
  173. if err != nil {
  174. panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())
  175. }
  176. }
  177. lastDir := dirs[len(dirs)-1]
  178. // We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
  179. if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, _ error) error {
  180. r, err := filepath.Rel(lastDir, path)
  181. if err != nil {
  182. return err
  183. }
  184. ext := GetExt(r)
  185. for _, extension := range opt.Extensions {
  186. if ext != extension {
  187. continue
  188. }
  189. var data []byte
  190. if !omitData {
  191. // Loop over candidates of directory, break out once found.
  192. // The file always exists because it's inside the walk function,
  193. // and read original file is the worst case.
  194. for i := range dirs {
  195. path = filepath.Join(dirs[i], r)
  196. if !com.IsFile(path) {
  197. continue
  198. }
  199. data, err = ioutil.ReadFile(path)
  200. if err != nil {
  201. return err
  202. }
  203. break
  204. }
  205. }
  206. name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
  207. fs.files = append(fs.files, NewTplFile(name, data, ext))
  208. }
  209. return nil
  210. }); err != nil {
  211. panic("NewTemplateFileSystem: " + err.Error())
  212. }
  213. return fs
  214. }
  215. func (fs TplFileSystem) ListFiles() []TemplateFile {
  216. return fs.files
  217. }
  218. func (fs TplFileSystem) Get(name string) (io.Reader, error) {
  219. for i := range fs.files {
  220. if fs.files[i].Name()+fs.files[i].Ext() == name {
  221. return bytes.NewReader(fs.files[i].Data()), nil
  222. }
  223. }
  224. return nil, fmt.Errorf("file '%s' not found", name)
  225. }
  226. func PrepareCharset(charset string) string {
  227. if len(charset) != 0 {
  228. return "; charset=" + charset
  229. }
  230. return "; charset=" + _DEFAULT_CHARSET
  231. }
  232. func GetExt(s string) string {
  233. index := strings.Index(s, ".")
  234. if index == -1 {
  235. return ""
  236. }
  237. return s[index:]
  238. }
  239. func compile(opt RenderOptions) *template.Template {
  240. t := template.New(opt.Directory)
  241. t.Delims(opt.Delims.Left, opt.Delims.Right)
  242. // Parse an initial template in case we don't have any.
  243. template.Must(t.Parse("Macaron"))
  244. if opt.TemplateFileSystem == nil {
  245. opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
  246. }
  247. for _, f := range opt.TemplateFileSystem.ListFiles() {
  248. tmpl := t.New(f.Name())
  249. for _, funcs := range opt.Funcs {
  250. tmpl.Funcs(funcs)
  251. }
  252. // Bomb out if parse fails. We don't want any silent server starts.
  253. template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
  254. }
  255. return t
  256. }
  257. const (
  258. DEFAULT_TPL_SET_NAME = "DEFAULT"
  259. )
  260. // TemplateSet represents a template set of type *template.Template.
  261. type TemplateSet struct {
  262. lock sync.RWMutex
  263. sets map[string]*template.Template
  264. dirs map[string]string
  265. }
  266. // NewTemplateSet initializes a new empty template set.
  267. func NewTemplateSet() *TemplateSet {
  268. return &TemplateSet{
  269. sets: make(map[string]*template.Template),
  270. dirs: make(map[string]string),
  271. }
  272. }
  273. func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {
  274. t := compile(*opt)
  275. ts.lock.Lock()
  276. defer ts.lock.Unlock()
  277. ts.sets[name] = t
  278. ts.dirs[name] = opt.Directory
  279. return t
  280. }
  281. func (ts *TemplateSet) Get(name string) *template.Template {
  282. ts.lock.RLock()
  283. defer ts.lock.RUnlock()
  284. return ts.sets[name]
  285. }
  286. func (ts *TemplateSet) GetDir(name string) string {
  287. ts.lock.RLock()
  288. defer ts.lock.RUnlock()
  289. return ts.dirs[name]
  290. }
  291. func prepareRenderOptions(options []RenderOptions) RenderOptions {
  292. var opt RenderOptions
  293. if len(options) > 0 {
  294. opt = options[0]
  295. }
  296. // Defaults.
  297. if len(opt.Directory) == 0 {
  298. opt.Directory = "templates"
  299. }
  300. if len(opt.Extensions) == 0 {
  301. opt.Extensions = []string{".tmpl", ".html"}
  302. }
  303. if len(opt.HTMLContentType) == 0 {
  304. opt.HTMLContentType = _CONTENT_HTML
  305. }
  306. return opt
  307. }
  308. func ParseTplSet(tplSet string) (tplName string, tplDir string) {
  309. tplSet = strings.TrimSpace(tplSet)
  310. if len(tplSet) == 0 {
  311. panic("empty template set argument")
  312. }
  313. infos := strings.Split(tplSet, ":")
  314. if len(infos) == 1 {
  315. tplDir = infos[0]
  316. tplName = path.Base(tplDir)
  317. } else {
  318. tplName = infos[0]
  319. tplDir = infos[1]
  320. }
  321. if !com.IsDir(tplDir) {
  322. panic("template set path does not exist or is not a directory")
  323. }
  324. return tplName, tplDir
  325. }
  326. func renderHandler(opt RenderOptions, tplSets []string) Handler {
  327. cs := PrepareCharset(opt.Charset)
  328. ts := NewTemplateSet()
  329. ts.Set(DEFAULT_TPL_SET_NAME, &opt)
  330. var tmpOpt RenderOptions
  331. for _, tplSet := range tplSets {
  332. tplName, tplDir := ParseTplSet(tplSet)
  333. tmpOpt = opt
  334. tmpOpt.Directory = tplDir
  335. ts.Set(tplName, &tmpOpt)
  336. }
  337. return func(ctx *Context) {
  338. r := &TplRender{
  339. ResponseWriter: ctx.Resp,
  340. TemplateSet: ts,
  341. Opt: &opt,
  342. CompiledCharset: cs,
  343. }
  344. ctx.Data["TmplLoadTimes"] = func() string {
  345. if r.startTime.IsZero() {
  346. return ""
  347. }
  348. return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
  349. }
  350. ctx.Render = r
  351. ctx.MapTo(r, (*Render)(nil))
  352. }
  353. }
  354. // Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
  355. // An single variadic macaron.RenderOptions struct can be optionally provided to configure
  356. // HTML rendering. The default directory for templates is "templates" and the default
  357. // file extension is ".tmpl" and ".html".
  358. //
  359. // If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
  360. // MACARON_ENV environment variable to "production".
  361. func Renderer(options ...RenderOptions) Handler {
  362. return renderHandler(prepareRenderOptions(options), []string{})
  363. }
  364. func Renderers(options RenderOptions, tplSets ...string) Handler {
  365. return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
  366. }
  367. type TplRender struct {
  368. http.ResponseWriter
  369. *TemplateSet
  370. Opt *RenderOptions
  371. CompiledCharset string
  372. startTime time.Time
  373. }
  374. func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
  375. r.ResponseWriter = rw
  376. }
  377. func (r *TplRender) JSON(status int, v interface{}) {
  378. var (
  379. result []byte
  380. err error
  381. )
  382. if r.Opt.IndentJSON {
  383. result, err = json.MarshalIndent(v, "", " ")
  384. } else {
  385. result, err = json.Marshal(v)
  386. }
  387. if err != nil {
  388. http.Error(r, err.Error(), 500)
  389. return
  390. }
  391. // json rendered fine, write out the result
  392. r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)
  393. r.WriteHeader(status)
  394. if len(r.Opt.PrefixJSON) > 0 {
  395. _, _ = r.Write(r.Opt.PrefixJSON)
  396. }
  397. _, _ = r.Write(result)
  398. }
  399. func (r *TplRender) JSONString(v interface{}) (string, error) {
  400. var result []byte
  401. var err error
  402. if r.Opt.IndentJSON {
  403. result, err = json.MarshalIndent(v, "", " ")
  404. } else {
  405. result, err = json.Marshal(v)
  406. }
  407. if err != nil {
  408. return "", err
  409. }
  410. return string(result), nil
  411. }
  412. func (r *TplRender) XML(status int, v interface{}) {
  413. var result []byte
  414. var err error
  415. if r.Opt.IndentXML {
  416. result, err = xml.MarshalIndent(v, "", " ")
  417. } else {
  418. result, err = xml.Marshal(v)
  419. }
  420. if err != nil {
  421. http.Error(r, err.Error(), 500)
  422. return
  423. }
  424. // XML rendered fine, write out the result
  425. r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)
  426. r.WriteHeader(status)
  427. if len(r.Opt.PrefixXML) > 0 {
  428. _, _ = r.Write(r.Opt.PrefixXML)
  429. }
  430. _, _ = r.Write(result)
  431. }
  432. func (r *TplRender) data(status int, contentType string, v []byte) {
  433. if r.Header().Get(_CONTENT_TYPE) == "" {
  434. r.Header().Set(_CONTENT_TYPE, contentType)
  435. }
  436. r.WriteHeader(status)
  437. _, _ = r.Write(v)
  438. }
  439. func (r *TplRender) RawData(status int, v []byte) {
  440. r.data(status, _CONTENT_BINARY, v)
  441. }
  442. func (r *TplRender) PlainText(status int, v []byte) {
  443. r.data(status, _CONTENT_PLAIN, v)
  444. }
  445. func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
  446. buf := bufpool.Get().(*bytes.Buffer)
  447. return buf, t.ExecuteTemplate(buf, name, data)
  448. }
  449. func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
  450. funcs := template.FuncMap{
  451. "yield": func() (template.HTML, error) {
  452. buf, err := r.execute(t, tplName, data)
  453. // return safe html here since we are rendering our own template
  454. return template.HTML(buf.String()), err
  455. },
  456. "current": func() (string, error) {
  457. return tplName, nil
  458. },
  459. }
  460. t.Funcs(funcs)
  461. }
  462. func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
  463. t := r.TemplateSet.Get(setName)
  464. if Env == DEV {
  465. opt := *r.Opt
  466. opt.Directory = r.TemplateSet.GetDir(setName)
  467. t = r.TemplateSet.Set(setName, &opt)
  468. }
  469. if t == nil {
  470. return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
  471. }
  472. opt := r.prepareHTMLOptions(htmlOpt)
  473. if len(opt.Layout) > 0 {
  474. r.addYield(t, tplName, data)
  475. tplName = opt.Layout
  476. }
  477. out, err := r.execute(t, tplName, data)
  478. if err != nil {
  479. return nil, err
  480. }
  481. return out, nil
  482. }
  483. func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
  484. r.startTime = time.Now()
  485. out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
  486. if err != nil {
  487. http.Error(r, err.Error(), http.StatusInternalServerError)
  488. return
  489. }
  490. r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)
  491. r.WriteHeader(status)
  492. if _, err := out.WriteTo(r); err != nil {
  493. out.Reset()
  494. }
  495. bufpool.Put(out)
  496. }
  497. func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
  498. r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
  499. }
  500. func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
  501. r.renderHTML(status, setName, tplName, data, htmlOpt...)
  502. }
  503. func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
  504. out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
  505. if err != nil {
  506. return []byte(""), err
  507. }
  508. return out.Bytes(), nil
  509. }
  510. func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
  511. return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
  512. }
  513. func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
  514. p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
  515. return string(p), err
  516. }
  517. func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
  518. p, err := r.HTMLBytes(name, data, htmlOpt...)
  519. return string(p), err
  520. }
  521. // Error writes the given HTTP status to the current ResponseWriter
  522. func (r *TplRender) Error(status int, message ...string) {
  523. r.WriteHeader(status)
  524. if len(message) > 0 {
  525. _, _ = r.Write([]byte(message[0]))
  526. }
  527. }
  528. func (r *TplRender) Status(status int) {
  529. r.WriteHeader(status)
  530. }
  531. func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
  532. if len(htmlOpt) > 0 {
  533. return htmlOpt[0]
  534. }
  535. return HTMLOptions{
  536. Layout: r.Opt.Layout,
  537. }
  538. }
  539. func (r *TplRender) SetTemplatePath(setName, dir string) {
  540. if len(setName) == 0 {
  541. setName = DEFAULT_TPL_SET_NAME
  542. }
  543. opt := *r.Opt
  544. opt.Directory = dir
  545. r.TemplateSet.Set(setName, &opt)
  546. }
  547. func (r *TplRender) HasTemplateSet(name string) bool {
  548. return r.TemplateSet.Get(name) != nil
  549. }
  550. // DummyRender is used when user does not choose any real render to use.
  551. // This way, we can print out friendly message which asks them to register one,
  552. // instead of ugly and confusing 'nil pointer' panic.
  553. type DummyRender struct {
  554. http.ResponseWriter
  555. }
  556. func renderNotRegistered() {
  557. panic("middleware render hasn't been registered")
  558. }
  559. func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {
  560. renderNotRegistered()
  561. }
  562. func (r *DummyRender) JSON(int, interface{}) {
  563. renderNotRegistered()
  564. }
  565. func (r *DummyRender) JSONString(interface{}) (string, error) {
  566. renderNotRegistered()
  567. return "", nil
  568. }
  569. func (r *DummyRender) RawData(int, []byte) {
  570. renderNotRegistered()
  571. }
  572. func (r *DummyRender) PlainText(int, []byte) {
  573. renderNotRegistered()
  574. }
  575. func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {
  576. renderNotRegistered()
  577. }
  578. func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {
  579. renderNotRegistered()
  580. }
  581. func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {
  582. renderNotRegistered()
  583. return "", nil
  584. }
  585. func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {
  586. renderNotRegistered()
  587. return "", nil
  588. }
  589. func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {
  590. renderNotRegistered()
  591. return nil, nil
  592. }
  593. func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {
  594. renderNotRegistered()
  595. return nil, nil
  596. }
  597. func (r *DummyRender) XML(int, interface{}) {
  598. renderNotRegistered()
  599. }
  600. func (r *DummyRender) Error(int, ...string) {
  601. renderNotRegistered()
  602. }
  603. func (r *DummyRender) Status(int) {
  604. renderNotRegistered()
  605. }
  606. func (r *DummyRender) SetTemplatePath(string, string) {
  607. renderNotRegistered()
  608. }
  609. func (r *DummyRender) HasTemplateSet(string) bool {
  610. renderNotRegistered()
  611. return false
  612. }