From 74aeb081ee7a3867e8c720c8c0ba8d021cb1c094 Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Wed, 13 Jul 2016 14:54:56 +0300 Subject: [PATCH] move the template engines manager to the iris-contrib/template --- config/config.go | 12 +- iris.go | 26 +++-- template.go | 277 ----------------------------------------------- 3 files changed, 16 insertions(+), 299 deletions(-) delete mode 100644 template.go diff --git a/config/config.go b/config/config.go index 4bd0bb1b..97252830 100644 --- a/config/config.go +++ b/config/config.go @@ -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" ) diff --git a/iris.go b/iris.go index 5227c943..2038e047 100644 --- a/iris.go +++ b/iris.go @@ -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 diff --git a/template.go b/template.go deleted file mode 100644 index 9dbb4379..00000000 --- a/template.go +++ /dev/null @@ -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 -}