diff --git a/HISTORY.md b/HISTORY.md index 699bd9cb..3e3c9ae3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,26 @@ **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`. + +## 3.0.0 -> 4.0.0-alpha.1 + +[logger](https://github.com/iris-contrib/logger), [rest](https://github.com/iris-contrib/rest) and all [template engines](https://github.com/iris-contrib/template) **moved** to the [iris-contrib](https://github.com/iris-contrib). + +- `config.Logger` -> `iris.Logger.Config` +- `config.Render/config.Render.Rest/config.Render.Template` -> **Removed** +- `config.Render.Rest` -> `rest.Config` +- `config.Render.Template` -> `$TEMPLATE_ENGINE.Config` except Directory,Extensions, Assets, AssetNames, +- `config.Render.Template.Directory` -> `iris.UseEngine($TEMPLAET_ENGINE.New()).Directory("./templates", ".html")` +- `config.Render.Template.Assets` -> `iris.UseEngine($TEMPLAET_ENGINE.New()).Directory("./templates",".html").Binary(assetFn func(name string) ([]byte, error), namesFn func() []string)` + +- `context.ExecuteTemplate` -> **Removed**, you can use the `context.Response.BodyWriter()` to get its writer and execute html/template engine manually, but this is useless because we support the best support for template engines among all other (golang) web frameworks +- **Added** `config.Server.ReadBufferSize & config.Server.WriteBufferSize` which can be passed as configuration fields inside `iris.ListenTo(config.Server{...})`, which does the same job as `iris.Listen` +- **Added** `iris.UseEngine($TEMPLAET_ENGINE.New()).Directory("./templates", ".html")` to register a template engine, now iris supports multi template engines, each template engine has its own file extension, no big changes on context.Render except the last parameter: +- `context.Render(filename string, binding interface{}, layout string{})` -> `context.Render(filename string, binding interface{}, options ...map[string]interface{}) | context.Render("myfile.html", myPage{}, iris.Map{"gzip":true,"layout":"layouts/MyLayout.html"}) |` + +E-book and examples are not yet updated, no big changes. + + ## 3.0.0-rc.4 -> 3.0.0-pre.release - `context.PostFormValue` -> `context.FormValueString`, old func stays until the next revision diff --git a/README.md b/README.md index fb789271..b99af9b4 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [![Travis Widget]][Travis] [![Release Widget]][Release] [![Report Widget]][Report] [![License Widget]][License] [![Chat Widget]][Chat] [![Documentation Widget]][Documentation] -The [fastest](#benchmarks) web framework for Go. +The fastest web framework for Go. -[![Alt: The results have been updated on July 1, 2016](https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/master/benchmark.png)](https://github.com/smallnest/go-web-framework-benchmark) +[![Alt: Benchmark results with pipeline and 500ms processing time](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/benchmark_horizontal_transparent.png)](#benchmarks) @@ -22,7 +22,7 @@ func main() { c.JSON(iris.StatusOK, iris.Map{ "Name": "Iris", "Born": "13 March 2016", - "Stars": 3693, + "Stars": 4262, }) }) iris.Listen(":8080") @@ -46,7 +46,7 @@ The only requirement is the [Go Programming Language](https://golang.org/dl), at $ go get -u github.com/kataras/iris/iris ``` - >If you have installation issues and you are connected to the Internet through China please, [click here](https://kataras.gitbooks.io/iris/content/install.html). + >If you have installation issues or you are connected to the Internet through China please, [click here](https://kataras.gitbooks.io/iris/content/install.html). FAQ ------------ @@ -76,6 +76,12 @@ Features | Name | Description | Usage | | ------------------|:---------------------:|-------:| +| [HTML/Default Engine ](https://github.com/iris-contrib/template/tree/master/html) | HTML Template Engine (Default) |[example 1](https://github.com/iris-contrib/examples/blob/master/template_engines/template_html_1/main.go), [book section](https://kataras.gitbooks.io/iris/content/render_templates.html) +| [Django Engine ](https://github.com/iris-contrib/template/tree/master/django) | Django Template Engine |[example 1](https://github.com/iris-contrib/examples/blob/master/template_engines/template_django_1/main.go), [book section](https://kataras.gitbooks.io/iris/content/brender_templates.html) +| [Pug/Jade Engine ](https://github.com/iris-contrib/template/tree/master/pug) | Pug Template Engine |[example 1](https://github.com/iris-contrib/examples/blob/master/template_engines/template_pug_1/main.go), [book section](https://kataras.gitbooks.io/iris/content/render_templates.html) +| [Handlebars Engine ](https://github.com/iris-contrib/template/tree/master/handlebars) | Handlebars Template Engine |[example 1](https://github.com/iris-contrib/examples/blob/master/template_engines/template_handlebars_1/main.go), [book section](https://kataras.gitbooks.io/iris/content/render_templates.html) +| [Amber Engine ](https://github.com/iris-contrib/template/tree/master/amber) | Amber Template Engine |[example 1](https://github.com/iris-contrib/examples/blob/master/template_engines/template_amber_1/main.go), [book section](https://kataras.gitbooks.io/iris/content/render_templates.html) +| [Markdown Engine ](https://github.com/iris-contrib/template/tree/master/markdown) | Markdown Template Engine |[example 1](https://github.com/iris-contrib/examples/blob/master/template_engines/template_markdown_1/main.go), [book section](https://kataras.gitbooks.io/iris/content/render_templates.html) | [Basicauth Middleware ](https://github.com/iris-contrib/middleware/tree/master/basicauth) | HTTP Basic authentication |[example 1](https://github.com/iris-contrib/examples/blob/master/middleware_basicauth_1/main.go), [example 2](https://github.com/iris-contrib/examples/blob/master/middleware_basicauth_2/main.go), [book section](https://kataras.gitbooks.io/iris/content/basic-authentication.html) | | [JWT Middleware ](https://github.com/iris-contrib/middleware/tree/master/jwt) | JSON Web Tokens |[example ](https://github.com/iris-contrib/examples/blob/master/middleware_jwt/main.go), [book section](https://kataras.gitbooks.io/iris/content/jwt.html) | | [Cors Middleware ](https://github.com/iris-contrib/middleware/tree/master/cors) | Cross Origin Resource Sharing W3 specification | [how to use ](https://github.com/iris-contrib/middleware/tree/master/cors#how-to-use) | @@ -133,30 +139,27 @@ Iris does not force you to use any specific ORM or template engine. With support Testing ------------ -Tests are located to the [iris-contrib/tests repository](https://github.com/iris-contrib/tests), community should write some code there! +Community should write third-party or iris base tests to the [iris-contrib/tests repository](https://github.com/iris-contrib/tests). I recommend writing your API tests using this new library, [httpexpect](https://github.com/gavv/httpexpect) which supports Iris and fasthttp now, after my request [here](https://github.com/gavv/httpexpect/issues/2). Versioning ------------ -Current: **v3.0.0** +Current: **v4.0.0-alpha.1** > Iris is an active project -Read more about Semantic Versioning 2.0.0 - - - http://semver.org/ - - https://en.wikipedia.org/wiki/Software_versioning - - https://wiki.debian.org/UpstreamGuide#Releases_and_Versions - Todo ------------ > for the next version 'v4' -- [ ] Refactor & extend view engine, separate the engines from the main code base, easier for the community to create new view engines. -- [ ] Create a router as optional plugin, for optional path parts. Its name, 'ryan', taken from the community-member and donator who requested this feature. +- [x] Refactor & extend view engine, separate the engines from the main code base, easier for the community to create new view engines +- [ ] Implement all [opened community's feature requests](https://github.com/kataras/iris/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22) +- [ ] Extend i18n middleware for easier and better internalization support +- [ ] Create a router as optional plugin, for optional path parts. Its name, 'ryan', taken from the community-member and donator who requested this feature - [ ] Extend the iris control plugin -- [ ] Remove deprecated functions. +- [ ] Remove deprecated functions +- [ ] Will think more :) > completed for release 'v3' @@ -191,7 +194,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-v3.0.0-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-v4.0.0--alpha.1-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/config/config.go b/config/config.go index 9d1bc1b8..4bd0bb1b 100644 --- a/config/config.go +++ b/config/config.go @@ -6,6 +6,13 @@ import ( ) var ( + // Charset character encoding for template rendering + Charset = "UTF-8" +) + +var ( + // TimeFormat default time format for any kind of datetime parsing + TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" // StaticCacheDuration expiration duration for INACTIVE file handlers StaticCacheDuration = 20 * time.Second // CompressedFileSuffix is the suffix to add to the name of @@ -13,4 +20,14 @@ 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/config/iris.go b/config/iris.go index 247356a2..716c2b74 100644 --- a/config/iris.go +++ b/config/iris.go @@ -1,6 +1,9 @@ package config -import "github.com/imdario/mergo" +import ( + "github.com/imdario/mergo" + "github.com/iris-contrib/rest" +) // Default values for base Iris conf const ( @@ -78,16 +81,19 @@ type ( // http://debug.yourdomain:PORT/threadcreate // http://debug.yourdomain:PORT/pprof/block ProfilePath string - - // Logger the configuration for the logger - // Iris logs ONLY SEMANTIC errors and the banner if enabled - Logger Logger + // DisableTemplateEngines set to true to disable loading the default template engine (html/template) and disallow the use of iris.UseEngine + // default is false + DisableTemplateEngines bool + // IsDevelopment iris will act like a developer, for example + // If true then re-builds the templates on each request + // default is false + IsDevelopment bool // Sessions contains the configs for sessions Sessions Sessions - // Render contains the configs for template and rest configuration - Render Render + // Rest contains the configs for rest render configuration + Rest rest.Config // Websocket contains the configs for Websocket's server integration Websocket *Websocket @@ -95,41 +101,21 @@ type ( // Tester contains the configs for the test framework, so far we have only one because all test framework's configs are setted by the iris itself Tester Tester } - - // Render struct keeps organise all configuration about rendering, templates and rest currently. - Render struct { - // Template the configs for template - Template Template - // Rest configs for rendering. - // - // these options inside this config don't have any relation with the TemplateEngine - // from github.com/kataras/iris/rest - Rest Rest - } ) -// DefaultRender returns default configuration for templates and rest rendering -func DefaultRender() Render { - return Render{ - // set the default template config both not nil and default Engine to Standar - Template: DefaultTemplate(), - // set the default configs for rest - Rest: DefaultRest(), - } -} - // Default returns the default configuration for the Iris staton func Default() Iris { return Iris{ - DisablePathCorrection: DefaultDisablePathCorrection, - DisablePathEscape: DefaultDisablePathEscape, - DisableBanner: false, - ProfilePath: "", - Logger: DefaultLogger(), - Sessions: DefaultSessions(), - Render: DefaultRender(), - Websocket: DefaultWebsocket(), - Tester: DefaultTester(), + DisablePathCorrection: DefaultDisablePathCorrection, + DisablePathEscape: DefaultDisablePathEscape, + DisableBanner: false, + DisableTemplateEngines: false, + IsDevelopment: false, + ProfilePath: "", + Sessions: DefaultSessions(), + Rest: rest.DefaultConfig(), + Websocket: DefaultWebsocket(), + Tester: DefaultTester(), } } @@ -158,18 +144,3 @@ func (c Iris) MergeSingle(cfg Iris) (config Iris) { return } - -/* maybe some day -// FromFile returns the configuration for Iris station -// -// receives one parameter -// pathIni(string) the file path of the configuration-ini style -// -// returns an error if something bad happens -func FromFile(pathIni string) (c Iris, err error) { - c = Iris{} - err = ini.MapTo(&c, pathIni) - - return -} -*/ diff --git a/config/server.go b/config/server.go index 0a04f671..6c4c450f 100644 --- a/config/server.go +++ b/config/server.go @@ -69,6 +69,7 @@ type Server struct { // // Default buffer size is used if not set. WriteBufferSize int + // RedirectTo, defaults to empty, set it in order to override the station's handler and redirect all requests to this address which is of form(HOST:PORT or :PORT) // // NOTE: the http status is 'StatusMovedPermanently', means one-time-redirect(the browser remembers the new addr and goes to the new address without need to request something from this server diff --git a/context.go b/context.go index 87393756..465049dd 100644 --- a/context.go +++ b/context.go @@ -10,7 +10,6 @@ import ( "encoding/json" "encoding/xml" "fmt" - "html/template" "io" "net" "os" @@ -476,23 +475,23 @@ func (ctx *Context) Data(status int, v []byte) error { // RenderWithStatus builds up the response from the specified template and bindings. // Note: parameter layout has meaning only when using the iris.HTMLTemplate -func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, layout ...string) error { +func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) error { ctx.SetStatusCode(status) - return ctx.framework.templates.Render(ctx, name, binding, layout...) + return ctx.framework.templates.GetBy(name).Execute(ctx, name, binding, options...) } // Render same as .RenderWithStatus but with status to iris.StatusOK (200) -func (ctx *Context) Render(name string, binding interface{}, layout ...string) error { +func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error { errCode := ctx.RequestCtx.Response.StatusCode() if errCode <= 0 { errCode = StatusOK } - return ctx.RenderWithStatus(errCode, name, binding, layout...) + return ctx.RenderWithStatus(errCode, name, binding, options...) } // MustRender same as .Render but returns 500 internal server http status (error) if rendering fail -func (ctx *Context) MustRender(name string, binding interface{}, layout ...string) { - if err := ctx.Render(name, binding, layout...); err != nil { +func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) { + if err := ctx.Render(name, binding, options...); err != nil { ctx.Panic() ctx.framework.Logger.Dangerf("MustRender panics for client with IP: %s On template: %s.Trace: %s\n", ctx.RemoteAddr(), name, err) } @@ -500,8 +499,8 @@ func (ctx *Context) MustRender(name string, binding interface{}, layout ...strin // TemplateString accepts a template filename, its context data and returns the result of the parsed template (string) // if any error returns empty string -func (ctx *Context) TemplateString(name string, binding interface{}, layout ...string) string { - return ctx.framework.TemplateString(name, binding, layout...) +func (ctx *Context) TemplateString(name string, binding interface{}, options ...map[string]interface{}) string { + return ctx.framework.TemplateString(name, binding, options...) } // JSON marshals the given interface object and writes the JSON response. @@ -537,17 +536,6 @@ func (ctx *Context) Markdown(status int, markdown string) { ctx.HTML(status, ctx.MarkdownString(markdown)) } -// ExecuteTemplate executes a simple html template, you can use that if you already have the cached templates -// the recommended way to render is to use iris.Templates("./templates/path/*.html") and ctx.RenderFile("filename.html",struct{}) -// accepts 2 parameters -// the first parameter is the template (*template.Template) -// the second parameter is the page context (interfac{}) -// returns an error if any errors occurs while executing this template -func (ctx *Context) ExecuteTemplate(tmpl *template.Template, pageContext interface{}) error { - ctx.RequestCtx.SetContentType(contentHTML + ctx.framework.rest.CompiledCharset) - return errTemplateExecute.With(tmpl.Execute(ctx.RequestCtx.Response.BodyWriter(), pageContext)) -} - // ServeContent serves content, headers are autoset // receives three parameters, it's low-level function, instead you can use .ServeFile(string) // diff --git a/context/context.go b/context/context.go index 166455dc..0d4f0f33 100644 --- a/context/context.go +++ b/context/context.go @@ -2,7 +2,6 @@ package context import ( "bufio" - "html/template" "io" "time" @@ -46,17 +45,16 @@ type ( Write(string, ...interface{}) HTML(int, string) Data(int, []byte) error - RenderWithStatus(int, string, interface{}, ...string) error - Render(string, interface{}, ...string) error - MustRender(string, interface{}, ...string) - TemplateString(string, interface{}, ...string) string + RenderWithStatus(int, string, interface{}, ...map[string]interface{}) error + Render(string, interface{}, ...map[string]interface{}) error + MustRender(string, interface{}, ...map[string]interface{}) + TemplateString(string, interface{}, ...map[string]interface{}) string MarkdownString(string) string Markdown(int, string) JSON(int, interface{}) error JSONP(int, string, interface{}) error Text(int, string) error XML(int, interface{}) error - ExecuteTemplate(*template.Template, interface{}) error ServeContent(io.ReadSeeker, string, time.Time, bool) error ServeFile(string, bool) error SendFile(string, string) error diff --git a/deprecated.go b/deprecated.go index 6357741e..8da1ea37 100644 --- a/deprecated.go +++ b/deprecated.go @@ -89,9 +89,7 @@ func MustUseFunc(handlersFn ...HandlerFunc) { // Use it when you want to add a global middleware to all parties, to all routes in all subdomains // It can be called after other, (but before .Listen of course) func (s *Framework) MustUse(handlers ...Handler) { - for _, r := range s.mux.lookups { - r.middleware = append(handlers, r.middleware...) - } + s.UseGlobal(handlers...) } // MustUseFunc registers HandlerFunc middleware to the beginning, prepends them instead of append @@ -99,7 +97,7 @@ func (s *Framework) MustUse(handlers ...Handler) { // Use it when you want to add a global middleware to all parties, to all routes in all subdomains // It can be called after other, (but before .Listen of course) func (s *Framework) MustUseFunc(handlersFn ...HandlerFunc) { - s.MustUse(convertToHandlers(handlersFn)...) + s.UseGlobalFunc(handlersFn...) } // PostFormMulti returns a slice of string from post request's data diff --git a/glide.lock b/glide.lock index 99f93ca0..ce131ecb 100644 --- a/glide.lock +++ b/glide.lock @@ -101,7 +101,7 @@ imports: subpackages: - github.com\valyala\bytebufferpool - name: github.com/valyala/fasthttp - version: 97d96cb3b7feaf22ce9b813313cd8ae20b3589b9 + version: 886e5411604884629c566961ea8ed2cec074e4b1 subpackages: - github.com\valyala\fasthttp - github.com\valyala\fasthttp\fasthttpadaptor diff --git a/http.go b/http.go index 02e44f9c..ec7e932d 100644 --- a/http.go +++ b/http.go @@ -13,8 +13,8 @@ import ( "time" "github.com/iris-contrib/errors" + "github.com/iris-contrib/logger" "github.com/kataras/iris/config" - "github.com/kataras/iris/logger" "github.com/kataras/iris/utils" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpadaptor" @@ -262,6 +262,11 @@ func newServer(cfg config.Server) *Server { // prepare just prepares the listening addr func (s *Server) prepare() { s.Config.ListeningAddr = config.ServerParseAddr(s.Config.ListeningAddr) + if s.Server != nil { + s.Server.MaxRequestBodySize = s.Config.MaxRequestBodySize + s.Server.ReadBufferSize = s.Config.ReadBufferSize + s.Server.WriteBufferSize = s.Config.WriteBufferSize + } } // IsListening returns true if server is listening/started, otherwise false @@ -399,10 +404,6 @@ func (s *Server) Open(h fasthttp.RequestHandler) error { s.prepare() // do it again for any case - s.Server.MaxRequestBodySize = s.Config.MaxRequestBodySize - s.Server.ReadBufferSize = s.Config.ReadBufferSize - s.Server.WriteBufferSize = s.Config.WriteBufferSize - if s.Config.RedirectTo != "" { // override the handler and redirect all requests to this addr s.Server.Handler = func(reqCtx *fasthttp.RequestCtx) { diff --git a/iris.go b/iris.go index 9463783a..2a1197ee 100644 --- a/iris.go +++ b/iris.go @@ -65,11 +65,11 @@ import ( "github.com/gavv/httpexpect" "github.com/iris-contrib/errors" + "github.com/iris-contrib/logger" + "github.com/iris-contrib/rest" + "github.com/iris-contrib/template/html" "github.com/kataras/iris/config" "github.com/kataras/iris/context" - "github.com/kataras/iris/logger" - "github.com/kataras/iris/render/rest" - "github.com/kataras/iris/render/template" "github.com/kataras/iris/sessions" "github.com/kataras/iris/utils" "github.com/kataras/iris/websocket" @@ -81,27 +81,7 @@ import ( const ( // Version of the iris - Version = "3.0.0" - - // HTMLEngine conversion for config.HTMLEngine - HTMLEngine = config.HTMLEngine - // PongoEngine conversion for config.PongoEngine - PongoEngine = config.PongoEngine - // MarkdownEngine conversion for config.MarkdownEngine - MarkdownEngine = config.MarkdownEngine - // JadeEngine conversion for config.JadeEngine - JadeEngine = config.JadeEngine - // AmberEngine conversion for config.AmberEngine - AmberEngine = config.AmberEngine - // HandlebarsEngine conversion for config.HandlebarsEngine - HandlebarsEngine = config.HandlebarsEngine - // DefaultEngine conversion for config.DefaultEngine - DefaultEngine = config.DefaultEngine - // NoEngine conversion for config.NoEngine - NoEngine = config.NoEngine - // NoLayout to disable layout for a particular template file - // conversion for config.NoLayout - NoLayout = config.NoLayout + Version = "4.0.0-alpha.1" banner = ` _____ _ |_ _| (_) @@ -177,7 +157,7 @@ type ( Lookups() []Route Path(string, ...interface{}) string URL(string, ...interface{}) string - TemplateString(string, interface{}, ...string) string + TemplateString(string, interface{}, ...map[string]interface{}) string Tester(t *testing.T) *httpexpect.Expect } @@ -187,12 +167,14 @@ type ( Framework struct { *muxAPI rest *rest.Render - templates *template.Template sessions *sessions.Manager + templates *TemplateEngines + // fields which are useful to the user/dev // the last added server is the main server - Servers *ServerList - Config *config.Iris + Servers *ServerList + Config *config.Iris + // configuration by instance.Logger.Config Logger *logger.Logger Plugins PluginContainer Websocket websocket.Server @@ -217,9 +199,17 @@ func New(cfg ...config.Iris) *Framework { { ///NOTE: set all with s.Config pointer // set the Logger - s.Logger = logger.New(s.Config.Logger) + s.Logger = logger.New(logger.DefaultConfig()) // 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), + } // set the websocket server s.Websocket = websocket.NewServer(s.Config.Websocket) // set the servemux, which will provide us the public API also, with its context pool @@ -241,10 +231,18 @@ func (s *Framework) initialize() { } // set the rest - s.rest = rest.New(s.Config.Render.Rest) - - // set templates if not already setted - s.prepareTemplates() + s.rest = rest.New(s.Config.Rest) + // prepare the templates if enabled + if !s.Config.DisableTemplateEngines { + 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 + s.UseEngine(html.New()) + } + s.templates.setReload(s.Config.IsDevelopment) + } // listen to websocket connections websocket.RegisterServer(s, s.Websocket, s.Logger) @@ -258,22 +256,6 @@ func (s *Framework) initialize() { } } -// prepareTemplates sets the templates if not nil, we make this check because of .TemplateString, which can be called before Listen -func (s *Framework) prepareTemplates() { - // prepare the templates - if s.templates == nil { - // These functions are directly contact with Iris' functionality. - funcs := map[string]interface{}{ - "url": s.URL, - "urlpath": s.Path, - } - - template.RegisterSharedFuncs(funcs) - - s.templates = template.New(s.Config.Render.Template) - } -} - // Go starts the iris station, listens to all registered servers, and prepare only if Virtual func Go() error { return Default.Go() @@ -290,7 +272,6 @@ func (s *Framework) Go() error { // print the banner if !s.Config.DisableBanner { - openedServers := s.Servers.GetAllOpened() l := len(openedServers) hosts := make([]string, l, l) @@ -495,6 +476,31 @@ func (s *Framework) Close() error { return s.Servers.CloseAll() } +/* + +// set the template engines +s.renderer = &renderer{ + engines: make([]TemplateEngine, 0), + buffer: utils.NewBufferPool(64), + helpers: map[string]interface{}{ + "url": s.URL, + "urlpath": s.Path, + }, + contentType: s.Config.Render.Template.ContentType + "; " + s.Config.Render.Template.Charset, +}*/ + +// UseEngine adds a template engine to the iris view system +// it does not build/load them yet +func UseEngine(e TemplateEngine) *TemplateEngineLocation { + return Default.UseEngine(e) +} + +// UseEngine adds a template engine to the iris view system +// it does not build/load them yet +func (s *Framework) UseEngine(e TemplateEngine) *TemplateEngineLocation { + return s.templates.Add(e) +} + // UseGlobal registers Handler middleware to the beginning, prepends them instead of append // // Use it when you want to add a global middleware to all parties, to all routes in all subdomains @@ -713,17 +719,19 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) { return } -// TemplateString executes a template and returns its result as string, useful when you want it for sending rich e-mails +// TemplateString executes a template from the default template engine and returns its result as string, useful when you want it for sending rich e-mails // returns empty string on error -func TemplateString(templateFile string, pageContext interface{}, layout ...string) string { - return Default.TemplateString(templateFile, pageContext, layout...) +func TemplateString(templateFile string, pageContext interface{}, options ...map[string]interface{}) string { + return Default.TemplateString(templateFile, pageContext, options...) } -// TemplateString executes a template and returns its result as string, useful when you want it for sending rich e-mails +// TemplateString executes a template from the default template engine and returns its result as string, useful when you want it for sending rich e-mails // returns empty string on error -func (s *Framework) TemplateString(templateFile string, pageContext interface{}, layout ...string) string { - s.prepareTemplates() - res, err := s.templates.RenderString(templateFile, pageContext, layout...) +func (s *Framework) TemplateString(templateFile string, pageContext interface{}, options ...map[string]interface{}) string { + if s.Config.DisableTemplateEngines { + return "" + } + res, err := s.templates.GetBy(templateFile).ExecuteToString(templateFile, pageContext, options...) if err != nil { return "" } diff --git a/iris/main.go b/iris/main.go index d3eccb91..cc3c7163 100644 --- a/iris/main.go +++ b/iris/main.go @@ -3,10 +3,9 @@ package main import ( "os" + "github.com/iris-contrib/logger" "github.com/kataras/cli" "github.com/kataras/iris" - "github.com/kataras/iris/config" - "github.com/kataras/iris/logger" ) const ( @@ -49,7 +48,7 @@ func init() { app.Command(runAndWatchCmd) // init the logger - printer = logger.New(config.DefaultLogger()) + printer = logger.New(logger.DefaultConfig()) } func main() { diff --git a/logger/README.md b/logger/README.md deleted file mode 100644 index 50ad3565..00000000 --- a/logger/README.md +++ /dev/null @@ -1,6 +0,0 @@ -## Package information - -I decide to split the logger from the main iris package because logger.go doesn't depends on any of the iris' types. - - -**Examples and more info will be added soon.** diff --git a/logger/logger.go b/logger/logger.go deleted file mode 100644 index 23747fc0..00000000 --- a/logger/logger.go +++ /dev/null @@ -1,186 +0,0 @@ -package logger - -import ( - "os" - - "github.com/fatih/color" - "github.com/kataras/iris/config" - "github.com/mattn/go-colorable" -) - -var ( - // Prefix is the prefix for the logger, default is [IRIS] - Prefix = "[IRIS] " - // bannersRan keeps track of the logger's print banner count - bannersRan = 0 -) - -// Logger the logger -type Logger struct { - config *config.Logger - underline *color.Color -} - -// attr takes a color integer and converts it to color.Attribute -func attr(sgr int) color.Attribute { - return color.Attribute(sgr) -} - -// check if background color > 0 and if so then set it -func (l *Logger) setBg(sgr int) { - if sgr > 0 { - l.underline.Add(attr(sgr)) - } -} - -// New creates a new Logger from config.Logger configuration -func New(c config.Logger) *Logger { - color.Output = colorable.NewColorable(c.Out) - - l := &Logger{&c, color.New(attr(c.ColorFgDefault))} - l.setBg(c.ColorBgDefault) - - return l -} - -// SetEnable true enables, false disables the Logger -func (l *Logger) SetEnable(enable bool) { - l.config.Disabled = !enable -} - -// IsEnabled returns true if Logger is enabled, otherwise false -func (l *Logger) IsEnabled() bool { - return !l.config.Disabled -} - -// ResetColors sets the colors to the default -// this func is called every time a success, info, warning, or danger message is printed -func (l *Logger) ResetColors() { - l.underline.Add(attr(l.config.ColorFgDefault)) - l.setBg(l.config.ColorBgDefault) -} - -// PrintBanner prints a text (banner) with BannerFgColor, BannerBgColor and a success message at the end -// It doesn't cares if the logger is disabled or not, it will print this -func (l *Logger) PrintBanner(banner string, successMessage string) { - c := color.New(attr(l.config.ColorFgBanner)) - if l.config.ColorBgDefault > 0 { - c.Add(attr(l.config.ColorBgDefault)) - } - c.Println(banner) - bannersRan++ - - if successMessage != "" { - c.Add(attr(l.config.ColorFgSuccess)) - if l.config.ColorBgSuccess > 0 { - c.Add(attr(l.config.ColorBgSuccess)) - } - if bannersRan > 1 { - c.Printf("Server[%#v]\n", bannersRan) - - } - c.Println(successMessage) - } - - c.DisableColor() - c = nil -} - -// Printf calls l.Output to print to the logger. -// Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Printf(format string, a ...interface{}) { - if !l.config.Disabled { - l.underline.Printf(l.config.Prefix+format, a...) - } -} - -// Print calls l.Output to print to the logger. -// Arguments are handled in the manner of fmt.Print. -func (l *Logger) Print(a interface{}) { - if !l.config.Disabled { - l.ResetColors() - l.Printf("%#v", a) - } -} - -// Println calls l.Output to print to the logger. -// Arguments are handled in the manner of fmt.Println. -func (l *Logger) Println(a interface{}) { - if !l.config.Disabled { - l.Printf("%#v\n", a) - } -} - -// Fatal is equivalent to l.Dangerf("%#v",interface{}) followed by a call to panic(). -func (l *Logger) Fatal(a interface{}) { - l.Warningf("%#v", a) - panic("") -} - -// Fatalf is equivalent to l.Warningf() followed by a call to os.Exit(1). -func (l *Logger) Fatalf(format string, a ...interface{}) { - l.Warningf(format, a...) - os.Exit(1) -} - -// Panic is equivalent to l.Dangerf("%#v",interface{}) followed by a call to panic(). -func (l *Logger) Panic(a interface{}) { - l.Dangerf("%s\n", a) - panic(a) -} - -// Panicf is equivalent to l.Dangerf() followed by a call to panic(). -func (l *Logger) Panicf(format string, a ...interface{}) { - l.Dangerf(format, a...) - panic("") -} - -// Successf calls l.Output to print to the logger with the Success colors. -// Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Successf(format string, a ...interface{}) { - if !l.config.Disabled { - l.underline.Add(attr(l.config.ColorFgSuccess)) - l.setBg(l.config.ColorBgSuccess) - l.Printf(format, a...) - } -} - -// Infof calls l.Output to print to the logger with the Info colors. -// Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Infof(format string, a ...interface{}) { - if !l.config.Disabled { - l.underline.Add(attr(l.config.ColorFgInfo)) - l.setBg(l.config.ColorBgInfo) - l.Printf(format, a...) - } -} - -// Warningf calls l.Output to print to the logger with the Warning colors. -// Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Warningf(format string, a ...interface{}) { - if !l.config.Disabled { - l.underline.Add(attr(l.config.ColorFgWarning)) - l.setBg(l.config.ColorBgWarning) - l.Printf(format, a...) - } -} - -// Dangerf calls l.Output to print to the logger with the Danger colors. -// Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Dangerf(format string, a ...interface{}) { - if !l.config.Disabled { - l.underline.Add(attr(l.config.ColorFgDanger)) - l.setBg(l.config.ColorBgDanger) - l.Printf(format, a...) - } -} - -// Otherf calls l.Output to print to the logger with the Other colors. -// Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Otherf(format string, a ...interface{}) { - if !l.config.Disabled { - l.underline.Add(attr(l.config.ColorFgOther)) - l.setBg(l.config.ColorBgOther) - l.Printf(format, a...) - } -} diff --git a/plugin.go b/plugin.go index cb1ec492..94b5bc96 100644 --- a/plugin.go +++ b/plugin.go @@ -5,7 +5,7 @@ import ( "github.com/iris-contrib/errors" - "github.com/kataras/iris/logger" + "github.com/iris-contrib/logger" "github.com/kataras/iris/utils" ) diff --git a/render/rest/engine.go b/render/rest/engine.go deleted file mode 100644 index 9566dd99..00000000 --- a/render/rest/engine.go +++ /dev/null @@ -1,317 +0,0 @@ -package rest - -import ( - "bytes" - "encoding/json" - "encoding/xml" - - "github.com/klauspost/compress/gzip" - "github.com/valyala/fasthttp" -) - -// Engine is the generic interface for all responses. -type Engine interface { - Render(*fasthttp.RequestCtx, interface{}) error - //used only if config gzip is enabled - RenderGzip(*fasthttp.RequestCtx, interface{}) error -} - -// Head defines the basic ContentType and Status fields. -type Head struct { - ContentType string - Status int -} - -// Data built-in renderer. -type Data struct { - Head -} - -// JSON built-in renderer. -type JSON struct { - Head - Indent bool - UnEscapeHTML bool - Prefix []byte - StreamingJSON bool -} - -// JSONP built-in renderer. -type JSONP struct { - Head - Indent bool - Callback string -} - -// Text built-in renderer. -type Text struct { - Head -} - -// XML built-in renderer. -type XML struct { - Head - Indent bool - Prefix []byte -} - -// Write outputs the header content. -func (h Head) Write(ctx *fasthttp.RequestCtx) { - ctx.Response.Header.Set(ContentType, h.ContentType) - ctx.SetStatusCode(h.Status) -} - -// Render a data response. -func (d Data) Render(ctx *fasthttp.RequestCtx, v interface{}) error { - c := string(ctx.Request.Header.Peek(ContentType)) - w := ctx.Response.BodyWriter() - if c != "" { - d.Head.ContentType = c - } - - d.Head.Write(ctx) - w.Write(v.([]byte)) - return nil - -} - -// RenderGzip a data response using gzip compression. -func (d Data) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error { - c := string(ctx.Request.Header.Peek(ContentType)) - if c != "" { - d.Head.ContentType = c - } - - d.Head.Write(ctx) - _, err := fasthttp.WriteGzip(ctx.Response.BodyWriter(), v.([]byte)) - if err == nil { - ctx.Response.Header.Add("Content-Encoding", "gzip") - } - return err -} - -// Render a JSON response. -func (j JSON) Render(ctx *fasthttp.RequestCtx, v interface{}) error { - if j.StreamingJSON { - return j.renderStreamingJSON(ctx, v) - } - - var result []byte - var err error - - if j.Indent { - result, err = json.MarshalIndent(v, "", " ") - result = append(result, '\n') - } else { - result, err = json.Marshal(v) - } - if err != nil { - return err - } - - // Unescape HTML if needed. - if j.UnEscapeHTML { - result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1) - result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1) - result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1) - } - w := ctx.Response.BodyWriter() - // JSON marshaled fine, write out the result. - j.Head.Write(ctx) - if len(j.Prefix) > 0 { - w.Write(j.Prefix) - } - w.Write(result) - return nil -} - -// RenderGzip a JSON response using gzip compression. -func (j JSON) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error { - if j.StreamingJSON { - return j.renderStreamingJSONGzip(ctx, v) - } - - var result []byte - var err error - - if j.Indent { - result, err = json.MarshalIndent(v, "", " ") - result = append(result, '\n') - } else { - result, err = json.Marshal(v) - } - if err != nil { - return err - } - ctx.Response.Header.Add("Content-Encoding", "gzip") - - // Unescape HTML if needed. - if j.UnEscapeHTML { - result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1) - result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1) - result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1) - } - w := gzip.NewWriter(ctx.Response.BodyWriter()) - // JSON marshaled fine, write out the result. - j.Head.Write(ctx) - if len(j.Prefix) > 0 { - w.Write(j.Prefix) - } - w.Write(result) - w.Close() - return nil -} - -func (j JSON) renderStreamingJSON(ctx *fasthttp.RequestCtx, v interface{}) error { - j.Head.Write(ctx) - w := ctx.Response.BodyWriter() - if len(j.Prefix) > 0 { - w.Write(j.Prefix) - } - return json.NewEncoder(w).Encode(v) -} - -func (j JSON) renderStreamingJSONGzip(ctx *fasthttp.RequestCtx, v interface{}) error { - ctx.Response.Header.Add("Content-Encoding", "gzip") - j.Head.Write(ctx) - w := gzip.NewWriter(ctx.Response.BodyWriter()) - if len(j.Prefix) > 0 { - w.Write(j.Prefix) - } - w.Close() - return json.NewEncoder(w).Encode(v) -} - -// Render a JSONP response. -func (j JSONP) Render(ctx *fasthttp.RequestCtx, v interface{}) error { - var result []byte - var err error - - if j.Indent { - result, err = json.MarshalIndent(v, "", " ") - } else { - result, err = json.Marshal(v) - } - if err != nil { - return err - } - w := ctx.Response.BodyWriter() - - // JSON marshaled fine, write out the result. - j.Head.Write(ctx) - w.Write([]byte(j.Callback + "(")) - w.Write(result) - w.Write([]byte(");")) - - // If indenting, append a new line. - if j.Indent { - w.Write([]byte("\n")) - } - return nil -} - -// RenderGzip a JSONP response using gzip compression. -func (j JSONP) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error { - var result []byte - var err error - - if j.Indent { - result, err = json.MarshalIndent(v, "", " ") - } else { - result, err = json.Marshal(v) - } - if err != nil { - return err - } - w := gzip.NewWriter(ctx.Response.BodyWriter()) - - ctx.Response.Header.Add("Content-Encoding", "gzip") - // JSON marshaled fine, write out the result. - j.Head.Write(ctx) - w.Write([]byte(j.Callback + "(")) - w.Write(result) - w.Write([]byte(");")) - - // If indenting, append a new line. - if j.Indent { - w.Write([]byte("\n")) - } - w.Close() - return nil -} - -// Render a text response. -func (t Text) Render(ctx *fasthttp.RequestCtx, v interface{}) error { - c := string(ctx.Request.Header.Peek(ContentType)) - if c != "" { - t.Head.ContentType = c - } - w := ctx.Response.BodyWriter() - t.Head.Write(ctx) - w.Write([]byte(v.(string))) - return nil -} - -// RenderGzip a Text response using gzip compression. -func (t Text) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error { - c := string(ctx.Request.Header.Peek(ContentType)) - if c != "" { - t.Head.ContentType = c - } - ctx.Response.Header.Add("Content-Encoding", "gzip") - t.Head.Write(ctx) - fasthttp.WriteGzip(ctx.Response.BodyWriter(), []byte(v.(string))) - - return nil -} - -// Render an XML response. -func (x XML) Render(ctx *fasthttp.RequestCtx, v interface{}) error { - var result []byte - var err error - - if x.Indent { - result, err = xml.MarshalIndent(v, "", " ") - result = append(result, '\n') - } else { - result, err = xml.Marshal(v) - } - if err != nil { - return err - } - - // XML marshaled fine, write out the result. - x.Head.Write(ctx) - w := ctx.Response.BodyWriter() - if len(x.Prefix) > 0 { - w.Write(x.Prefix) - } - w.Write(result) - return nil -} - -// RenderGzip an XML response using gzip compression. -func (x XML) RenderGzip(ctx *fasthttp.RequestCtx, v interface{}) error { - var result []byte - var err error - - if x.Indent { - result, err = xml.MarshalIndent(v, "", " ") - result = append(result, '\n') - } else { - result, err = xml.Marshal(v) - } - if err != nil { - return err - } - ctx.Response.Header.Add("Content-Encoding", "gzip") - // XML marshaled fine, write out the result. - x.Head.Write(ctx) - w := gzip.NewWriter(ctx.Response.BodyWriter()) - if len(x.Prefix) > 0 { - w.Write(x.Prefix) - } - w.Write(result) - w.Close() - return nil -} diff --git a/render/rest/render.go b/render/rest/render.go deleted file mode 100644 index 9037574d..00000000 --- a/render/rest/render.go +++ /dev/null @@ -1,173 +0,0 @@ -// Package rest is an edited file of https://github.com/unrolled/render -package rest - -import ( - "github.com/kataras/iris/config" - "github.com/kataras/iris/utils" - "github.com/microcosm-cc/bluemonday" - "github.com/russross/blackfriday" - "github.com/valyala/fasthttp" -) - -const ( - // ContentBinary header value for binary data. - ContentBinary = "application/octet-stream" - // ContentJSON header value for JSON data. - ContentJSON = "application/json" - // ContentJSONP header value for JSONP data. - ContentJSONP = "application/javascript" - // ContentLength header constant. - ContentLength = "Content-Length" - // ContentText header value for Text data. - ContentText = "text/plain" - // ContentType header constant. - ContentType = "Content-Type" - // ContentXML header value for XML data. - ContentXML = "text/xml" -) - -// bufPool represents a reusable buffer pool for executing templates into. -var bufPool *utils.BufferPool - -// Render is a service that provides functions for easily writing JSON, XML, -// binary data, and HTML templates out to a HTTP Response. -type Render struct { - // Customize Secure with an Options struct. - Config config.Rest - CompiledCharset string -} - -// New constructs a new Render instance with the supplied configs. -func New(cfg ...config.Rest) *Render { - if bufPool == nil { - bufPool = utils.NewBufferPool(64) - } - - c := config.DefaultRest().Merge(cfg) - - r := &Render{ - Config: c, - } - - r.prepareConfig() - - return r -} - -func (r *Render) prepareConfig() { - // Fill in the defaults if need be. - if len(r.Config.Charset) == 0 { - r.Config.Charset = config.Charset - } - r.CompiledCharset = "; charset=" + r.Config.Charset -} - -// Render is the generic function called by XML, JSON, Data, HTML, and can be called by custom implementations. -func (r *Render) Render(ctx *fasthttp.RequestCtx, e Engine, data interface{}) error { - var err error - if r.Config.Gzip { - err = e.RenderGzip(ctx, data) - } else { - err = e.Render(ctx, data) - } - - if err != nil && !r.Config.DisableHTTPErrorRendering { - ctx.Response.SetBodyString(err.Error()) - ctx.Response.SetStatusCode(fasthttp.StatusInternalServerError) - } - return err -} - -// Data writes out the raw bytes as binary data. -func (r *Render) Data(ctx *fasthttp.RequestCtx, status int, v []byte) error { - head := Head{ - ContentType: ContentBinary, - Status: status, - } - - d := Data{ - Head: head, - } - - return r.Render(ctx, d, v) -} - -// JSON marshals the given interface object and writes the JSON response. -func (r *Render) JSON(ctx *fasthttp.RequestCtx, status int, v interface{}) error { - head := Head{ - ContentType: ContentJSON + r.CompiledCharset, - Status: status, - } - - j := JSON{ - Head: head, - Indent: r.Config.IndentJSON, - Prefix: r.Config.PrefixJSON, - UnEscapeHTML: r.Config.UnEscapeHTML, - StreamingJSON: r.Config.StreamingJSON, - } - - return r.Render(ctx, j, v) -} - -// JSONP marshals the given interface object and writes the JSON response. -func (r *Render) JSONP(ctx *fasthttp.RequestCtx, status int, callback string, v interface{}) error { - head := Head{ - ContentType: ContentJSONP + r.CompiledCharset, - Status: status, - } - - j := JSONP{ - Head: head, - Indent: r.Config.IndentJSON, - Callback: callback, - } - - return r.Render(ctx, j, v) -} - -// Text writes out a string as plain text. -func (r *Render) Text(ctx *fasthttp.RequestCtx, status int, v string) error { - head := Head{ - ContentType: ContentText + r.CompiledCharset, - Status: status, - } - - t := Text{ - Head: head, - } - - return r.Render(ctx, t, v) -} - -// XML marshals the given interface object and writes the XML response. -func (r *Render) XML(ctx *fasthttp.RequestCtx, status int, v interface{}) error { - head := Head{ - ContentType: ContentXML + r.CompiledCharset, - Status: status, - } - - x := XML{ - Head: head, - Indent: r.Config.IndentXML, - Prefix: r.Config.PrefixXML, - } - - return r.Render(ctx, x, v) -} - -// Markdown parses and returns the converted html from a markdown []byte -// accepts two parameters -// first is the http status code -// second is the markdown string -// -// Note that: Works different than the other rest's functions. -func (r *Render) Markdown(markdownBytes []byte) string { - buf := blackfriday.MarkdownCommon(markdownBytes) - if r.Config.MarkdownSanitize { - buf = bluemonday.UGCPolicy().SanitizeBytes(buf) - } - - return string(buf) - -} diff --git a/render/template/README.md b/render/template/README.md deleted file mode 100644 index 4fad6e8e..00000000 --- a/render/template/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Folder Information - -This folder contains the template support for Iris. The folder name is singular (template) so the `/template/engine`, because you can use **ONLY ONE** at the same time. - - -## How to use - -**Refer to the Book** diff --git a/render/template/engine/amber/amber.go b/render/template/engine/amber/amber.go deleted file mode 100644 index dea8033e..00000000 --- a/render/template/engine/amber/amber.go +++ /dev/null @@ -1,80 +0,0 @@ -package amber - -import ( - "html/template" - - "fmt" - "io" - "path/filepath" - "sync" - - "github.com/eknkc/amber" - "github.com/kataras/iris/config" -) - -// Engine the amber template engine -type Engine struct { - Config *config.Template - templateCache map[string]*template.Template - mu sync.Mutex -} - -// New creates and returns a new amber engine -func New(cfg config.Template) *Engine { - return &Engine{Config: &cfg} -} - -// BuildTemplates builds the amber templates -func (e *Engine) BuildTemplates() error { - opt := amber.DirOptions{} - opt.Recursive = true - if e.Config.Extensions == nil || len(e.Config.Extensions) == 0 { - e.Config.Extensions = []string{".html"} - } - - // prepare the global amber funcs - funcs := template.FuncMap{} - for k, v := range amber.FuncMap { // add the amber's default funcs - funcs[k] = v - } - if e.Config.Amber.Funcs != nil { // add the config's funcs - for k, v := range e.Config.Amber.Funcs { - funcs[k] = v - } - } - - amber.FuncMap = funcs //set the funcs - - opt.Ext = e.Config.Extensions[0] - templates, err := amber.CompileDir(e.Config.Directory, opt, amber.DefaultOptions) // this returns the map with stripped extension, we want extension so we copy the map - if err == nil { - e.templateCache = make(map[string]*template.Template) - for k, v := range templates { - name := filepath.ToSlash(k + opt.Ext) - e.templateCache[name] = v - delete(templates, k) - } - - } - return err - -} -func (e *Engine) fromCache(relativeName string) *template.Template { - e.mu.Lock() - tmpl, ok := e.templateCache[relativeName] - if ok { - e.mu.Unlock() - return tmpl - } - e.mu.Unlock() - return nil -} - -// ExecuteWriter executes a templates and write its results to the out writer -func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error { - if tmpl := e.fromCache(name); tmpl != nil { - return tmpl.ExecuteTemplate(out, name, binding) - } - - return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, e.Config.Directory) -} diff --git a/render/template/engine/handlebars/handlebars.go b/render/template/engine/handlebars/handlebars.go deleted file mode 100644 index e1ec963d..00000000 --- a/render/template/engine/handlebars/handlebars.go +++ /dev/null @@ -1,162 +0,0 @@ -// Package handlebars the HandlebarsEngine's functionality -package handlebars - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/aymerick/raymond" - "github.com/kataras/iris/config" -) - -type ( - // Engine the Handlebars engine - Engine struct { - Config *config.Template - templateCache map[string]*raymond.Template - mu sync.Mutex - } -) - -// New creates and returns the Handlebars template engine -func New(c config.Template) *Engine { - s := &Engine{Config: &c, templateCache: make(map[string]*raymond.Template, 0)} - return s -} - -// BuildTemplates builds the handlebars templates -func (e *Engine) BuildTemplates() error { - if e.Config.Extensions == nil || len(e.Config.Extensions) == 0 { - e.Config.Extensions = []string{".html"} - } - - // register the global helpers - if e.Config.Handlebars.Helpers != nil { - raymond.RegisterHelpers(e.Config.Handlebars.Helpers) - } - - // the render works like {{ render "myfile.html" theContext.PartialContext}} - // instead of the html/template engine which works like {{ render "myfile.html"}} and accepts the parent binding, with handlebars we can't do that because of lack of runtime helpers (dublicate error) - raymond.RegisterHelper("render", func(partial string, binding interface{}) raymond.SafeString { - contents, err := e.executeTemplateBuf(partial, binding) - if err != nil { - return raymond.SafeString("Template with name: " + partial + " couldn't not be found.") - } - return raymond.SafeString(contents) - }) - - var templateErr error - - dir := e.Config.Directory - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if info == nil || info.IsDir() { - return nil - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - return err - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = filepath.Ext(rel) - } - - for _, extension := range e.Config.Extensions { - if ext == extension { - - buf, err := ioutil.ReadFile(path) - contents := string(buf) - - if err != nil { - templateErr = err - break - } - - name := filepath.ToSlash(rel) - - tmpl, err := raymond.Parse(contents) - if err != nil { - templateErr = err - continue - } - e.mu.Lock() - e.templateCache[name] = tmpl - e.mu.Unlock() - - break - } - } - return nil - }) - - return templateErr - -} -func (e *Engine) fromCache(relativeName string) *raymond.Template { - e.mu.Lock() - tmpl, ok := e.templateCache[relativeName] - if ok { - e.mu.Unlock() - return tmpl - } - e.mu.Unlock() - return nil -} - -func (e *Engine) executeTemplateBuf(name string, binding interface{}) (string, error) { - if tmpl := e.fromCache(name); tmpl != nil { - return tmpl.Exec(binding) - } - return "", nil -} - -// ExecuteWriter executes a templates and write its results to the out writer -func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error { - - isLayout := false - - renderFilename := name - if layout != "" { - isLayout = true - renderFilename = layout // the render becomes the layout, and the name is the partial. - } - - if tmpl := e.fromCache(renderFilename); tmpl != nil { - if isLayout { - var context map[string]interface{} - if m, is := binding.(map[string]interface{}); is { //handlebars accepts maps, - context = m - } else { - return fmt.Errorf("Please provide a map[string]interface{} type as the binding instead of the %#v", binding) - } - - contents, err := e.executeTemplateBuf(name, binding) - if err != nil { - return err - } - if context == nil { - context = make(map[string]interface{}, 1) - } - // I'm implemented the {{ yield }} as with the rest of template engines, so this is not inneed for iris, but the user can do that manually if want - // there is no performanrce different: raymond.RegisterPartialTemplate(name, tmpl) - context["yield"] = raymond.SafeString(contents) - } - - res, err := tmpl.Exec(binding) - - if err != nil { - return err - } - _, err = fmt.Fprint(out, res) - return err - } - - return fmt.Errorf("[IRIS TEMPLATES] Template with name %s[original name = %s] doesn't exists in the dir %s", renderFilename, name, e.Config.Directory) -} diff --git a/render/template/engine/html/html.go b/render/template/engine/html/html.go deleted file mode 100644 index f1a4448f..00000000 --- a/render/template/engine/html/html.go +++ /dev/null @@ -1,245 +0,0 @@ -package html - -import ( - "bytes" - "fmt" - "html/template" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/Joker/jade" - "github.com/kataras/iris/config" -) - -type ( - // Engine the html/template engine & Jade - Engine struct { - Config *config.Template - Templates *template.Template - } -) - -var emptyFuncs = template.FuncMap{ - "yield": func() (string, error) { - return "", fmt.Errorf("yield was called, yet no layout defined") - }, - "partial": func() (string, error) { - return "", fmt.Errorf("block was called, yet no layout defined") - }, - "current": func() (string, error) { - return "", nil - }, "render": func() (string, error) { - return "", nil - }, -} - -// New creates and returns the HTMLTemplate template engine -func New(c config.Template) *Engine { - s := &Engine{Config: &c} - return s -} - -// BuildTemplates builds the templates -func (s *Engine) BuildTemplates() error { - - if s.Config.Asset == nil || s.Config.AssetNames == nil { - return s.buildFromDir() - - } - return s.buildFromAsset() - -} - -func (s *Engine) buildFromDir() error { - if s.Config.Directory == "" { - return nil //we don't return fill error here(yet) - } - - var templateErr error - /*var minifier *minify.M - if s.Config.Minify { - minifier = minify.New() - minifier.AddFunc("text/html", htmlMinifier.Minify) - } // Note: minifier has bugs, I complety remove this from Iris. - */ - dir := s.Config.Directory - s.Templates = template.New(dir) - s.Templates.Delims(s.Config.HTMLTemplate.Left, s.Config.HTMLTemplate.Right) - // Walk the supplied directory and compile any files that match our extension list. - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if info == nil || info.IsDir() { - return nil - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - return err - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = filepath.Ext(rel) - } - - for _, extension := range s.Config.Extensions { - if ext == extension { - - buf, err := ioutil.ReadFile(path) - contents := string(buf) - /*if s.Config.Minify { - buf, err = minifier.Bytes("text/html", buf) - }*/ - - if err != nil { - templateErr = err - break - } - - name := filepath.ToSlash(rel) - tmpl := s.Templates.New(name) - - if s.Config.Engine == config.JadeEngine { - contents, err = jade.Parse(name, contents) - } - - if err != nil { - templateErr = err - break - } - - // Add our funcmaps. - if s.Config.HTMLTemplate.Funcs != nil { - tmpl.Funcs(s.Config.HTMLTemplate.Funcs) - } - - tmpl.Funcs(emptyFuncs).Parse(contents) - break - } - } - return nil - }) - - return templateErr -} - -func (s *Engine) buildFromAsset() error { - var templateErr error - dir := s.Config.Directory - s.Templates = template.New(dir) - s.Templates.Delims(s.Config.HTMLTemplate.Left, s.Config.HTMLTemplate.Right) - - for _, path := range s.Config.AssetNames() { - if !strings.HasPrefix(path, dir) { - continue - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - panic(err) - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".") - } - - for _, extension := range s.Config.Extensions { - if ext == extension { - - buf, err := s.Config.Asset(path) - if err != nil { - panic(err) - } - contents := string(buf) - name := filepath.ToSlash(rel) - tmpl := s.Templates.New(name) - - if s.Config.Engine == config.JadeEngine { - contents, err = jade.Parse(name, contents) - } - if err != nil { - return err - } - - // Add our funcmaps. - if s.Config.HTMLTemplate.Funcs != nil { - tmpl.Funcs(s.Config.HTMLTemplate.Funcs) - } - - tmpl.Funcs(emptyFuncs).Parse(contents) - break - } - } - } - return templateErr -} - -func (s *Engine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) { - buf := new(bytes.Buffer) - err := s.Templates.ExecuteTemplate(buf, name, binding) - - return buf, err -} - -func (s *Engine) layoutFuncsFor(name string, binding interface{}) { - funcs := template.FuncMap{ - "yield": func() (template.HTML, error) { - buf, err := s.executeTemplateBuf(name, binding) - // Return safe HTML here since we are rendering our own template. - return template.HTML(buf.String()), err - }, - "current": func() (string, error) { - return name, nil - }, - "partial": func(partialName string) (template.HTML, error) { - fullPartialName := fmt.Sprintf("%s-%s", partialName, name) - if s.Config.HTMLTemplate.RequirePartials || s.Templates.Lookup(fullPartialName) != nil { - buf, err := s.executeTemplateBuf(fullPartialName, binding) - return template.HTML(buf.String()), err - } - return "", nil - }, - "render": func(fullPartialName string) (template.HTML, error) { - buf, err := s.executeTemplateBuf(fullPartialName, binding) - return template.HTML(buf.String()), err - }, - } - _userLayoutFuncs := s.Config.HTMLTemplate.LayoutFuncs - if _userLayoutFuncs != nil && len(_userLayoutFuncs) > 0 { - for k, v := range _userLayoutFuncs { - funcs[k] = v - } - } - if tpl := s.Templates.Lookup(name); tpl != nil { - tpl.Funcs(funcs) - } -} - -func (s *Engine) runtimeFuncsFor(name string, binding interface{}) { - funcs := template.FuncMap{ - "render": func(fullPartialName string) (template.HTML, error) { - buf, err := s.executeTemplateBuf(fullPartialName, binding) - return template.HTML(buf.String()), err - }, - } - - if tpl := s.Templates.Lookup(name); tpl != nil { - tpl.Funcs(funcs) - } -} - -// ExecuteWriter executes a templates and write its results to the out writer -func (s *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error { - if layout != "" { - s.layoutFuncsFor(name, binding) - name = layout - - } else { - s.runtimeFuncsFor(name, binding) - } - - return s.Templates.ExecuteTemplate(out, name, binding) -} diff --git a/render/template/engine/jade/jade.go b/render/template/engine/jade/jade.go deleted file mode 100644 index 67c8f4eb..00000000 --- a/render/template/engine/jade/jade.go +++ /dev/null @@ -1,15 +0,0 @@ -// Package jade the JadeEngine's functionality lives inside ../html now -package jade - -import ( - "github.com/kataras/iris/config" - "github.com/kataras/iris/render/template/engine/html" -) - -// New creates and returns the HTMLTemplate template engine -func New(c config.Template) *html.Engine { - // copy the Jade to the HTMLTemplate - c.HTMLTemplate = config.HTMLTemplate(c.Jade) - s := &html.Engine{Config: &c} - return s -} diff --git a/render/template/engine/markdown/markdown.go b/render/template/engine/markdown/markdown.go deleted file mode 100644 index 5726d43b..00000000 --- a/render/template/engine/markdown/markdown.go +++ /dev/null @@ -1,155 +0,0 @@ -package markdown - -import ( - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" - - "fmt" - - "github.com/kataras/iris/config" - "github.com/microcosm-cc/bluemonday" - "github.com/russross/blackfriday" -) - -// Supports RAW markdown only, no context binding or layout, to use dynamic markdown with other template engine use the context.Markdown/MarkdownString - -type ( - // Engine the jade engine - Engine struct { - Config *config.Template - templateCache map[string][]byte - mu sync.Mutex - } -) - -// New creates and returns a Pongo template engine -func New(c config.Template) *Engine { - return &Engine{Config: &c, templateCache: make(map[string][]byte)} -} - -// BuildTemplates builds the templates -func (e *Engine) BuildTemplates() error { - if e.Config.Asset == nil || e.Config.AssetNames == nil { - return e.buildFromDir() - } - return e.buildFromAsset() - -} - -func (e *Engine) buildFromDir() (templateErr error) { - if e.Config.Directory == "" { - return nil //we don't return fill error here(yet) - } - dir := e.Config.Directory - - // Walk the supplied directory and compile any files that match our extension list. - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - - if info == nil || info.IsDir() { - return nil - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - return err - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = filepath.Ext(rel) - } - - for _, extension := range e.Config.Extensions { - if ext == extension { - buf, err := ioutil.ReadFile(path) - if err != nil { - templateErr = err - break - } - - buf = blackfriday.MarkdownCommon(buf) - if e.Config.Markdown.Sanitize { - buf = bluemonday.UGCPolicy().SanitizeBytes(buf) - } - - if err != nil { - templateErr = err - break - } - name := filepath.ToSlash(rel) - e.templateCache[name] = buf - break - } - } - return nil - }) - - return nil -} - -func (e *Engine) buildFromAsset() error { - var templateErr error - dir := e.Config.Directory - for _, path := range e.Config.AssetNames() { - if !strings.HasPrefix(path, dir) { - continue - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - panic(err) - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".") - } - - for _, extension := range e.Config.Extensions { - if ext == extension { - - buf, err := e.Config.Asset(path) - if err != nil { - templateErr = err - break - } - b := blackfriday.MarkdownCommon(buf) - if e.Config.Markdown.Sanitize { - b = bluemonday.UGCPolicy().SanitizeBytes(b) - } - name := filepath.ToSlash(rel) - e.templateCache[name] = b - break - } - } - } - return templateErr -} - -func (e *Engine) fromCache(relativeName string) []byte { - e.mu.Lock() - - tmpl, ok := e.templateCache[relativeName] - - if ok { - e.mu.Unlock() // defer is slow - return tmpl - } - e.mu.Unlock() // defer is slow - return nil -} - -// ExecuteWriter executes a templates and write its results to the out writer -// layout here is useless -func (e *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error { - if tmpl := e.fromCache(name); tmpl != nil { - _, err := out.Write(tmpl) - return err - } - - return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, e.Config.Directory) -} diff --git a/render/template/engine/pongo/pongo.go b/render/template/engine/pongo/pongo.go deleted file mode 100644 index 113d14ed..00000000 --- a/render/template/engine/pongo/pongo.go +++ /dev/null @@ -1,183 +0,0 @@ -package pongo - -import ( - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" - - "fmt" - - "github.com/flosch/pongo2" - "github.com/kataras/iris/config" -) - -type ( - // Engine the pongo2 engine - Engine struct { - Config *config.Template - templateCache map[string]*pongo2.Template - mu sync.Mutex - } -) - -// New creates and returns a Pongo template engine -func New(c config.Template) *Engine { - return &Engine{Config: &c, templateCache: make(map[string]*pongo2.Template)} -} - -// BuildTemplates builds the templates -func (p *Engine) BuildTemplates() error { - // Add our filters. first - for k, v := range p.Config.Pongo.Filters { - pongo2.RegisterFilter(k, v) - } - if p.Config.Asset == nil || p.Config.AssetNames == nil { - return p.buildFromDir() - - } - return p.buildFromAsset() - -} - -func (p *Engine) buildFromDir() (templateErr error) { - if p.Config.Directory == "" { - return nil //we don't return fill error here(yet) - } - dir := p.Config.Directory - - fsLoader, err := pongo2.NewLocalFileSystemLoader(dir) // I see that this doesn't read the content if already parsed, so do it manually via filepath.Walk - if err != nil { - return err - } - - set := pongo2.NewSet("", fsLoader) - set.Globals = getPongoContext(p.Config.Pongo.Globals) - // Walk the supplied directory and compile any files that match our extension list. - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - // Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html". - // These dirs should be excluded as they are not valid golang templates, but files under - // them should be treat as normal. - // If is a dir, return immediately (dir is not a valid golang template). - if info == nil || info.IsDir() { - return nil - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - return err - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = filepath.Ext(rel) - } - - for _, extension := range p.Config.Extensions { - if ext == extension { - buf, err := ioutil.ReadFile(path) - if err != nil { - templateErr = err - break - } - if err != nil { - templateErr = err - break - } - name := filepath.ToSlash(rel) - p.templateCache[name], templateErr = set.FromString(string(buf)) - - if templateErr != nil { - return templateErr - } - break - } - } - return nil - }) - - return -} - -func (p *Engine) buildFromAsset() error { - var templateErr error - dir := p.Config.Directory - fsLoader, err := pongo2.NewLocalFileSystemLoader(dir) - if err != nil { - return err - } - set := pongo2.NewSet("", fsLoader) - set.Globals = getPongoContext(p.Config.Pongo.Globals) - for _, path := range p.Config.AssetNames() { - if !strings.HasPrefix(path, dir) { - continue - } - - rel, err := filepath.Rel(dir, path) - if err != nil { - panic(err) - } - - ext := "" - if strings.Index(rel, ".") != -1 { - ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".") - } - - for _, extension := range p.Config.Extensions { - if ext == extension { - - buf, err := p.Config.Asset(path) - if err != nil { - templateErr = err - break - } - name := filepath.ToSlash(rel) - p.templateCache[name], err = set.FromString(string(buf)) - if err != nil { - templateErr = err - break - } - break - } - } - } - return templateErr -} - -// getPongoContext returns the pongo2.Context from map[string]interface{} or from pongo2.Context, used internaly -func getPongoContext(templateData interface{}) pongo2.Context { - if templateData == nil { - return nil - } - - if contextData, isPongoContext := templateData.(pongo2.Context); isPongoContext { - return contextData - } - - return templateData.(map[string]interface{}) -} - -func (p *Engine) fromCache(relativeName string) *pongo2.Template { - p.mu.Lock() // defer is slow - - tmpl, ok := p.templateCache[relativeName] - - if ok { - p.mu.Unlock() - return tmpl - } - p.mu.Unlock() - return nil -} - -// ExecuteWriter executes a templates and write its results to the out writer -// layout here is useless -func (p *Engine) ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error { - if tmpl := p.fromCache(name); tmpl != nil { - return tmpl.ExecuteWriter(getPongoContext(binding), out) - } - - return fmt.Errorf("[IRIS TEMPLATES] Template with name %s doesn't exists in the dir %s", name, p.Config.Directory) -} diff --git a/render/template/template.go b/render/template/template.go deleted file mode 100644 index 644128e1..00000000 --- a/render/template/template.go +++ /dev/null @@ -1,231 +0,0 @@ -package template - -import ( - "fmt" - "io" - - "github.com/klauspost/compress/gzip" - - "sync" - - "github.com/kataras/iris/config" - "github.com/kataras/iris/context" - "github.com/kataras/iris/render/template/engine/amber" - "github.com/kataras/iris/render/template/engine/handlebars" - "github.com/kataras/iris/render/template/engine/html" - "github.com/kataras/iris/render/template/engine/jade" - "github.com/kataras/iris/render/template/engine/markdown" - "github.com/kataras/iris/render/template/engine/pongo" - "github.com/kataras/iris/utils" -) - -type ( - // Engine the interface that all template engines must inheritance - Engine interface { - // BuildTemplates builds the templates for a directory - BuildTemplates() error - // ExecuteWriter finds and execute a template and write its result to the out writer - ExecuteWriter(out io.Writer, name string, binding interface{}, layout string) error - } - - // Template the internal configs for the common configs for the template engines - Template struct { - // Engine the type of the Engine - Engine Engine - // Gzip enable gzip compression - // default is false - Gzip bool - // IsDevelopment re-builds the templates on each request - // default is false - IsDevelopment bool - // Directory the system path which the templates live - // default is ./templates - Directory string - // Extensions the allowed file extension - // default is []string{".html"} - Extensions []string - // ContentType is the Content-Type response header - // default is text/html but you can change if if needed - ContentType string - // Layout the template file ( with its extension) which is the mother of all - // use it to have it as a root file, and include others with {{ yield }}, refer the docs - Layout string - - buffer *utils.BufferPool // this is used only for RenderString - gzipWriterPool sync.Pool - } -) - -// sharedFuncs the funcs should be exists in all supported view template engines -var sharedFuncs map[string]interface{} - -// we do this because we don't want to override the user's funcs -func setSharedFuncs(source map[string]interface{}, target map[string]interface{}) { - if source == nil { - return - } - - if target == nil { - target = make(map[string]interface{}, len(source)) - } - - for k, v := range source { - if target[k] == nil { - target[k] = v - } - } -} - -// New creates and returns a Template instance which keeps the Template Engine and helps with render -func New(c config.Template) *Template { - defer func() { - sharedFuncs = nil - }() - - var e Engine - // [ENGINE-2] - switch c.Engine { - case config.HTMLEngine: - setSharedFuncs(sharedFuncs, c.HTMLTemplate.Funcs) - e = html.New(c) // HTMLTemplate - case config.JadeEngine: - setSharedFuncs(sharedFuncs, c.Jade.Funcs) - e = jade.New(c) // Jade - case config.PongoEngine: - setSharedFuncs(sharedFuncs, c.Pongo.Globals) - e = pongo.New(c) // Pongo2 - case config.MarkdownEngine: - e = markdown.New(c) // Markdown - case config.AmberEngine: - setSharedFuncs(sharedFuncs, c.Amber.Funcs) - e = amber.New(c) // Amber - case config.HandlebarsEngine: - setSharedFuncs(sharedFuncs, c.Handlebars.Helpers) - e = handlebars.New(c) - default: // config.NoEngine - return nil - } - - if err := e.BuildTemplates(); err != nil { // first build the templates, if error then panic because this is called before server's run - panic(err) - } - - compiledContentType := c.ContentType + "; charset=" + c.Charset - - t := &Template{ - Engine: e, - IsDevelopment: c.IsDevelopment, - Gzip: c.Gzip, - ContentType: compiledContentType, - Layout: c.Layout, - buffer: utils.NewBufferPool(64), - gzipWriterPool: sync.Pool{New: func() interface{} { - return &gzip.Writer{} - }}, - } - - return t - -} - -// RegisterSharedFunc registers a functionality that should be inherited from all supported template engines -func RegisterSharedFunc(name string, fn interface{}) { - if sharedFuncs == nil { - sharedFuncs = make(map[string]interface{}) - } - sharedFuncs[name] = fn -} - -// RegisterSharedFuncs registers functionalities that should be inherited from all supported template engines -func RegisterSharedFuncs(theFuncs map[string]interface{}) { - if sharedFuncs == nil || len(sharedFuncs) == 0 { - sharedFuncs = theFuncs - return - } - for k, v := range theFuncs { - sharedFuncs[k] = v - } - -} - -func (t *Template) getLayout(ctx context.IContext, passedLayouts []string) string { - - _layout := "" - if len(passedLayouts) > 0 { - _layout = passedLayouts[0] - } else if ctx != nil { - if layoutFromCtx := ctx.GetString(config.TemplateLayoutContextKey); layoutFromCtx != "" { - _layout = layoutFromCtx - } - } - if _layout == "" && _layout != config.NoLayout { - if t.Layout != config.NoLayout { // the config's Layout is disabled if "" , config.NoLayout should be passed only on ctx.Render but for any case check if user set it as config.NoLayout also - _layout = t.Layout - } - } - - return _layout -} - -// Render renders a template using the context's writer -func (t *Template) Render(ctx context.IContext, name string, binding interface{}, layout ...string) (err error) { - - if t == nil { // No engine was given but .Render was called - ctx.HTML(403, " Iris
Templates are disabled via config.NoEngine, check your iris' configuration please.") - return fmt.Errorf("[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n") - } - - // build templates again on each render if IsDevelopment. - if t.IsDevelopment { - if err = t.Engine.BuildTemplates(); err != nil { - return - } - } - - _layout := t.getLayout(ctx, layout) - - ctx.GetRequestCtx().Response.Header.Set("Content-Type", t.ContentType) - - var out io.Writer - if t.Gzip { - ctx.GetRequestCtx().Response.Header.Add("Content-Encoding", "gzip") - gzipWriter := t.gzipWriterPool.Get().(*gzip.Writer) - gzipWriter.Reset(ctx.GetRequestCtx().Response.BodyWriter()) - defer gzipWriter.Close() - defer t.gzipWriterPool.Put(gzipWriter) - out = gzipWriter - } else { - out = ctx.GetRequestCtx().Response.BodyWriter() - } - - err = t.Engine.ExecuteWriter(out, name, binding, _layout) - - return -} - -// RenderString executes a template and returns its contents result (string) -func (t *Template) RenderString(name string, binding interface{}, layout ...string) (result string, err error) { - - if t == nil { // No engine was given but .Render was called - err = fmt.Errorf("[IRIS TEMPLATES] Templates are disabled via config.NoEngine, check your iris' configuration please.\n") - return - } - - // build templates again on each render if IsDevelopment. - if t.IsDevelopment { - if err = t.Engine.BuildTemplates(); err != nil { - return - } - } - - _layout := t.getLayout(nil, layout) - - out := t.buffer.Get() - // if we have problems later consider that -> out.Reset() - defer t.buffer.Put(out) - err = t.Engine.ExecuteWriter(out, name, binding, _layout) - if err == nil { - result = out.String() - } - return -} diff --git a/template.go b/template.go new file mode 100644 index 00000000..9dbb4379 --- /dev/null +++ b/template.go @@ -0,0 +1,277 @@ +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 +} diff --git a/websocket/websocket.go b/websocket/websocket.go index dc7e31b9..71efaf55 100644 --- a/websocket/websocket.go +++ b/websocket/websocket.go @@ -1,9 +1,9 @@ package websocket import ( + "github.com/iris-contrib/logger" "github.com/kataras/iris/config" "github.com/kataras/iris/context" - "github.com/kataras/iris/logger" ) // to avoid the import cycle to /kataras/iris. The ws package is used inside iris' station configuration