forked from Shiloh/githaven
715 lines
18 KiB
Go
715 lines
18 KiB
Go
|
// Copyright 2013 Martini Authors
|
||
|
// Copyright 2014 The Macaron Authors
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||
|
// not use this file except in compliance with the License. You may obtain
|
||
|
// a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
|
// License for the specific language governing permissions and limitations
|
||
|
// under the License.
|
||
|
|
||
|
package macaron
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"encoding/xml"
|
||
|
"fmt"
|
||
|
"html/template"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/Unknwon/com"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
_CONTENT_TYPE = "Content-Type"
|
||
|
_CONTENT_LENGTH = "Content-Length"
|
||
|
_CONTENT_BINARY = "application/octet-stream"
|
||
|
_CONTENT_JSON = "application/json"
|
||
|
_CONTENT_HTML = "text/html"
|
||
|
_CONTENT_PLAIN = "text/plain"
|
||
|
_CONTENT_XHTML = "application/xhtml+xml"
|
||
|
_CONTENT_XML = "text/xml"
|
||
|
_DEFAULT_CHARSET = "UTF-8"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// Provides a temporary buffer to execute templates into and catch errors.
|
||
|
bufpool = sync.Pool{
|
||
|
New: func() interface{} { return new(bytes.Buffer) },
|
||
|
}
|
||
|
|
||
|
// Included helper functions for use when rendering html
|
||
|
helperFuncs = template.FuncMap{
|
||
|
"yield": func() (string, error) {
|
||
|
return "", fmt.Errorf("yield called with no layout defined")
|
||
|
},
|
||
|
"current": func() (string, error) {
|
||
|
return "", nil
|
||
|
},
|
||
|
}
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
// TemplateFile represents a interface of template file that has name and can be read.
|
||
|
TemplateFile interface {
|
||
|
Name() string
|
||
|
Data() []byte
|
||
|
Ext() string
|
||
|
}
|
||
|
// TemplateFileSystem represents a interface of template file system that able to list all files.
|
||
|
TemplateFileSystem interface {
|
||
|
ListFiles() []TemplateFile
|
||
|
}
|
||
|
|
||
|
// Delims represents a set of Left and Right delimiters for HTML template rendering
|
||
|
Delims struct {
|
||
|
// Left delimiter, defaults to {{
|
||
|
Left string
|
||
|
// Right delimiter, defaults to }}
|
||
|
Right string
|
||
|
}
|
||
|
|
||
|
// RenderOptions represents a struct for specifying configuration options for the Render middleware.
|
||
|
RenderOptions struct {
|
||
|
// Directory to load templates. Default is "templates".
|
||
|
Directory string
|
||
|
// Addtional directories to overwite templates.
|
||
|
AppendDirectories []string
|
||
|
// Layout template name. Will not render a layout if "". Default is to "".
|
||
|
Layout string
|
||
|
// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
|
||
|
Extensions []string
|
||
|
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
|
||
|
Funcs []template.FuncMap
|
||
|
// Delims sets the action delimiters to the specified strings in the Delims struct.
|
||
|
Delims Delims
|
||
|
// Appends the given charset to the Content-Type header. Default is "UTF-8".
|
||
|
Charset string
|
||
|
// Outputs human readable JSON.
|
||
|
IndentJSON bool
|
||
|
// Outputs human readable XML.
|
||
|
IndentXML bool
|
||
|
// Prefixes the JSON output with the given bytes.
|
||
|
PrefixJSON []byte
|
||
|
// Prefixes the XML output with the given bytes.
|
||
|
PrefixXML []byte
|
||
|
// Allows changing of output to XHTML instead of HTML. Default is "text/html"
|
||
|
HTMLContentType string
|
||
|
// TemplateFileSystem is the interface for supporting any implmentation of template file system.
|
||
|
TemplateFileSystem
|
||
|
}
|
||
|
|
||
|
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
|
||
|
HTMLOptions struct {
|
||
|
// Layout template name. Overrides Options.Layout.
|
||
|
Layout string
|
||
|
}
|
||
|
|
||
|
Render interface {
|
||
|
http.ResponseWriter
|
||
|
SetResponseWriter(http.ResponseWriter)
|
||
|
|
||
|
JSON(int, interface{})
|
||
|
JSONString(interface{}) (string, error)
|
||
|
RawData(int, []byte) // Serve content as binary
|
||
|
PlainText(int, []byte) // Serve content as plain text
|
||
|
HTML(int, string, interface{}, ...HTMLOptions)
|
||
|
HTMLSet(int, string, string, interface{}, ...HTMLOptions)
|
||
|
HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
|
||
|
HTMLString(string, interface{}, ...HTMLOptions) (string, error)
|
||
|
HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
|
||
|
HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
|
||
|
XML(int, interface{})
|
||
|
Error(int, ...string)
|
||
|
Status(int)
|
||
|
SetTemplatePath(string, string)
|
||
|
HasTemplateSet(string) bool
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// TplFile implements TemplateFile interface.
|
||
|
type TplFile struct {
|
||
|
name string
|
||
|
data []byte
|
||
|
ext string
|
||
|
}
|
||
|
|
||
|
// NewTplFile cerates new template file with given name and data.
|
||
|
func NewTplFile(name string, data []byte, ext string) *TplFile {
|
||
|
return &TplFile{name, data, ext}
|
||
|
}
|
||
|
|
||
|
func (f *TplFile) Name() string {
|
||
|
return f.name
|
||
|
}
|
||
|
|
||
|
func (f *TplFile) Data() []byte {
|
||
|
return f.data
|
||
|
}
|
||
|
|
||
|
func (f *TplFile) Ext() string {
|
||
|
return f.ext
|
||
|
}
|
||
|
|
||
|
// TplFileSystem implements TemplateFileSystem interface.
|
||
|
type TplFileSystem struct {
|
||
|
files []TemplateFile
|
||
|
}
|
||
|
|
||
|
// NewTemplateFileSystem creates new template file system with given options.
|
||
|
func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
|
||
|
fs := TplFileSystem{}
|
||
|
fs.files = make([]TemplateFile, 0, 10)
|
||
|
|
||
|
// Directories are composed in reverse order because later one overwrites previous ones,
|
||
|
// so once found, we can directly jump out of the loop.
|
||
|
dirs := make([]string, 0, len(opt.AppendDirectories)+1)
|
||
|
for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {
|
||
|
dirs = append(dirs, opt.AppendDirectories[i])
|
||
|
}
|
||
|
dirs = append(dirs, opt.Directory)
|
||
|
|
||
|
var err error
|
||
|
for i := range dirs {
|
||
|
// Skip ones that does not exists for symlink test,
|
||
|
// but allow non-symlink ones added after start.
|
||
|
if !com.IsExist(dirs[i]) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
dirs[i], err = filepath.EvalSymlinks(dirs[i])
|
||
|
if err != nil {
|
||
|
panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())
|
||
|
}
|
||
|
}
|
||
|
lastDir := dirs[len(dirs)-1]
|
||
|
|
||
|
// We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
|
||
|
if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, err error) error {
|
||
|
r, err := filepath.Rel(lastDir, path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
ext := GetExt(r)
|
||
|
|
||
|
for _, extension := range opt.Extensions {
|
||
|
if ext != extension {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
var data []byte
|
||
|
if !omitData {
|
||
|
// Loop over candidates of directory, break out once found.
|
||
|
// The file always exists because it's inside the walk function,
|
||
|
// and read original file is the worst case.
|
||
|
for i := range dirs {
|
||
|
path = filepath.Join(dirs[i], r)
|
||
|
if !com.IsFile(path) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
data, err = ioutil.ReadFile(path)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
|
||
|
fs.files = append(fs.files, NewTplFile(name, data, ext))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}); err != nil {
|
||
|
panic("NewTemplateFileSystem: " + err.Error())
|
||
|
}
|
||
|
|
||
|
return fs
|
||
|
}
|
||
|
|
||
|
func (fs TplFileSystem) ListFiles() []TemplateFile {
|
||
|
return fs.files
|
||
|
}
|
||
|
|
||
|
func PrepareCharset(charset string) string {
|
||
|
if len(charset) != 0 {
|
||
|
return "; charset=" + charset
|
||
|
}
|
||
|
|
||
|
return "; charset=" + _DEFAULT_CHARSET
|
||
|
}
|
||
|
|
||
|
func GetExt(s string) string {
|
||
|
index := strings.Index(s, ".")
|
||
|
if index == -1 {
|
||
|
return ""
|
||
|
}
|
||
|
return s[index:]
|
||
|
}
|
||
|
|
||
|
func compile(opt RenderOptions) *template.Template {
|
||
|
t := template.New(opt.Directory)
|
||
|
t.Delims(opt.Delims.Left, opt.Delims.Right)
|
||
|
// Parse an initial template in case we don't have any.
|
||
|
template.Must(t.Parse("Macaron"))
|
||
|
|
||
|
if opt.TemplateFileSystem == nil {
|
||
|
opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
|
||
|
}
|
||
|
|
||
|
for _, f := range opt.TemplateFileSystem.ListFiles() {
|
||
|
tmpl := t.New(f.Name())
|
||
|
for _, funcs := range opt.Funcs {
|
||
|
tmpl.Funcs(funcs)
|
||
|
}
|
||
|
// Bomb out if parse fails. We don't want any silent server starts.
|
||
|
template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
|
||
|
}
|
||
|
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
DEFAULT_TPL_SET_NAME = "DEFAULT"
|
||
|
)
|
||
|
|
||
|
// TemplateSet represents a template set of type *template.Template.
|
||
|
type TemplateSet struct {
|
||
|
lock sync.RWMutex
|
||
|
sets map[string]*template.Template
|
||
|
dirs map[string]string
|
||
|
}
|
||
|
|
||
|
// NewTemplateSet initializes a new empty template set.
|
||
|
func NewTemplateSet() *TemplateSet {
|
||
|
return &TemplateSet{
|
||
|
sets: make(map[string]*template.Template),
|
||
|
dirs: make(map[string]string),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {
|
||
|
t := compile(*opt)
|
||
|
|
||
|
ts.lock.Lock()
|
||
|
defer ts.lock.Unlock()
|
||
|
|
||
|
ts.sets[name] = t
|
||
|
ts.dirs[name] = opt.Directory
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
func (ts *TemplateSet) Get(name string) *template.Template {
|
||
|
ts.lock.RLock()
|
||
|
defer ts.lock.RUnlock()
|
||
|
|
||
|
return ts.sets[name]
|
||
|
}
|
||
|
|
||
|
func (ts *TemplateSet) GetDir(name string) string {
|
||
|
ts.lock.RLock()
|
||
|
defer ts.lock.RUnlock()
|
||
|
|
||
|
return ts.dirs[name]
|
||
|
}
|
||
|
|
||
|
func prepareRenderOptions(options []RenderOptions) RenderOptions {
|
||
|
var opt RenderOptions
|
||
|
if len(options) > 0 {
|
||
|
opt = options[0]
|
||
|
}
|
||
|
|
||
|
// Defaults.
|
||
|
if len(opt.Directory) == 0 {
|
||
|
opt.Directory = "templates"
|
||
|
}
|
||
|
if len(opt.Extensions) == 0 {
|
||
|
opt.Extensions = []string{".tmpl", ".html"}
|
||
|
}
|
||
|
if len(opt.HTMLContentType) == 0 {
|
||
|
opt.HTMLContentType = _CONTENT_HTML
|
||
|
}
|
||
|
|
||
|
return opt
|
||
|
}
|
||
|
|
||
|
func ParseTplSet(tplSet string) (tplName string, tplDir string) {
|
||
|
tplSet = strings.TrimSpace(tplSet)
|
||
|
if len(tplSet) == 0 {
|
||
|
panic("empty template set argument")
|
||
|
}
|
||
|
infos := strings.Split(tplSet, ":")
|
||
|
if len(infos) == 1 {
|
||
|
tplDir = infos[0]
|
||
|
tplName = path.Base(tplDir)
|
||
|
} else {
|
||
|
tplName = infos[0]
|
||
|
tplDir = infos[1]
|
||
|
}
|
||
|
|
||
|
if !com.IsDir(tplDir) {
|
||
|
panic("template set path does not exist or is not a directory")
|
||
|
}
|
||
|
return tplName, tplDir
|
||
|
}
|
||
|
|
||
|
func renderHandler(opt RenderOptions, tplSets []string) Handler {
|
||
|
cs := PrepareCharset(opt.Charset)
|
||
|
ts := NewTemplateSet()
|
||
|
ts.Set(DEFAULT_TPL_SET_NAME, &opt)
|
||
|
|
||
|
var tmpOpt RenderOptions
|
||
|
for _, tplSet := range tplSets {
|
||
|
tplName, tplDir := ParseTplSet(tplSet)
|
||
|
tmpOpt = opt
|
||
|
tmpOpt.Directory = tplDir
|
||
|
ts.Set(tplName, &tmpOpt)
|
||
|
}
|
||
|
|
||
|
return func(ctx *Context) {
|
||
|
r := &TplRender{
|
||
|
ResponseWriter: ctx.Resp,
|
||
|
TemplateSet: ts,
|
||
|
Opt: &opt,
|
||
|
CompiledCharset: cs,
|
||
|
}
|
||
|
ctx.Data["TmplLoadTimes"] = func() string {
|
||
|
if r.startTime.IsZero() {
|
||
|
return ""
|
||
|
}
|
||
|
return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
|
||
|
}
|
||
|
|
||
|
ctx.Render = r
|
||
|
ctx.MapTo(r, (*Render)(nil))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
|
||
|
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
|
||
|
// HTML rendering. The default directory for templates is "templates" and the default
|
||
|
// file extension is ".tmpl" and ".html".
|
||
|
//
|
||
|
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
|
||
|
// MACARON_ENV environment variable to "production".
|
||
|
func Renderer(options ...RenderOptions) Handler {
|
||
|
return renderHandler(prepareRenderOptions(options), []string{})
|
||
|
}
|
||
|
|
||
|
func Renderers(options RenderOptions, tplSets ...string) Handler {
|
||
|
return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
|
||
|
}
|
||
|
|
||
|
type TplRender struct {
|
||
|
http.ResponseWriter
|
||
|
*TemplateSet
|
||
|
Opt *RenderOptions
|
||
|
CompiledCharset string
|
||
|
|
||
|
startTime time.Time
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
|
||
|
r.ResponseWriter = rw
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) JSON(status int, v interface{}) {
|
||
|
var (
|
||
|
result []byte
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// json rendered fine, write out the result
|
||
|
r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)
|
||
|
r.WriteHeader(status)
|
||
|
if len(r.Opt.PrefixJSON) > 0 {
|
||
|
r.Write(r.Opt.PrefixJSON)
|
||
|
}
|
||
|
r.Write(result)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) 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 *TplRender) XML(status int, v interface{}) {
|
||
|
var result []byte
|
||
|
var err error
|
||
|
if r.Opt.IndentXML {
|
||
|
result, err = xml.MarshalIndent(v, "", " ")
|
||
|
} else {
|
||
|
result, err = xml.Marshal(v)
|
||
|
}
|
||
|
if err != nil {
|
||
|
http.Error(r, err.Error(), 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// XML rendered fine, write out the result
|
||
|
r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)
|
||
|
r.WriteHeader(status)
|
||
|
if len(r.Opt.PrefixXML) > 0 {
|
||
|
r.Write(r.Opt.PrefixXML)
|
||
|
}
|
||
|
r.Write(result)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) data(status int, contentType string, v []byte) {
|
||
|
if r.Header().Get(_CONTENT_TYPE) == "" {
|
||
|
r.Header().Set(_CONTENT_TYPE, contentType)
|
||
|
}
|
||
|
r.WriteHeader(status)
|
||
|
r.Write(v)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) RawData(status int, v []byte) {
|
||
|
r.data(status, _CONTENT_BINARY, v)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) PlainText(status int, v []byte) {
|
||
|
r.data(status, _CONTENT_PLAIN, v)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
|
||
|
buf := bufpool.Get().(*bytes.Buffer)
|
||
|
return buf, t.ExecuteTemplate(buf, name, data)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
|
||
|
funcs := template.FuncMap{
|
||
|
"yield": func() (template.HTML, error) {
|
||
|
buf, err := r.execute(t, tplName, data)
|
||
|
// return safe html here since we are rendering our own template
|
||
|
return template.HTML(buf.String()), err
|
||
|
},
|
||
|
"current": func() (string, error) {
|
||
|
return tplName, nil
|
||
|
},
|
||
|
}
|
||
|
t.Funcs(funcs)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
|
||
|
t := r.TemplateSet.Get(setName)
|
||
|
if Env == DEV {
|
||
|
opt := *r.Opt
|
||
|
opt.Directory = r.TemplateSet.GetDir(setName)
|
||
|
t = r.TemplateSet.Set(setName, &opt)
|
||
|
}
|
||
|
if t == nil {
|
||
|
return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
|
||
|
}
|
||
|
|
||
|
opt := r.prepareHTMLOptions(htmlOpt)
|
||
|
|
||
|
if len(opt.Layout) > 0 {
|
||
|
r.addYield(t, tplName, data)
|
||
|
tplName = opt.Layout
|
||
|
}
|
||
|
|
||
|
out, err := r.execute(t, tplName, data)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return out, nil
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
|
||
|
r.startTime = time.Now()
|
||
|
|
||
|
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
|
||
|
if err != nil {
|
||
|
http.Error(r, err.Error(), http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)
|
||
|
r.WriteHeader(status)
|
||
|
|
||
|
if _, err := out.WriteTo(r); err != nil {
|
||
|
out.Reset()
|
||
|
}
|
||
|
bufpool.Put(out)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
|
||
|
r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
|
||
|
r.renderHTML(status, setName, tplName, data, htmlOpt...)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
|
||
|
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
|
||
|
if err != nil {
|
||
|
return []byte(""), err
|
||
|
}
|
||
|
return out.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
|
||
|
return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
||
|
p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
|
||
|
return string(p), err
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
||
|
p, err := r.HTMLBytes(name, data, htmlOpt...)
|
||
|
return string(p), err
|
||
|
}
|
||
|
|
||
|
// Error writes the given HTTP status to the current ResponseWriter
|
||
|
func (r *TplRender) Error(status int, message ...string) {
|
||
|
r.WriteHeader(status)
|
||
|
if len(message) > 0 {
|
||
|
r.Write([]byte(message[0]))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) Status(status int) {
|
||
|
r.WriteHeader(status)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
|
||
|
if len(htmlOpt) > 0 {
|
||
|
return htmlOpt[0]
|
||
|
}
|
||
|
|
||
|
return HTMLOptions{
|
||
|
Layout: r.Opt.Layout,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) SetTemplatePath(setName, dir string) {
|
||
|
if len(setName) == 0 {
|
||
|
setName = DEFAULT_TPL_SET_NAME
|
||
|
}
|
||
|
opt := *r.Opt
|
||
|
opt.Directory = dir
|
||
|
r.TemplateSet.Set(setName, &opt)
|
||
|
}
|
||
|
|
||
|
func (r *TplRender) HasTemplateSet(name string) bool {
|
||
|
return r.TemplateSet.Get(name) != nil
|
||
|
}
|
||
|
|
||
|
// DummyRender is used when user does not choose any real render to use.
|
||
|
// This way, we can print out friendly message which asks them to register one,
|
||
|
// instead of ugly and confusing 'nil pointer' panic.
|
||
|
type DummyRender struct {
|
||
|
http.ResponseWriter
|
||
|
}
|
||
|
|
||
|
func renderNotRegistered() {
|
||
|
panic("middleware render hasn't been registered")
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) JSON(int, interface{}) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) JSONString(interface{}) (string, error) {
|
||
|
renderNotRegistered()
|
||
|
return "", nil
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) RawData(int, []byte) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) PlainText(int, []byte) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {
|
||
|
renderNotRegistered()
|
||
|
return "", nil
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {
|
||
|
renderNotRegistered()
|
||
|
return "", nil
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {
|
||
|
renderNotRegistered()
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {
|
||
|
renderNotRegistered()
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) XML(int, interface{}) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) Error(int, ...string) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) Status(int) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) SetTemplatePath(string, string) {
|
||
|
renderNotRegistered()
|
||
|
}
|
||
|
|
||
|
func (r *DummyRender) HasTemplateSet(string) bool {
|
||
|
renderNotRegistered()
|
||
|
return false
|
||
|
}
|