mirror of
https://github.com/kataras/iris.git
synced 2025-02-09 02:34:55 +01:00
move the template engines manager to the iris-contrib/template
This commit is contained in:
parent
f7a782b692
commit
74aeb081ee
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Charset character encoding for template rendering
|
// Charset character encoding for various rendering
|
||||||
Charset = "UTF-8"
|
Charset = "UTF-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,14 +20,4 @@ var (
|
||||||
//
|
//
|
||||||
// Defaults to iris-fasthttp.gz
|
// Defaults to iris-fasthttp.gz
|
||||||
CompressedFileSuffix = "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
26
iris.go
|
@ -67,6 +67,7 @@ import (
|
||||||
"github.com/iris-contrib/errors"
|
"github.com/iris-contrib/errors"
|
||||||
"github.com/iris-contrib/logger"
|
"github.com/iris-contrib/logger"
|
||||||
"github.com/iris-contrib/rest"
|
"github.com/iris-contrib/rest"
|
||||||
|
"github.com/iris-contrib/template"
|
||||||
"github.com/iris-contrib/template/html"
|
"github.com/iris-contrib/template/html"
|
||||||
"github.com/kataras/iris/config"
|
"github.com/kataras/iris/config"
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
|
@ -89,6 +90,9 @@ const (
|
||||||
| | | __|| |/ __|
|
| | | __|| |/ __|
|
||||||
_| |_| | | |\__ \
|
_| |_| | | |\__ \
|
||||||
|_____|_| |_||___/ ` + Version + ` `
|
|_____|_| |_||___/ ` + 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
|
// Default entry, use it with iris.$anyPublicFunc
|
||||||
|
@ -144,7 +148,7 @@ type (
|
||||||
ListenVirtual(...string) *Server
|
ListenVirtual(...string) *Server
|
||||||
Go() error
|
Go() error
|
||||||
Close() error
|
Close() error
|
||||||
UseTemplate(TemplateEngine) *TemplateEngineLocation
|
UseTemplate(template.TemplateEngine) *template.TemplateEngineLocation
|
||||||
UseGlobal(...Handler)
|
UseGlobal(...Handler)
|
||||||
UseGlobalFunc(...HandlerFunc)
|
UseGlobalFunc(...HandlerFunc)
|
||||||
OnError(int, HandlerFunc)
|
OnError(int, HandlerFunc)
|
||||||
|
@ -164,7 +168,7 @@ type (
|
||||||
*muxAPI
|
*muxAPI
|
||||||
rest *rest.Render
|
rest *rest.Render
|
||||||
sessions *sessions.Manager
|
sessions *sessions.Manager
|
||||||
templates *TemplateEngines
|
templates *template.TemplateEngines
|
||||||
|
|
||||||
// fields which are useful to the user/dev
|
// fields which are useful to the user/dev
|
||||||
// the last added server is the main server
|
// the last added server is the main server
|
||||||
|
@ -199,12 +203,12 @@ func New(cfg ...config.Iris) *Framework {
|
||||||
// set the plugin container
|
// set the plugin container
|
||||||
s.Plugins = &pluginContainer{logger: s.Logger}
|
s.Plugins = &pluginContainer{logger: s.Logger}
|
||||||
// set the templates
|
// set the templates
|
||||||
s.templates = &TemplateEngines{
|
s.templates = &template.TemplateEngines{
|
||||||
helpers: map[string]interface{}{
|
Helpers: map[string]interface{}{
|
||||||
"url": s.URL,
|
"url": s.URL,
|
||||||
"urlpath": s.Path,
|
"urlpath": s.Path,
|
||||||
},
|
},
|
||||||
engines: make([]*TemplateEngineWrapper, 0),
|
Engines: make([]*template.TemplateEngineWrapper, 0),
|
||||||
}
|
}
|
||||||
// set the websocket server
|
// set the websocket server
|
||||||
s.Websocket = websocket.NewServer(s.Config.Websocket)
|
s.Websocket = websocket.NewServer(s.Config.Websocket)
|
||||||
|
@ -230,14 +234,14 @@ func (s *Framework) initialize() {
|
||||||
s.rest = rest.New(s.Config.Rest)
|
s.rest = rest.New(s.Config.Rest)
|
||||||
// prepare the templates if enabled
|
// prepare the templates if enabled
|
||||||
if !s.Config.DisableTemplateEngines {
|
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.
|
s.Logger.Panic(err) // panic on templates loading before listening if we have an error.
|
||||||
}
|
}
|
||||||
// check and prepare the templates
|
// 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.UseTemplate(html.New())
|
||||||
}
|
}
|
||||||
s.templates.setReload(s.Config.IsDevelopment)
|
s.templates.Reload = s.Config.IsDevelopment
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen to websocket connections
|
// listen to websocket connections
|
||||||
|
@ -487,13 +491,13 @@ s.renderer = &renderer{
|
||||||
|
|
||||||
// UseTemplate adds a template engine to the iris view system
|
// UseTemplate adds a template engine to the iris view system
|
||||||
// it does not build/load them yet
|
// it does not build/load them yet
|
||||||
func UseTemplate(e TemplateEngine) *TemplateEngineLocation {
|
func UseTemplate(e template.TemplateEngine) *template.TemplateEngineLocation {
|
||||||
return Default.UseTemplate(e)
|
return Default.UseTemplate(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseTemplate adds a template engine to the iris view system
|
// UseTemplate adds a template engine to the iris view system
|
||||||
// it does not build/load them yet
|
// 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)
|
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 {
|
func (api *muxAPI) Layout(tmplLayoutFile string) MuxAPI {
|
||||||
api.UseFunc(func(ctx *Context) {
|
api.UseFunc(func(ctx *Context) {
|
||||||
ctx.Set(config.TemplateLayoutContextKey, tmplLayoutFile)
|
ctx.Set(template.TemplateLayoutContextKey, tmplLayoutFile)
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
})
|
})
|
||||||
return api
|
return api
|
||||||
|
|
277
template.go
277
template.go
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user