diff --git a/_examples/README.md b/_examples/README.md index 5ac8802a..6c7346a3 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -110,6 +110,7 @@ * [Inject Engine Between Handlers](view/context-view-engine/main.go) * [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go) * [Write to a custom `io.Writer`](view/write-to) + * [Parse a Template manually](view/parse-template/main.go) * [Blocks](view/template_blocks_0) * [Blocks Embedded](view/template_blocks_1_embedded) * [Pug: `Actions`](view/template_pug_0) diff --git a/_examples/view/parse-template/main.go b/_examples/view/parse-template/main.go new file mode 100644 index 00000000..bfddf399 --- /dev/null +++ b/_examples/view/parse-template/main.go @@ -0,0 +1,35 @@ +// Package main shows how to parse a template through custom byte slice content. +package main + +import "github.com/kataras/iris/v12" + +func main() { + app := iris.New() + // To not load any templates from files or embedded data, + // pass nil or empty string on the first argument: + // view := iris.HTML(nil, ".html") + + view := iris.HTML("./views", ".html") + view.ParseTemplate("program.html", []byte(`

{{greet .Name}}

`), iris.Map{ + "greet": func(name string) string { + return "Hello, " + name + "!" + }, + }) + + app.RegisterView(view) + app.Get("/", index) + app.Get("/layout", layout) + + app.Listen(":8080") +} + +func index(ctx iris.Context) { + ctx.View("program.html", iris.Map{ + "Name": "Gerasimos", + }) +} + +func layout(ctx iris.Context) { + ctx.ViewLayout("layouts/main.html") + index(ctx) +} diff --git a/_examples/view/parse-template/views/layouts/main.html b/_examples/view/parse-template/views/layouts/main.html new file mode 100644 index 00000000..3f6b65a6 --- /dev/null +++ b/_examples/view/parse-template/views/layouts/main.html @@ -0,0 +1,11 @@ + + +My Layout + + + +

[layout] Body content is below...

+ + {{ yield }} + + diff --git a/view/fs.go b/view/fs.go index 0732724b..8c7b13e0 100644 --- a/view/fs.go +++ b/view/fs.go @@ -49,6 +49,10 @@ func assetNames(fs http.FileSystem, name string) ([]string, error) { return nil, err } + if f == nil { + return nil, nil + } + infos, err := f.Readdir(-1) f.Close() if err != nil { @@ -82,9 +86,17 @@ func asset(fs http.FileSystem, name string) ([]byte, error) { } func getFS(fsOrDir interface{}) (fs http.FileSystem) { + if fsOrDir == nil { + return noOpFS{} + } + switch v := fsOrDir.(type) { case string: - fs = httpDirWrapper{http.Dir(v)} + if v == "" { + fs = noOpFS{} + } else { + fs = httpDirWrapper{http.Dir(v)} + } case http.FileSystem: fs = v default: @@ -94,6 +106,10 @@ func getFS(fsOrDir interface{}) (fs http.FileSystem) { return } +type noOpFS struct{} + +func (fs noOpFS) Open(name string) (http.File, error) { return nil, nil } + // fixes: "invalid character in file path" // on amber engine (it uses the virtual fs directly // and it uses filepath instead of the path package...). @@ -101,6 +117,6 @@ type httpDirWrapper struct { http.Dir } -func (d httpDirWrapper) Open(name string) (http.File, error) { - return d.Dir.Open(filepath.ToSlash(name)) +func (fs httpDirWrapper) Open(name string) (http.File, error) { + return fs.Dir.Open(filepath.ToSlash(name)) } diff --git a/view/html.go b/view/html.go index 63be0fb1..d00b9601 100644 --- a/view/html.go +++ b/view/html.go @@ -215,12 +215,6 @@ func (s *HTMLEngine) Funcs(funcMap template.FuncMap) *HTMLEngine { // // Returns an error if something bad happens, caller is responsible to handle that. func (s *HTMLEngine) Load() error { - s.rmu.Lock() - defer s.rmu.Unlock() - - s.Templates = template.New(s.rootDir) - s.Templates.Delims(s.left, s.right) - return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, err error) error { if info == nil || info.IsDir() { return nil @@ -236,30 +230,51 @@ func (s *HTMLEngine) Load() error { if err != nil { return fmt.Errorf("%s: %w", path, err) } - contents := string(buf) - name := strings.TrimPrefix(path, "/") - tmpl := s.Templates.New(name) - tmpl.Option(s.options...) - - if s.middleware != nil { - contents, err = s.middleware(name, buf) - if err != nil { - return err - } - } - - // Add our funcmaps. - _, err = tmpl. - Funcs(emptyFuncs). - // Funcs(s.makeDefaultLayoutFuncs(name)). - // Funcs(s.layoutFuncs). - Funcs(s.funcs). - Parse(contents) - return err + return s.ParseTemplate(path, buf, nil) }) } +func (s *HTMLEngine) initRootTmpl() { // protected by the caller. + if s.Templates == nil { + // the root template should be the same, + // no matter how many reloads as the + // following unexported fields cannot be modified. + s.Templates = template.New(s.rootDir) + s.Templates.Delims(s.left, s.right) + } +} + +// ParseTemplate adds a custom template to the root template. +func (s *HTMLEngine) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) (err error) { + s.rmu.Lock() + defer s.rmu.Unlock() + + s.initRootTmpl() + + name = strings.TrimPrefix(name, "/") + tmpl := s.Templates.New(name) + tmpl.Option(s.options...) + + var text string + + if s.middleware != nil { + text, err = s.middleware(name, contents) + if err != nil { + return + } + } else { + text = string(contents) + } + + tmpl.Funcs(emptyFuncs).Funcs(s.funcs) + if len(funcMap) > 0 { + tmpl.Funcs(funcMap) // custom for this template. + } + _, err = tmpl.Parse(text) + return +} + func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) { buf := new(bytes.Buffer) err := s.Templates.ExecuteTemplate(buf, name, binding)