From 6844be57ea3a098ef28fce3468959bf5bb6f735a Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 3 Aug 2020 05:46:04 +0300 Subject: [PATCH] add 'iris.Ace' template engine: _examples/view/template_ace_0 --- HISTORY.md | 2 + NOTICE | 5 +- _examples/README.md | 1 + _examples/view/template_ace_0/main.go | 31 ++++++ _examples/view/template_ace_0/views/index.ace | 5 + .../template_ace_0/views/layouts/main.ace | 6 + .../template_ace_0/views/partials/footer.ace | 1 + .../template_ace_0/views/partials/header.ace | 1 + _examples/view/template_html_2/README.md | 2 +- .../view/template_html_2/templates/page1.html | 2 +- aliases.go | 3 + configuration.go | 4 +- go.mod | 5 +- view/README.md | 10 +- view/ace.go | 57 ++++++++++ view/amber.go | 2 +- view/django.go | 2 +- view/handlebars.go | 2 +- view/html.go | 105 ++++++++++++------ view/pug.go | 3 +- 20 files changed, 197 insertions(+), 52 deletions(-) create mode 100644 _examples/view/template_ace_0/main.go create mode 100644 _examples/view/template_ace_0/views/index.ace create mode 100644 _examples/view/template_ace_0/views/layouts/main.ace create mode 100644 _examples/view/template_ace_0/views/partials/footer.ace create mode 100644 _examples/view/template_ace_0/views/partials/header.ace create mode 100644 view/ace.go diff --git a/HISTORY.md b/HISTORY.md index 523ce10a..cbcc2789 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -359,6 +359,8 @@ Response: Other Improvements: +- Add [Ace](_examples/view/template_ace_0) template parser to the view engine and other minor improvements. + - Fix huge repo size of 55.7MB, which slows down the overall Iris installation experience. Now, go-get performs ~3 times faster. I 've managed it using the [bfg-repo-cleaner](https://github.com/rtyley/bfg-repo-cleaner) tool - an alternative to git-filter-branch command. Watch the small gif below to learn how: [![](https://media.giphy.com/media/U8560aiWTurW4iAOLn/giphy.gif)](https://media.giphy.com/media/U8560aiWTurW4iAOLn/giphy.gif) diff --git a/NOTICE b/NOTICE index 753c1e64..c7754fd4 100644 --- a/NOTICE +++ b/NOTICE @@ -10,7 +10,10 @@ Revision ID: d1c07411df0bb21f6b21f5b5d9325fac6f29c911 ----------------- ----------------- ------------------------------------------ Package Version Website ------------------ ----------------- ------------------------------------------ +----------------- ----------------- ------------------------------------------ + ace ea038f4770b6746 https://github.com/yosssi/ace + c3f8f84f14fa60d + 9fe1205b56 badger 536fed1846d0f4d https://github.com/dgraph-io/badger b9579bcff679761 4e134eadfa diff --git a/_examples/README.md b/_examples/README.md index b61c2b7f..1cafd46b 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -111,6 +111,7 @@ * [Jet Embedded](view/template_jet_1_embedded) * [Jet 'urlpath' tmpl func](view/template_jet_2) * [Jet Template Funcs from Struct](view/template_jet_3) + - [Ace](view/template_ace_0) * Third-Parties * [Render `valyala/quicktemplate` templates](view/quicktemplate) * [Render `shiyanhui/hero` templates](view/herotemplate) diff --git a/_examples/view/template_ace_0/main.go b/_examples/view/template_ace_0/main.go new file mode 100644 index 00000000..fcf6619b --- /dev/null +++ b/_examples/view/template_ace_0/main.go @@ -0,0 +1,31 @@ +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + + // Read about its markup syntax at: https://github.com/yosssi/ace + tmpl := iris.Ace("./views", ".ace") + // tmpl.Layout("layouts/main.ace") -> global layout for all pages. + + app.RegisterView(tmpl) + + app.Get("/", func(ctx iris.Context) { + ctx.View("index.ace", iris.Map{ + "Title": "Title of The Page", + }) + }) + + app.Get("/layout", func(ctx iris.Context) { + ctx.ViewLayout("layouts/main.ace") // layout for that response. + ctx.View("index.ace", iris.Map{ + "Title": "Title of the main Page", + }) + }) + + // otherGroup := app.Party("/other").Layout("layouts/other.ace") -> layout for that party. + // otherGroup.Get("/", func(ctx iris.Context) { ctx.View("index.ace", [...]) }) + + app.Listen(":8080") +} diff --git a/_examples/view/template_ace_0/views/index.ace b/_examples/view/template_ace_0/views/index.ace new file mode 100644 index 00000000..776cc11f --- /dev/null +++ b/_examples/view/template_ace_0/views/index.ace @@ -0,0 +1,5 @@ += include partials/header.ace . + +h2 {{.Title}} + += include partials/footer.ace . \ No newline at end of file diff --git a/_examples/view/template_ace_0/views/layouts/main.ace b/_examples/view/template_ace_0/views/layouts/main.ace new file mode 100644 index 00000000..b3997c7c --- /dev/null +++ b/_examples/view/template_ace_0/views/layouts/main.ace @@ -0,0 +1,6 @@ += doctype html +html + head + title Main Page + body + {{ yield }} \ No newline at end of file diff --git a/_examples/view/template_ace_0/views/partials/footer.ace b/_examples/view/template_ace_0/views/partials/footer.ace new file mode 100644 index 00000000..99261685 --- /dev/null +++ b/_examples/view/template_ace_0/views/partials/footer.ace @@ -0,0 +1 @@ +h1 Partial Footer \ No newline at end of file diff --git a/_examples/view/template_ace_0/views/partials/header.ace b/_examples/view/template_ace_0/views/partials/header.ace new file mode 100644 index 00000000..dca23ac2 --- /dev/null +++ b/_examples/view/template_ace_0/views/partials/header.ace @@ -0,0 +1 @@ +h1 Partial Header \ No newline at end of file diff --git a/_examples/view/template_html_2/README.md b/_examples/view/template_html_2/README.md index b987e79c..f4f5ac99 100644 --- a/_examples/view/template_html_2/README.md +++ b/_examples/view/template_html_2/README.md @@ -1,3 +1,3 @@ ## Info -This folder examines the {{render "dir/templatefilename"}} functionality to manually render any template inside any template +This folder examines the {{render "dir/templatefilename" .}} functionality to manually render any template inside any template diff --git a/_examples/view/template_html_2/templates/page1.html b/_examples/view/template_html_2/templates/page1.html index 22bd16a1..8c1d8ed6 100644 --- a/_examples/view/template_html_2/templates/page1.html +++ b/_examples/view/template_html_2/templates/page1.html @@ -2,6 +2,6 @@

Page 1 {{ greet "iris developer"}}

- {{ render "partials/page1_partial1.html"}} + {{ render "partials/page1_partial1.html" }} diff --git a/aliases.go b/aliases.go index d0126d80..57d2945a 100644 --- a/aliases.go +++ b/aliases.go @@ -207,6 +207,9 @@ var ( // Jet view engine. // Shortcut of the kataras/iris/view.Jet. Jet = view.Jet + // Ace view engine. + // Shortcut of the kataras/iris/view.Ace. + Ace = view.Ace ) // PrefixDir returns a new FileSystem that opens files diff --git a/configuration.go b/configuration.go index 596cb541..5906a309 100644 --- a/configuration.go +++ b/configuration.go @@ -81,7 +81,7 @@ func parseYAML(filename string) (Configuration, error) { // // Accepts the absolute path of the cfg.yml. // An error will be shown to the user via panic with the error message. -// Error may occur when the cfg.yml doesn't exists or is not formatted correctly. +// Error may occur when the cfg.yml does not exist or is not formatted correctly. // // Note: if the char '~' passed as "filename" then it tries to load and return // the configuration from the $home_directory + iris.yml, @@ -115,7 +115,7 @@ func YAML(filename string) Configuration { // // Accepts the absolute path of the configuration file. // An error will be shown to the user via panic with the error message. -// Error may occur when the file doesn't exists or is not formatted correctly. +// Error may occur when the file does not exist or is not formatted correctly. // // Note: if the char '~' passed as "filename" then it tries to load and return // the configuration from the $home_directory + iris.tml, diff --git a/go.mod b/go.mod index 9d92bccc..883aec67 100644 --- a/go.mod +++ b/go.mod @@ -33,10 +33,11 @@ require ( github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693 github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 + github.com/yosssi/ace v0.0.5 go.etcd.io/bbolt v1.3.5 - golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de golang.org/x/net v0.0.0-20200707034311-ab3426394381 - golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae + golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/protobuf v1.25.0 diff --git a/view/README.md b/view/README.md index 1617c714..0ff73293 100644 --- a/view/README.md +++ b/view/README.md @@ -1,7 +1,7 @@ # View -Iris supports 6 template engines out-of-the-box, developers can still use any external golang template engine, -as `context/context#ResponseWriter()` is an `io.Writer`. +Iris supports 7 template engines out-of-the-box, developers can still use any external golang template engine, +as `Context.ResponseWriter()` is an `io.Writer`. All of these six template engines have common features with common API, like Layout, Template Funcs, Party-specific layout, partial rendering and more. @@ -12,6 +12,7 @@ like Layout, Template Funcs, Party-specific layout, partial rendering and more. - Handlebars, its template parser is the [github.com/aymerick/raymond](https://github.com/aymerick/raymond) - Amber, its template parser is the [github.com/eknkc/amber](https://github.com/eknkc/amber) - Jet, its template parser is the [github.com/CloudyKit/jet](https://github.com/CloudyKit/jet) +- Ace, its template parser is the [github.com/yosssi/ace](https://github.com/yosssi/ace) ## Examples @@ -27,8 +28,9 @@ like Layout, Template Funcs, Party-specific layout, partial rendering and more. - [Pug (Jade) Actions`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_1) - [Pug (Jade) Includes`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_2) - [Pug (Jade) Extends`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_3) -- [Jet](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_0) **NEW** -- [Jet Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_1_embedded) **NEW** +- [Jet](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_0) +- [Jet Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_1_embedded) +- [Ace](https://github.com/kataras/iris/blob/master/_examples/view/template_ace_0) You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `context#ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example. diff --git a/view/ace.go b/view/ace.go new file mode 100644 index 00000000..134f2cf4 --- /dev/null +++ b/view/ace.go @@ -0,0 +1,57 @@ +package view + +import ( + "path" + "sync" + + "github.com/yosssi/ace" +) + +// Ace returns a new ace view engine. +// It shares the same exactly logic with the +// html view engine, it uses the same exactly configuration. +// +// Read more about the Ace Go Parser: https://github.com/yosssi/ace +func Ace(directory, extension string) *HTMLEngine { + s := HTML(directory, extension) + + funcs := make(map[string]interface{}, 0) + + once := new(sync.Once) + s.middleware = func(name string, text []byte) (contents string, err error) { + once.Do(func() { // on first template parse, all funcs are given. + for k, v := range s.funcs { + funcs[k] = v + } + for k, v := range emptyFuncs { + funcs[k] = v + } + }) + + name = path.Join(path.Clean(directory), name) + + src := ace.NewSource( + ace.NewFile(name, text), + ace.NewFile("", []byte{}), + []*ace.File{}, + ) + + rslt, err := ace.ParseSource(src, nil) + if err != nil { + return "", err + } + + t, err := ace.CompileResult(name, rslt, &ace.Options{ + Extension: extension[1:], + FuncMap: funcs, + DelimLeft: s.left, + DelimRight: s.right, + }) + if err != nil { + return "", err + } + + return t.Lookup(name).Tree.Root.String(), nil + } + return s +} diff --git a/view/amber.go b/view/amber.go index 05986dd1..c8a7fa7c 100644 --- a/view/amber.go +++ b/view/amber.go @@ -220,5 +220,5 @@ func (s *AmberEngine) ExecuteWriter(w io.Writer, filename string, layout string, return tmpl.Execute(w, bindingData) } - return fmt.Errorf("Template with name %s doesn't exists in the dir", filename) + return fmt.Errorf("Template with name: %s does not exist in the dir: %s", filename, s.directory) } diff --git a/view/django.go b/view/django.go index 81f85082..2732e86d 100644 --- a/view/django.go +++ b/view/django.go @@ -376,5 +376,5 @@ func (s *DjangoEngine) ExecuteWriter(w io.Writer, filename string, layout string return tmpl.ExecuteWriter(getPongoContext(bindingData), w) } - return fmt.Errorf("template with name %s doesn't exists in the dir", filename) + return fmt.Errorf("template with name: %s ddoes not exist in the dir: %s", filename, s.directory) } diff --git a/view/handlebars.go b/view/handlebars.go index 23003c1a..f1130b96 100644 --- a/view/handlebars.go +++ b/view/handlebars.go @@ -291,5 +291,5 @@ func (s *HandlebarsEngine) ExecuteWriter(w io.Writer, filename string, layout st return err } - return fmt.Errorf("template with name %s[original name = %s] doesn't exists in the dir", renderFilename, filename) + return fmt.Errorf("template with name: %s[original name = %s] does not exist in the dir: %s", renderFilename, filename, s.directory) } diff --git a/view/html.go b/view/html.go index c030ad7d..a2ad1fe3 100644 --- a/view/html.go +++ b/view/html.go @@ -26,8 +26,8 @@ type HTMLEngine struct { right string layout string rmu sync.RWMutex // locks for layoutFuncs and funcs - layoutFuncs map[string]interface{} - funcs map[string]interface{} + layoutFuncs template.FuncMap + funcs template.FuncMap // middleware func(name string, contents []byte) (string, error) @@ -38,7 +38,7 @@ type HTMLEngine struct { var _ Engine = (*HTMLEngine)(nil) var emptyFuncs = template.FuncMap{ - "yield": func() (string, error) { + "yield": func(binding interface{}) (string, error) { return "", fmt.Errorf("yield was called, yet no layout defined") }, "part": func() (string, error) { @@ -52,7 +52,8 @@ var emptyFuncs = template.FuncMap{ }, "current": func() (string, error) { return "", nil - }, "render": func() (string, error) { + }, + "render": func() (string, error) { return "", nil }, } @@ -70,8 +71,8 @@ func HTML(directory, extension string) *HTMLEngine { left: "{{", right: "}}", layout: "", - layoutFuncs: make(map[string]interface{}), - funcs: make(map[string]interface{}), + layoutFuncs: make(template.FuncMap), + funcs: make(template.FuncMap), } return s @@ -172,10 +173,34 @@ func (s *HTMLEngine) AddLayoutFunc(funcName string, funcBody interface{}) *HTMLE // - url func(routeName string, args ...string) string // - urlpath func(routeName string, args ...string) string // - render func(fullPartialName string) (template.HTML, error). -func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) { +func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) *HTMLEngine { s.rmu.Lock() s.funcs[funcName] = funcBody s.rmu.Unlock() + + return s +} + +// SetFuncs overrides the template funcs with the given "funcMap". +func (s *HTMLEngine) SetFuncs(funcMap template.FuncMap) *HTMLEngine { + s.rmu.Lock() + s.funcs = funcMap + s.rmu.Unlock() + + return s +} + +// Funcs adds the elements of the argument map to the template's function map. +// It is legal to overwrite elements of the map. The return +// value is the template, so calls can be chained. +func (s *HTMLEngine) Funcs(funcMap template.FuncMap) *HTMLEngine { + s.rmu.Lock() + for k, v := range funcMap { + s.funcs[k] = v + } + s.rmu.Unlock() + + return s } // Load parses the templates to the engine. @@ -266,6 +291,7 @@ func (s *HTMLEngine) loadDirectory() error { name := filepath.ToSlash(rel) tmpl := s.Templates.New(name) tmpl.Option(s.options...) + if s.middleware != nil { contents, err = s.middleware(name, buf) } @@ -275,7 +301,12 @@ func (s *HTMLEngine) loadDirectory() error { } // s.mu.Lock() // Add our funcmaps. - _, err = tmpl.Funcs(emptyFuncs).Funcs(s.funcs).Parse(contents) + _, err = tmpl. + Funcs(emptyFuncs). + // Funcs(s.makeDefaultLayoutFuncs(name)). + // Funcs(s.layoutFuncs). + Funcs(s.funcs). + Parse(contents) // s.mu.Unlock() if err != nil { templateErr = err @@ -393,15 +424,28 @@ func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (*byte return buf, err } -func (s *HTMLEngine) layoutFuncsFor(name string, binding interface{}) { +func (s *HTMLEngine) layoutFuncsFor(lt *template.Template, name string, binding interface{}) { + s.runtimeFuncsFor(lt, name, binding) + 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 }, + } + + for k, v := range s.layoutFuncs { + funcs[k] = v + } + + lt.Funcs(funcs) +} + +func (s *HTMLEngine) runtimeFuncsFor(t *template.Template, name string, binding interface{}) { + funcs := template.FuncMap{ "part": func(partName string) (template.HTML, error) { - nameTemp := strings.Replace(name, ".html", "", -1) + nameTemp := strings.Replace(name, s.extension, "", -1) fullPartName := fmt.Sprintf("%s-%s", nameTemp, partName) buf, err := s.executeTemplateBuf(fullPartName, binding) if err != nil { @@ -440,25 +484,7 @@ func (s *HTMLEngine) layoutFuncsFor(name string, binding interface{}) { }, } - for k, v := range s.layoutFuncs { - funcs[k] = v - } - if tpl := s.Templates.Lookup(name); tpl != nil { - tpl.Funcs(funcs) - } -} - -func (s *HTMLEngine) 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) - } + t.Funcs(funcs) } // ExecuteWriter executes a template and writes its result to the w writer. @@ -474,14 +500,21 @@ func (s *HTMLEngine) ExecuteWriter(w io.Writer, name string, layout string, bind } } - layout = getLayout(layout, s.layout) + if layout = getLayout(layout, s.layout); layout != "" { + lt := s.Templates.Lookup(layout) + if lt == nil { + return fmt.Errorf("layout: %s does not exist in the dir: %s", name, s.directory) + } - if layout != "" { - s.layoutFuncsFor(name, bindingData) - name = layout - } else { - s.runtimeFuncsFor(name, bindingData) + s.layoutFuncsFor(lt, name, bindingData) + return lt.Execute(w, bindingData) } - return s.Templates.ExecuteTemplate(w, name, bindingData) + t := s.Templates.Lookup(name) + if t == nil { + return fmt.Errorf("template: %s does not exist in the dir: %s", name, s.directory) + } + s.runtimeFuncsFor(t, name, bindingData) + + return t.Execute(w, bindingData) } diff --git a/view/pug.go b/view/pug.go index 0a20aec2..f317efe4 100644 --- a/view/pug.go +++ b/view/pug.go @@ -12,8 +12,7 @@ import ( // Pug (or Jade) returns a new pug view engine. // It shares the same exactly logic with the // html view engine, it uses the same exactly configuration. -// It has got some features and a lot of functions -// which will make your life easier. +// // Read more about the Jade Go Parser: https://github.com/Joker/jade // // Examples: