273 lines
5.9 KiB
Go
273 lines
5.9 KiB
Go
// Copyright 2015 The Tango Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package renders
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"net/http"
|
|
|
|
"gitea.com/lunny/tango"
|
|
"github.com/oxtoacart/bpool"
|
|
)
|
|
|
|
// Version return the middleware's version
|
|
func Version() string {
|
|
return "0.4.0329"
|
|
}
|
|
|
|
const (
|
|
ContentType = "Content-Type"
|
|
ContentLength = "Content-Length"
|
|
ContentHTML = "text/html"
|
|
ContentXHTML = "application/xhtml+xml"
|
|
defaultCharset = "UTF-8"
|
|
)
|
|
|
|
// Provides a common buffer to execute templates.
|
|
type T map[string]interface{}
|
|
|
|
func (t T) Merge(at T) T {
|
|
if len(at) <= 0 {
|
|
return t
|
|
}
|
|
|
|
for k, v := range at {
|
|
t[k] = v
|
|
}
|
|
return t
|
|
}
|
|
|
|
type Renders struct {
|
|
Options
|
|
cs string
|
|
pool *bpool.BufferPool
|
|
templates map[string]*template.Template
|
|
}
|
|
|
|
func New(options ...Options) *Renders {
|
|
opt := prepareOptions(options)
|
|
t, err := compile(opt)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &Renders{
|
|
Options: opt,
|
|
cs: prepareCharset(opt.Charset),
|
|
pool: bpool.NewBufferPool(64),
|
|
templates: t,
|
|
}
|
|
}
|
|
|
|
type IRenderer interface {
|
|
SetRenderer(*Renders, *tango.Context, func(string), func(string), func(string, io.Reader))
|
|
}
|
|
|
|
// confirm Renderer implements IRenderer
|
|
var _ IRenderer = &Renderer{}
|
|
|
|
type Renderer struct {
|
|
ctx *tango.Context
|
|
renders *Renders
|
|
before, after func(string)
|
|
afterBuf func(string, io.Reader)
|
|
compiledCharset string
|
|
Charset string
|
|
HTMLContentType string
|
|
delimsLeft, delimsRight string
|
|
}
|
|
|
|
func (r *Renderer) SetRenderer(renders *Renders, ctx *tango.Context,
|
|
before, after func(string), afterBuf func(string, io.Reader)) {
|
|
r.renders = renders
|
|
r.ctx = ctx
|
|
r.before = before
|
|
r.after = after
|
|
r.afterBuf = afterBuf
|
|
r.HTMLContentType = renders.Options.HTMLContentType
|
|
r.compiledCharset = renders.cs
|
|
r.delimsLeft = renders.Options.DelimsLeft
|
|
r.delimsRight = renders.Options.DelimsRight
|
|
}
|
|
|
|
type Before interface {
|
|
BeforeRender(string)
|
|
}
|
|
|
|
type After interface {
|
|
AfterRender(string)
|
|
}
|
|
|
|
type AfterBuf interface {
|
|
AfterRender(string, io.Reader)
|
|
}
|
|
|
|
func (r *Renders) RenderBytes(name string, bindings ...interface{}) ([]byte, error) {
|
|
buf := new(bytes.Buffer)
|
|
err := r.Render(buf, name, bindings...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (r *Renders) Render(w io.Writer, name string, bindings ...interface{}) error {
|
|
var binding interface{}
|
|
if len(bindings) > 0 {
|
|
binding = bindings[0]
|
|
}
|
|
if t, ok := binding.(T); ok {
|
|
binding = t.Merge(r.Options.Vars)
|
|
}
|
|
|
|
if r.Reload {
|
|
var err error
|
|
// recompile for easy development
|
|
r.templates, err = compile(r.Options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
buf, err := r.execute(name, binding)
|
|
if err != nil {
|
|
r.pool.Put(buf)
|
|
return err
|
|
}
|
|
|
|
// template rendered fine, write out the result
|
|
_, err = io.Copy(w, buf)
|
|
r.pool.Put(buf)
|
|
return err
|
|
}
|
|
|
|
func (r *Renders) execute(name string, binding interface{}) (*bytes.Buffer, error) {
|
|
buf := r.pool.Get()
|
|
name = alignTmplName(name)
|
|
|
|
if rt, ok := r.templates[name]; ok {
|
|
return buf, rt.ExecuteTemplate(buf, name, binding)
|
|
}
|
|
return buf, errors.New("template is not exist")
|
|
}
|
|
|
|
func (r *Renders) Handle(ctx *tango.Context) {
|
|
if action := ctx.Action(); action != nil {
|
|
if rd, ok := action.(IRenderer); ok {
|
|
var before, after func(string)
|
|
var afterBuf func(string, io.Reader)
|
|
if b, ok := action.(Before); ok {
|
|
before = b.BeforeRender
|
|
}
|
|
if a, ok := action.(After); ok {
|
|
after = a.AfterRender
|
|
}
|
|
if a2, ok := action.(AfterBuf); ok {
|
|
afterBuf = a2.AfterRender
|
|
}
|
|
|
|
rd.SetRenderer(r, ctx, before, after, afterBuf)
|
|
}
|
|
}
|
|
|
|
ctx.Next()
|
|
}
|
|
|
|
func compile(options Options) (map[string]*template.Template, error) {
|
|
return Load(options)
|
|
}
|
|
|
|
func prepareCharset(charset string) string {
|
|
if len(charset) != 0 {
|
|
return "; charset=" + charset
|
|
}
|
|
|
|
return "; charset=" + defaultCharset
|
|
}
|
|
|
|
// Render a template
|
|
// r.Render("index.html")
|
|
// r.Render("index.html", renders.T{
|
|
// "name": value,
|
|
// })
|
|
func (r *Renderer) Render(name string, bindings ...interface{}) error {
|
|
return r.StatusRender(http.StatusOK, name, bindings...)
|
|
}
|
|
|
|
// RenderBytes Will not called before & after method.
|
|
func (r *Renderer) RenderBytes(name string, binding ...interface{}) ([]byte, error) {
|
|
return r.renders.RenderBytes(name, binding...)
|
|
}
|
|
|
|
func (r *Renderer) StatusRender(status int, name string, bindings ...interface{}) error {
|
|
var binding interface{}
|
|
if len(bindings) > 0 {
|
|
binding = bindings[0]
|
|
}
|
|
if t, ok := binding.(T); ok {
|
|
binding = t.Merge(r.renders.Options.Vars)
|
|
}
|
|
|
|
if r.renders.Reload {
|
|
var err error
|
|
// recompile for easy development
|
|
r.renders.templates, err = compile(r.renders.Options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
buf, err := r.execute(name, binding)
|
|
if err != nil {
|
|
r.renders.pool.Put(buf)
|
|
return err
|
|
}
|
|
|
|
var cs string
|
|
if len(r.Charset) > 0 {
|
|
cs = prepareCharset(r.Charset)
|
|
} else {
|
|
cs = r.compiledCharset
|
|
}
|
|
// template rendered fine, write out the result
|
|
r.ctx.Header().Set(ContentType, r.HTMLContentType+cs)
|
|
r.ctx.WriteHeader(status)
|
|
_, err = io.Copy(r.ctx.ResponseWriter, buf)
|
|
r.renders.pool.Put(buf)
|
|
return err
|
|
}
|
|
|
|
func (r *Renderer) Template(name string) *template.Template {
|
|
return r.renders.templates[alignTmplName(name)]
|
|
}
|
|
|
|
func (r *Renderer) execute(name string, binding interface{}) (*bytes.Buffer, error) {
|
|
buf := r.renders.pool.Get()
|
|
if r.before != nil {
|
|
r.before(name)
|
|
}
|
|
if r.after != nil {
|
|
defer r.after(name)
|
|
}
|
|
|
|
name = alignTmplName(name)
|
|
|
|
if rt, ok := r.renders.templates[name]; ok {
|
|
err := rt.Delims(r.delimsLeft, r.delimsRight).ExecuteTemplate(buf, name, binding)
|
|
if err == nil && r.afterBuf != nil {
|
|
var tmpBuf = bytes.NewBuffer(buf.Bytes())
|
|
r.afterBuf(name, tmpBuf)
|
|
}
|
|
return buf, err
|
|
}
|
|
if r.afterBuf != nil {
|
|
r.afterBuf(name, nil)
|
|
}
|
|
return buf, fmt.Errorf("template %s is not exist", name)
|
|
}
|