2016-05-30 16:08:09 +02:00
package template
import (
"fmt"
"io"
"github.com/klauspost/compress/gzip"
"sync"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
"github.com/kataras/iris/render/template/engine/amber"
2016-06-28 11:50:26 +02:00
"github.com/kataras/iris/render/template/engine/handlebars"
2016-05-30 16:08:09 +02:00
"github.com/kataras/iris/render/template/engine/html"
"github.com/kataras/iris/render/template/engine/jade"
"github.com/kataras/iris/render/template/engine/markdown"
"github.com/kataras/iris/render/template/engine/pongo"
"github.com/kataras/iris/utils"
)
type (
2016-05-31 10:05:42 +02:00
// Engine the interface that all template engines must inheritance
2016-05-30 16:08:09 +02:00
Engine interface {
2016-05-31 10:05:42 +02:00
// BuildTemplates builds the templates for a directory
2016-05-30 16:08:09 +02:00
BuildTemplates ( ) error
2016-05-31 10:05:42 +02:00
// ExecuteWriter finds and execute a template and write its result to the out writer
2016-05-30 16:08:09 +02:00
ExecuteWriter ( out io . Writer , name string , binding interface { } , layout string ) error
}
2016-05-31 10:05:42 +02:00
// Template the internal configs for the common configs for the template engines
2016-05-30 16:08:09 +02:00
Template struct {
2016-05-31 10:05:42 +02:00
// Engine the type of the Engine
Engine Engine
// Gzip enable gzip compression
// default is false
Gzip bool
// IsDevelopment re-builds the templates on each request
// default is false
IsDevelopment bool
// Directory the system path which the templates live
// default is ./templates
Directory string
// Extensions the allowed file extension
// default is []string{".html"}
Extensions [ ] string
// ContentType is the Content-Type response header
// default is text/html but you can change if if needed
ContentType string
// Layout the template file ( with its extension) which is the mother of all
// use it to have it as a root file, and include others with {{ yield }}, refer the docs
Layout string
2016-05-30 16:08:09 +02:00
buffer * utils . BufferPool // this is used only for RenderString
gzipWriterPool sync . Pool
}
)
2016-06-06 00:37:32 +02:00
// sharedFuncs the funcs should be exists in all supported view template engines
var sharedFuncs map [ string ] interface { }
// we do this because we don't want to override the user's funcs
func setSharedFuncs ( source map [ string ] interface { } , target map [ string ] interface { } ) {
if source == nil {
return
}
if target == nil {
target = make ( map [ string ] interface { } , len ( source ) )
}
for k , v := range source {
if target [ k ] == nil {
target [ k ] = v
}
}
}
2016-05-30 16:08:09 +02:00
// New creates and returns a Template instance which keeps the Template Engine and helps with render
func New ( c config . Template ) * Template {
2016-06-06 00:37:32 +02:00
defer func ( ) {
sharedFuncs = nil
} ( )
2016-05-30 16:08:09 +02:00
var e Engine
// [ENGINE-2]
switch c . Engine {
case config . HTMLEngine :
2016-06-06 00:37:32 +02:00
setSharedFuncs ( sharedFuncs , c . HTMLTemplate . Funcs )
2016-05-30 16:08:09 +02:00
e = html . New ( c ) // HTMLTemplate
2016-06-06 00:37:32 +02:00
case config . JadeEngine :
setSharedFuncs ( sharedFuncs , c . Jade . Funcs )
e = jade . New ( c ) // Jade
2016-05-30 16:08:09 +02:00
case config . PongoEngine :
2016-06-06 00:37:32 +02:00
setSharedFuncs ( sharedFuncs , c . Pongo . Globals )
2016-05-30 16:08:09 +02:00
e = pongo . New ( c ) // Pongo2
case config . MarkdownEngine :
e = markdown . New ( c ) // Markdown
case config . AmberEngine :
2016-06-06 00:37:32 +02:00
setSharedFuncs ( sharedFuncs , c . Amber . Funcs )
2016-05-30 16:08:09 +02:00
e = amber . New ( c ) // Amber
2016-06-28 11:50:26 +02:00
case config . HandlebarsEngine :
setSharedFuncs ( sharedFuncs , c . Handlebars . Helpers )
e = handlebars . New ( c )
2016-05-30 16:08:09 +02:00
default : // config.NoEngine
return nil
}
if err := e . BuildTemplates ( ) ; err != nil { // first build the templates, if error then panic because this is called before server's run
panic ( err )
}
compiledContentType := c . ContentType + "; charset=" + c . Charset
t := & Template {
Engine : e ,
IsDevelopment : c . IsDevelopment ,
Gzip : c . Gzip ,
ContentType : compiledContentType ,
Layout : c . Layout ,
buffer : utils . NewBufferPool ( 64 ) ,
gzipWriterPool : sync . Pool { New : func ( ) interface { } {
return & gzip . Writer { }
} } ,
}
return t
}
2016-06-06 00:37:32 +02:00
// RegisterSharedFunc registers a functionality that should be inherited from all supported template engines
func RegisterSharedFunc ( name string , fn interface { } ) {
if sharedFuncs == nil {
sharedFuncs = make ( map [ string ] interface { } )
}
sharedFuncs [ name ] = fn
}
2016-06-14 07:45:40 +02:00
// RegisterSharedFuncs registers functionalities that should be inherited from all supported template engines
func RegisterSharedFuncs ( theFuncs map [ string ] interface { } ) {
if sharedFuncs == nil || len ( sharedFuncs ) == 0 {
sharedFuncs = theFuncs
return
}
for k , v := range theFuncs {
sharedFuncs [ k ] = v
}
}
2016-07-03 02:01:48 +02:00
func ( t * Template ) getLayout ( ctx context . IContext , passedLayouts [ ] string ) string {
_layout := ""
if len ( passedLayouts ) > 0 {
_layout = passedLayouts [ 0 ]
} else if ctx != nil {
if layoutFromCtx := ctx . GetString ( config . TemplateLayoutContextKey ) ; layoutFromCtx != "" {
_layout = layoutFromCtx
}
}
if _layout == "" && _layout != config . NoLayout {
if t . Layout != config . NoLayout { // the config's Layout is disabled if "" , config.NoLayout should be passed only on ctx.Render but for any case check if user set it as config.NoLayout also
_layout = t . Layout
}
}
return _layout
}
2016-05-31 10:05:42 +02:00
// Render renders a template using the context's writer
2016-05-30 16:08:09 +02:00
func ( t * Template ) Render ( ctx context . IContext , name string , binding interface { } , layout ... string ) ( err error ) {
if t == nil { // No engine was given but .Render was called
2016-06-14 07:45:40 +02:00
ctx . HTML ( 403 , "<b> Iris </b> <br/> Templates are disabled via config.NoEngine, check your iris' configuration please." )
2016-05-30 16:08:09 +02:00
return fmt . Errorf ( "[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n" )
}
// build templates again on each render if IsDevelopment.
if t . IsDevelopment {
if err = t . Engine . BuildTemplates ( ) ; err != nil {
return
}
}
2016-07-03 02:01:48 +02:00
_layout := t . getLayout ( ctx , layout )
2016-05-30 16:08:09 +02:00
ctx . GetRequestCtx ( ) . Response . Header . Set ( "Content-Type" , t . ContentType )
var out io . Writer
if t . Gzip {
ctx . GetRequestCtx ( ) . Response . Header . Add ( "Content-Encoding" , "gzip" )
gzipWriter := t . gzipWriterPool . Get ( ) . ( * gzip . Writer )
gzipWriter . Reset ( ctx . GetRequestCtx ( ) . Response . BodyWriter ( ) )
defer gzipWriter . Close ( )
defer t . gzipWriterPool . Put ( gzipWriter )
out = gzipWriter
} else {
out = ctx . GetRequestCtx ( ) . Response . BodyWriter ( )
}
err = t . Engine . ExecuteWriter ( out , name , binding , _layout )
return
}
2016-05-31 10:05:42 +02:00
// RenderString executes a template and returns its contents result (string)
2016-05-30 16:08:09 +02:00
func ( t * Template ) RenderString ( name string , binding interface { } , layout ... string ) ( result string , err error ) {
if t == nil { // No engine was given but .Render was called
err = fmt . Errorf ( "[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n" )
return
}
// build templates again on each render if IsDevelopment.
if t . IsDevelopment {
if err = t . Engine . BuildTemplates ( ) ; err != nil {
return
}
}
2016-07-03 02:01:48 +02:00
_layout := t . getLayout ( nil , layout )
2016-05-30 16:08:09 +02:00
out := t . buffer . Get ( )
// if we have problems later consider that -> out.Reset()
defer t . buffer . Put ( out )
err = t . Engine . ExecuteWriter ( out , name , binding , _layout )
if err == nil {
result = out . String ( )
}
return
}