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 @@
-
+

@@ -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
-}