move the template engines manager to the iris-contrib/template

This commit is contained in:
Makis Maropoulos 2016-07-13 14:54:56 +03:00
parent f7a782b692
commit 74aeb081ee
3 changed files with 16 additions and 299 deletions

View File

@ -6,7 +6,7 @@ import (
)
var (
// Charset character encoding for template rendering
// Charset character encoding for various rendering
Charset = "UTF-8"
)
@ -20,14 +20,4 @@ var (
//
// Defaults to iris-fasthttp.gz
CompressedFileSuffix = "iris-fasthttp.gz"
// ContentTypeHTML defaults to text/html but you can change it, changes the template's content type also
ContentTypeHTML = "text/html"
)
const (
// NoLayout to disable layout for a particular template file
NoLayout = "@.|.@iris_no_layout@.|.@"
// TemplateLayoutContextKey is the name of the user values which can be used to set a template layout from a middleware and override the parent's
TemplateLayoutContextKey = "templateLayout"
)

26
iris.go
View File

@ -67,6 +67,7 @@ import (
"github.com/iris-contrib/errors"
"github.com/iris-contrib/logger"
"github.com/iris-contrib/rest"
"github.com/iris-contrib/template"
"github.com/iris-contrib/template/html"
"github.com/kataras/iris/config"
"github.com/kataras/iris/context"
@ -89,6 +90,9 @@ const (
| | | __|| |/ __|
_| |_| | | |\__ \
|_____|_| |_||___/ ` + Version + ` `
// NoLayout pass it to the layout option on the context.Render to disable layout for this execution
NoLayout = template.NoLayout
)
// Default entry, use it with iris.$anyPublicFunc
@ -144,7 +148,7 @@ type (
ListenVirtual(...string) *Server
Go() error
Close() error
UseTemplate(TemplateEngine) *TemplateEngineLocation
UseTemplate(template.TemplateEngine) *template.TemplateEngineLocation
UseGlobal(...Handler)
UseGlobalFunc(...HandlerFunc)
OnError(int, HandlerFunc)
@ -164,7 +168,7 @@ type (
*muxAPI
rest *rest.Render
sessions *sessions.Manager
templates *TemplateEngines
templates *template.TemplateEngines
// fields which are useful to the user/dev
// the last added server is the main server
@ -199,12 +203,12 @@ func New(cfg ...config.Iris) *Framework {
// set the plugin container
s.Plugins = &pluginContainer{logger: s.Logger}
// set the templates
s.templates = &TemplateEngines{
helpers: map[string]interface{}{
s.templates = &template.TemplateEngines{
Helpers: map[string]interface{}{
"url": s.URL,
"urlpath": s.Path,
},
engines: make([]*TemplateEngineWrapper, 0),
Engines: make([]*template.TemplateEngineWrapper, 0),
}
// set the websocket server
s.Websocket = websocket.NewServer(s.Config.Websocket)
@ -230,14 +234,14 @@ func (s *Framework) initialize() {
s.rest = rest.New(s.Config.Rest)
// prepare the templates if enabled
if !s.Config.DisableTemplateEngines {
if err := s.templates.loadAll(); err != nil {
if err := s.templates.LoadAll(); err != nil {
s.Logger.Panic(err) // panic on templates loading before listening if we have an error.
}
// check and prepare the templates
if len(s.templates.engines) == 0 { // no template engine is registered, let's use the default
if len(s.templates.Engines) == 0 { // no template engine is registered, let's use the default
s.UseTemplate(html.New())
}
s.templates.setReload(s.Config.IsDevelopment)
s.templates.Reload = s.Config.IsDevelopment
}
// listen to websocket connections
@ -487,13 +491,13 @@ s.renderer = &renderer{
// UseTemplate adds a template engine to the iris view system
// it does not build/load them yet
func UseTemplate(e TemplateEngine) *TemplateEngineLocation {
func UseTemplate(e template.TemplateEngine) *template.TemplateEngineLocation {
return Default.UseTemplate(e)
}
// UseTemplate adds a template engine to the iris view system
// it does not build/load them yet
func (s *Framework) UseTemplate(e TemplateEngine) *TemplateEngineLocation {
func (s *Framework) UseTemplate(e template.TemplateEngine) *template.TemplateEngineLocation {
return s.templates.Add(e)
}
@ -1557,7 +1561,7 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
//
func (api *muxAPI) Layout(tmplLayoutFile string) MuxAPI {
api.UseFunc(func(ctx *Context) {
ctx.Set(config.TemplateLayoutContextKey, tmplLayoutFile)
ctx.Set(template.TemplateLayoutContextKey, tmplLayoutFile)
ctx.Next()
})
return api

View File

@ -1,277 +0,0 @@
package iris
import (
"compress/gzip"
"io"
"path/filepath"
"sync"
"github.com/iris-contrib/errors"
"github.com/kataras/iris/config"
"github.com/kataras/iris/utils"
)
var (
builtinFuncs = [...]string{"url", "urlpath"}
// these are used inside load all in order when no directory and no extension is provided by the dev
// DefaultTemplateDirectory the default directory if empty setted
DefaultTemplateDirectory = "." + utils.PathSeparator + "templates"
// DefaultTemplateExtension the default file extension if empty setted
DefaultTemplateExtension = ".html"
// for conversional, exists on config because are shared:
// NoLayout pass it to the layout option on the context.Render to disable layout for this execution
NoLayout = config.NoLayout
)
type (
// TemplateEngine the interface that all template engines must implement
TemplateEngine interface {
// LoadDirectory builds the templates, usually by directory and extension but these are engine's decisions
LoadDirectory(directory string, extension string) error
// LoadAssets loads the templates by binary
// assetFn is a func which returns bytes, use it to load the templates by binary
// namesFn returns the template filenames
LoadAssets(virtualDirectory string, virtualExtension string, assetFn func(name string) ([]byte, error), namesFn func() []string) error
// ExecuteWriter finds, execute a template and write its result to the out writer
// options are the optional runtime options can be passed by user and catched by the template engine when render
// an example of this is the "layout" or "gzip" option
ExecuteWriter(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) error
}
// TemplateEngineFuncs is optional interface for the TemplateEngine
// used to insert the Iris' standard funcs, see var 'usedFuncs'
TemplateEngineFuncs interface {
// Funcs should returns the context or the funcs,
// this property is used in order to register the iris' helper funcs
Funcs() map[string]interface{}
}
)
type (
// TemplateFuncs is is a helper type for map[string]interface{}
TemplateFuncs map[string]interface{}
// RenderOptions is a helper type for the optional runtime options can be passed by user and catched by the template engine when render
// an example of this is the "layout" option
RenderOptions map[string]interface{}
)
// IsFree returns true if a function can be inserted to this map
// return false if this key is already used by Iris
func (t TemplateFuncs) IsFree(key string) bool {
for i := range builtinFuncs {
if builtinFuncs[i] == key {
return false
}
}
return true
}
type (
// TemplateEngineLocation contains the funcs to set the location for the templates by directory or by binary
TemplateEngineLocation struct {
directory string
extension string
assetFn func(name string) ([]byte, error)
namesFn func() []string
}
// TemplateEngineBinaryLocation called after TemplateEngineLocation's Directory, used when files are distrubuted inside the app executable
TemplateEngineBinaryLocation struct {
location *TemplateEngineLocation
}
)
// Directory sets the directory to load from
// returns the Binary location which is optional
func (t *TemplateEngineLocation) Directory(dir string, fileExtension string) TemplateEngineBinaryLocation {
t.directory = dir
t.extension = fileExtension
return TemplateEngineBinaryLocation{location: t}
}
// Binary sets the asset(s) and asssets names to load from, works with Directory
func (t *TemplateEngineBinaryLocation) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) {
t.location.assetFn = assetFn
t.location.namesFn = namesFn
// if extension is not static(setted by .Directory)
if t.location.extension == "" {
if names := namesFn(); len(names) > 0 {
t.location.extension = filepath.Ext(names[0]) // we need the extension to get the correct template engine on the Render method
}
}
}
func (t *TemplateEngineLocation) isBinary() bool {
return t.assetFn != nil && t.namesFn != nil
}
// TemplateEngineWrapper is the wrapper of a template engine
type TemplateEngineWrapper struct {
TemplateEngine
location *TemplateEngineLocation
buffer *utils.BufferPool
gzipWriterPool sync.Pool
reload bool
combiledContentType string
}
var (
errMissingDirectoryOrAssets = errors.New("Missing Directory or Assets by binary for the template engine!")
errNoTemplateEngineForExt = errors.New("No template engine found to manage '%s' extensions")
)
func (t *TemplateEngineWrapper) load() error {
if t.location.isBinary() {
t.LoadAssets(t.location.directory, t.location.extension, t.location.assetFn, t.location.namesFn)
} else if t.location.directory != "" {
t.LoadDirectory(t.location.directory, t.location.extension)
} else {
return errMissingDirectoryOrAssets.Return()
}
return nil
}
// Execute execute a template and write its result to the context's body
// options are the optional runtime options can be passed by user and catched by the template engine when render
// an example of this is the "layout"
// note that gzip option is an iris dynamic option which exists for all template engines
func (t *TemplateEngineWrapper) Execute(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) (err error) {
if t == nil {
//file extension, but no template engine registered, this caused by context, and TemplateEngines. GetBy
return errNoTemplateEngineForExt.Format(filepath.Ext(filename))
}
if t.reload {
if err = t.load(); err != nil {
return
}
}
// we do all these because we don't want to initialize a new map for each execution...
gzipEnabled := false
if len(options) > 0 {
gzipOpt := options[0]["gzip"] // we only need that, so don't create new map to keep the options.
if b, isBool := gzipOpt.(bool); isBool {
gzipEnabled = b
}
}
ctxLayout := ctx.GetString(config.TemplateLayoutContextKey)
if ctxLayout != "" {
if len(options) > 0 {
options[0]["layout"] = ctxLayout
} else {
options = []map[string]interface{}{map[string]interface{}{"layout": ctxLayout}}
}
}
var out io.Writer
if gzipEnabled {
ctx.Response.Header.Add("Content-Encoding", "gzip")
gzipWriter := t.gzipWriterPool.Get().(*gzip.Writer)
gzipWriter.Reset(ctx.Response.BodyWriter())
defer gzipWriter.Close()
defer t.gzipWriterPool.Put(gzipWriter)
out = gzipWriter
} else {
out = ctx.Response.BodyWriter()
}
ctx.SetHeader("Content-Type", t.combiledContentType)
return t.ExecuteWriter(out, filename, binding, options...)
}
// ExecuteToString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
func (t *TemplateEngineWrapper) ExecuteToString(filename string, binding interface{}, opt ...map[string]interface{}) (result string, err error) {
if t == nil {
//file extension, but no template engine registered, this caused by context, and TemplateEngines. GetBy
return "", errNoTemplateEngineForExt.Format(filepath.Ext(filename))
}
if t.reload {
if err = t.load(); err != nil {
return
}
}
out := t.buffer.Get()
defer t.buffer.Put(out)
err = t.ExecuteWriter(out, filename, binding, opt...)
if err == nil {
result = out.String()
}
return
}
// TemplateEngines is the container and manager of the template engines
type TemplateEngines struct {
engines []*TemplateEngineWrapper
helpers map[string]interface{}
reload bool
}
func (t *TemplateEngines) setReload(b bool) {
t.reload = b
}
// GetBy receives a filename, gets its extension and returns the template engine responsible for that file extension
func (t *TemplateEngines) GetBy(filename string) *TemplateEngineWrapper {
extension := filepath.Ext(filename)
for i, n := 0, len(t.engines); i < n; i++ {
e := t.engines[i]
if e.location.extension == extension {
return e
}
}
return nil
}
// Add adds but not loads a template engine
func (t *TemplateEngines) Add(e TemplateEngine) *TemplateEngineLocation {
location := &TemplateEngineLocation{}
// add the iris helper funcs
if funcer, ok := e.(TemplateEngineFuncs); ok {
if funcer.Funcs() != nil {
for k, v := range t.helpers {
funcer.Funcs()[k] = v
}
}
}
tmplEngine := &TemplateEngineWrapper{
TemplateEngine: e,
location: location,
buffer: utils.NewBufferPool(20),
gzipWriterPool: sync.Pool{New: func() interface{} {
return &gzip.Writer{}
}},
reload: t.reload,
combiledContentType: config.ContentTypeHTML + "; " + config.Charset,
}
t.engines = append(t.engines, tmplEngine)
return location
}
// loadAll loads all templates using all template engines, returns the first error
// called on iris' initialize
func (t *TemplateEngines) loadAll() error {
for i, n := 0, len(t.engines); i < n; i++ {
e := t.engines[i]
if e.location.directory == "" {
e.location.directory = DefaultTemplateDirectory // the defualt dir ./templates
}
if e.location.extension == "" {
e.location.extension = DefaultTemplateExtension // the default file ext .html
}
if err := e.load(); err != nil {
return err
}
}
return nil
}