From c38a9b2459c879fd35007a4527480681a7d04ac0 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Fri, 2 Sep 2016 06:05:44 +0300 Subject: [PATCH] Developers can ignore this update. Replace the template engines with a new cross-framework package (kataras/go-template) --- HISTORY.md | 8 +- README.md | 9 +- context.go | 2 +- iris.go | 31 +++--- iris/create.go | 3 +- plugin.go | 7 +- response.go | 7 +- template.go | 250 +++---------------------------------------------- 8 files changed, 46 insertions(+), 271 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 51a3a9af..cd800cb1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,7 +2,13 @@ **How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`. -## 4.1.1 -> 4.1.2 +## 4.1.2 -> 4.1.3 + +Zero front-end changes. No real improvements, developers can ignore this update. + +- Replace the template engines with a new cross-framework package, [go-template](https://github.com/kataras/go-websocket). Same front-end API, examples and iris-contrib/template are compatible. + +## 4.1.1 -> 4.1.2 Zero front-end changes. No real improvements, developers can ignore this update. diff --git a/README.md b/README.md index 0f4ea59e..dd68f077 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ License -Releases +Releases Practical Guide/Docs
@@ -113,10 +113,11 @@ go-* packages | Name | Description | ------------------|:---------------------:| +| [go-template](https://github.com/kataras/go-template) | Cross-framework template engines +| [go-websocket](https://github.com/kataras/go-errors) | A websocket server and ,optionally, client side lib for Go | [go-errors](https://github.com/kataras/go-errors) | Error handling | [go-fs](https://github.com/kataras/go-fs) | FileSystem utils and common net/http static files handlers | [go-events](https://github.com/kataras/go-events) | EventEmmiter for Go -| [go-websocket](https://github.com/kataras/go-errors) | A websocket server and ,optionally, client side lib for Go | [go-ssh](https://github.com/kataras/go-ssh) | SSH Server, build ssh interfaces, remote commands and remote cli with ease | [go-gzipwriter](https://github.com/kataras/go-gzipwriter) | Write gzip data to a io.Writer | [go-mailer](https://github.com/kataras/go-mailer) | E-mail Sender, send rich mails with one call @@ -159,7 +160,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v4.1.2** +Current: **v4.1.3** > Iris is an active project @@ -194,7 +195,7 @@ License can be found [here](LICENSE). [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-v4.1.2-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-v4.1.3-blue.svg?style=flat-square [Release]: https://github.com/kataras/iris/releases [Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square [Chat]: https://kataras.rocket.chat/channel/iris diff --git a/context.go b/context.go index f9b14eb3..b973d3bd 100644 --- a/context.go +++ b/context.go @@ -546,7 +546,7 @@ func (ctx *Context) Gzip(b []byte, status int) { func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) error { ctx.SetStatusCode(status) if strings.IndexByte(name, '.') > -1 { //we have template - return ctx.framework.templates.getBy(name).execute(ctx, name, binding, options...) + return ctx.framework.templates.render(ctx, name, binding, options...) } return ctx.framework.responses.getBy(name).render(ctx, binding, options...) } diff --git a/iris.go b/iris.go index aa6dd8f1..48dcd250 100644 --- a/iris.go +++ b/iris.go @@ -78,6 +78,7 @@ import ( "github.com/iris-contrib/template/html" "github.com/kataras/go-errors" "github.com/kataras/go-fs" + "github.com/kataras/go-template" "github.com/kataras/iris/config" "github.com/kataras/iris/context" "github.com/kataras/iris/utils" @@ -86,7 +87,7 @@ import ( const ( // Version of the iris - Version = "4.1.2" + Version = "4.1.3" banner = ` _____ _ |_ _| (_) @@ -155,7 +156,7 @@ type ( Close() error UseSessionDB(SessionDatabase) UseResponse(ResponseEngine, ...string) func(string) - UseTemplate(TemplateEngine) *TemplateEngineLocation + UseTemplate(template.Engine) *template.Loader UseGlobal(...Handler) UseGlobalFunc(...HandlerFunc) Lookup(string) Route @@ -216,13 +217,10 @@ 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{}{ - "url": s.URL, - "urlpath": s.Path, - }, - engines: make([]*templateEngineWrapper, 0), - } + s.templates = newTemplateEngines(map[string]interface{}{ + "url": s.URL, + "urlpath": s.Path, + }) // set the sessions if s.Config.Sessions.Cookie != "" { //set the session manager @@ -270,13 +268,13 @@ func (s *Framework) initialize() { // prepare the templates if enabled if !s.Config.DisableTemplateEngines { - s.templates.reload = s.Config.IsDevelopment + s.templates.Reload = s.Config.IsDevelopment // 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.Entries) == 0 { // no template engine is registered, let's use the default s.UseTemplate(html.New()) } - if err := s.templates.loadAll(); err != nil { + if err := s.templates.Load(); err != nil { s.Logger.Panic(err) // panic on templates loading before listening if we have an error. } } @@ -661,14 +659,14 @@ func (s *Framework) UseResponse(e ResponseEngine, forContentTypesOrKeys ...strin // 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.Engine) *template.Loader { 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 { - return s.templates.add(e) +func (s *Framework) UseTemplate(e template.Engine) *template.Loader { + return s.templates.AddEngine(e) } // UseGlobal registers Handler middleware to the beginning, prepends them instead of append @@ -938,7 +936,8 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{}, if s.Config.DisableTemplateEngines { return "" } - res, err := s.templates.getBy(templateFile).executeToString(templateFile, pageContext, options...) + + res, err := s.templates.ExecuteString(templateFile, pageContext, options...) if err != nil { return "" } diff --git a/iris/create.go b/iris/create.go index 4f68dd55..f83855e9 100644 --- a/iris/create.go +++ b/iris/create.go @@ -9,7 +9,6 @@ import ( "github.com/kataras/cli" "github.com/kataras/go-fs" - "github.com/kataras/go-installer" "github.com/kataras/iris/utils" ) @@ -86,7 +85,7 @@ func create(flags cli.Flags) (err error) { func downloadPackages() { errMsg := "\nProblem while downloading the assets from the internet for the first time. Trace: %s" - installedDir, err := installer.Install(PackagesURL, packagesInstallDir, true) + installedDir, err := fs.Install(PackagesURL, packagesInstallDir, true) if err != nil { printer.Dangerf(errMsg, err.Error()) return diff --git a/plugin.go b/plugin.go index d2915812..931db534 100644 --- a/plugin.go +++ b/plugin.go @@ -7,7 +7,6 @@ import ( "github.com/iris-contrib/logger" "github.com/kataras/go-fs" - "github.com/kataras/go-installer" ) var ( @@ -205,12 +204,12 @@ func (d *pluginDownloadManager) DirectoryExists(dir string) bool { // DownloadZip downlodas a zip to the given local path location func (d *pluginDownloadManager) DownloadZip(zipURL string, targetDir string) (string, error) { - return installer.DownloadZip(zipURL, targetDir, true) + return fs.DownloadZip(zipURL, targetDir, true) } // Unzip unzips a zip to the given local path location func (d *pluginDownloadManager) Unzip(archive string, target string) (string, error) { - return installer.DownloadZip(archive, target, true) + return fs.DownloadZip(archive, target, true) } // Remove deletes/removes/rm a file @@ -220,7 +219,7 @@ func (d *pluginDownloadManager) Remove(filePath string) error { // Install is just the flow of the: DownloadZip->Unzip->Remove the zip func (d *pluginDownloadManager) Install(remoteFileZip string, targetDirectory string) (string, error) { - return installer.Install(remoteFileZip, targetDirectory, true) + return fs.Install(remoteFileZip, targetDirectory, true) } // pluginContainer is the base container of all Iris, registed plugins diff --git a/response.go b/response.go index 0dc2c0df..46298c12 100644 --- a/response.go +++ b/response.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/kataras/go-errors" + "github.com/kataras/go-template" "github.com/valyala/fasthttp" ) @@ -139,10 +140,8 @@ func (r *responseEngineMap) render(ctx *Context, obj interface{}, options ...map gzipEnabled := ctx.framework.Config.Gzip charset := ctx.framework.Config.Charset if len(options) > 0 { - gzipEnabled = getGzipOption(ctx, options[0]) // located to the template.go below the RenderOptions - if chs := getCharsetOption(options[0]); chs != "" { - charset = chs - } + gzipEnabled = template.GetGzipOption(gzipEnabled, options[0]) // located to the template.go below the RenderOptions + charset = template.GetCharsetOption(charset, options[0]) } ctype := r.contentType diff --git a/template.go b/template.go index fb7e970a..7fd5d7cb 100644 --- a/template.go +++ b/template.go @@ -1,192 +1,49 @@ package iris import ( + "github.com/kataras/go-template" "io" - - "path/filepath" - - "github.com/kataras/go-errors" - "github.com/kataras/iris/utils" ) var ( builtinFuncs = [...]string{"url", "urlpath"} - - // DefaultTemplateDirectory the default directory if empty setted - DefaultTemplateDirectory = "." + utils.PathSeparator + "templates" ) const ( - - // DefaultTemplateExtension the default file extension if empty setted - DefaultTemplateExtension = ".html" // NoLayout to disable layout for a particular template file - NoLayout = "@.|.@iris_no_layout@.|.@" + NoLayout = template.NoLayout // 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" ) 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 when Render // an example of this is the "layout" or "gzip" option // same as Map but more specific name 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 +// templateEngines just a wrapper of template.Mux in order to use it's execute without break the whole of the API +type templateEngines struct { + *template.Mux } -func getGzipOption(ctx *Context, options map[string]interface{}) bool { - gzipOpt := options["gzip"] // we only need that, so don't create new map to keep the options. - if b, isBool := gzipOpt.(bool); isBool { - return b - } - return ctx.framework.Config.Gzip +func newTemplateEngines(sharedFuncs map[string]interface{}) *templateEngines { + return &templateEngines{Mux: template.NewMux(sharedFuncs)} } -func getCharsetOption(options map[string]interface{}) string { - charsetOpt := options["charset"] - if s, isString := charsetOpt.(string); isString { - return s - } - return "" // we return empty in order to set the default charset if not founded. -} - -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 { - if dir == "" { - dir = DefaultTemplateDirectory // the default templates dir - } - if fileExtension == "" { - fileExtension = DefaultTemplateExtension - } else if fileExtension[0] != '.' { // if missing the start dot - fileExtension = "." + fileExtension - } - - 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) { - if assetFn == nil || namesFn == nil { - return - } - - 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 - reload bool -} - -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 nil -} - -// execute execute a template and write its result to the context's body +// render executes 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 // the gzip and charset options are built'n with iris -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 - } - } - +func (t *templateEngines) render(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) (err error) { // we do all these because we don't want to initialize a new map for each execution... gzipEnabled := ctx.framework.Config.Gzip charset := ctx.framework.Config.Charset if len(options) > 0 { - gzipEnabled = getGzipOption(ctx, options[0]) - - if chs := getCharsetOption(options[0]); chs != "" { - charset = chs - } + gzipEnabled = template.GetGzipOption(gzipEnabled, options[0]) + charset = template.GetCharsetOption(charset, options[0]) } ctxLayout := ctx.GetString(TemplateLayoutContextKey) @@ -215,88 +72,3 @@ func (t *templateEngineWrapper) execute(ctx *Context, filename string, binding i err = t.ExecuteWriter(out, filename, binding, options...) return err } - -// 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 { - helpers map[string]interface{} - engines []*templateEngineWrapper - reload bool -} - -// 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(8), - reload: t.reload, - } - - 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 - } - - e.reload = t.reload // update the configuration every time a load happens - - if err := e.load(); err != nil { - return err - } - } - return nil -}