diff --git a/context.go b/context.go index ef5c45b..770ba41 100644 --- a/context.go +++ b/context.go @@ -142,13 +142,13 @@ func (p *Context) JSON(code int, data interface{}) { func (p *Context) YAP(code int, yapFile string, data interface{}) { w := p.ResponseWriter - t, err := p.engine.templ(yapFile) - if err != nil { - log.Panicf("YAP `%s`: %v\n", yapFile, err) + t := p.engine.templ(yapFile) + if t == nil { + log.Panicln("YAP: not find template:", yapFile) } h := w.Header() h.Set("Content-Type", "text/html") - err = t.Execute(w, data) + err := t.Execute(w, data) if err != nil { log.Panicln("YAP:", err) } diff --git a/demo/blog_nestetemplate/blog.go b/demo/blog_nestetemplate/blog.go new file mode 100644 index 0000000..dd6ed3a --- /dev/null +++ b/demo/blog_nestetemplate/blog.go @@ -0,0 +1,21 @@ +package main + +import ( + "os" + + "github.com/goplus/yap" +) + +func main() { + y := yap.New(os.DirFS(".")) + + y.GET("/", func(ctx *yap.Context) { + ctx.TEXT(200, "text/html", `Hello, YAP!`) + }) + y.GET("/p/:id", func(ctx *yap.Context) { + ctx.YAP(200, "blog", yap.H{ + "Id": ctx.Param("id"), + }) + }) + y.Run(":8888") +} diff --git a/demo/blog_nestetemplate/yap/blog_yap.html b/demo/blog_nestetemplate/yap/blog_yap.html new file mode 100644 index 0000000..d280926 --- /dev/null +++ b/demo/blog_nestetemplate/yap/blog_yap.html @@ -0,0 +1,13 @@ + + + + + + neste-template + + + {{ template "header" }} +

neste-template

+

{{ .Id }}

+ + \ No newline at end of file diff --git a/demo/blog_nestetemplate/yap/layout_yap.html b/demo/blog_nestetemplate/yap/layout_yap.html new file mode 100644 index 0000000..79aa9d3 --- /dev/null +++ b/demo/blog_nestetemplate/yap/layout_yap.html @@ -0,0 +1,3 @@ +{{ define "header"}} +

header

+{{ end}} \ No newline at end of file diff --git a/demo/classfile_nestetemplate/blog_yap.gox b/demo/classfile_nestetemplate/blog_yap.gox new file mode 100644 index 0000000..19d4464 --- /dev/null +++ b/demo/classfile_nestetemplate/blog_yap.gox @@ -0,0 +1,10 @@ +get "/", ctx => { + ctx.html `Hello, YAP!` +} +get "/p/:id", ctx => { + ctx.yap "blog", { + "Id": ctx.param("id"), + } +} + +run ":8888" diff --git a/demo/classfile_nestetemplate/gop_autogen.go b/demo/classfile_nestetemplate/gop_autogen.go new file mode 100644 index 0000000..9bea1c4 --- /dev/null +++ b/demo/classfile_nestetemplate/gop_autogen.go @@ -0,0 +1,29 @@ +// Code generated by gop (Go+); DO NOT EDIT. + +package main + +import "github.com/goplus/yap" + +const _ = true + +type blog struct { + yap.App +} +//line demo/classfile_nestetemplate/blog_yap.gox:1 +func (this *blog) MainEntry() { +//line demo/classfile_nestetemplate/blog_yap.gox:1:1 + this.Get("/", func(ctx *yap.Context) { +//line demo/classfile_nestetemplate/blog_yap.gox:2:1 + ctx.Html__1(`Hello, YAP!`) + }) +//line demo/classfile_nestetemplate/blog_yap.gox:4:1 + this.Get("/p/:id", func(ctx *yap.Context) { +//line demo/classfile_nestetemplate/blog_yap.gox:5:1 + ctx.Yap__1("blog", map[string]string{"Id": ctx.Param("id")}) + }) +//line demo/classfile_nestetemplate/blog_yap.gox:10:1 + this.Run(":8888") +} +func main() { + yap.Gopt_App_Main(new(blog)) +} diff --git a/demo/classfile_nestetemplate/yap/blog_yap.html b/demo/classfile_nestetemplate/yap/blog_yap.html new file mode 100644 index 0000000..d280926 --- /dev/null +++ b/demo/classfile_nestetemplate/yap/blog_yap.html @@ -0,0 +1,13 @@ + + + + + + neste-template + + + {{ template "header" }} +

neste-template

+

{{ .Id }}

+ + \ No newline at end of file diff --git a/demo/classfile_nestetemplate/yap/layout_yap.html b/demo/classfile_nestetemplate/yap/layout_yap.html new file mode 100644 index 0000000..79aa9d3 --- /dev/null +++ b/demo/classfile_nestetemplate/yap/layout_yap.html @@ -0,0 +1,3 @@ +{{ define "header"}} +

header

+{{ end}} \ No newline at end of file diff --git a/template.go b/template.go index 41c95c1..883358f 100644 --- a/template.go +++ b/template.go @@ -17,9 +17,13 @@ package yap import ( + "fmt" "html/template" "io/fs" + "log" + "os" "path/filepath" + "strings" "github.com/goplus/yap/internal/templ" ) @@ -32,8 +36,11 @@ type Template struct { } // NewTemplate allocates a new, undefined template with the given name. -func NewTemplate(name string) Template { - return Template{template.New(name)} +func NewTemplate(name string) *Template { + return &Template{template.New(name)} +} +func (t *Template) NewTemplate(name string) *Template { + return &Template{Template: t.Template.New(name)} } func (t Template) Parse(text string) (ret Template, err error) { @@ -49,3 +56,109 @@ func ParseFSFile(f fs.FS, file string) (t Template, err error) { name := filepath.Base(file) return NewTemplate(name).Parse(string(b)) } + +func ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(nil, readFileOS, filenames...) +} + +func (t *Template) ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(t, readFileOS, filenames...) +} + +func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) { + + if len(filenames) == 0 { + return nil, fmt.Errorf("yap/template: no files named in call to ParseFiles") + } + for _, filename := range filenames { + name, b, err := readFile(filename) + if err != nil { + return nil, err + } + s := string(b) + var tmpl *Template + if t == nil { + t = NewTemplate(name) + } + if name == t.Name() { + tmpl = t + } else { + tmpl = t.NewTemplate(name) + } + _, err = tmpl.Parse(s) + if err != nil { + return nil, err + } + } + log.Println("yap/template list:") + for i, t2 := range t.Templates() { + log.Println(i, t2.Name()) + } + return t, nil +} + +func ParseGlob(pattern string) (*Template, error) { + return parseGlob(nil, pattern) +} + +func (t *Template) ParseGlob(pattern string) (*Template, error) { + return parseGlob(t, pattern) +} + +func parseGlob(t *Template, pattern string) (*Template, error) { + filenames, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + if len(filenames) == 0 { + return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern) + } + return parseFiles(t, readFileOS, filenames...) +} + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// instead of the host operating system's file system. +// It accepts a list of glob patterns. +// (Note that most file names serve as glob patterns matching only themselves.) +func ParseFS(fs fs.FS, patterns ...string) (*Template, error) { + return parseFS(nil, fs, patterns) +} + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// instead of the host operating system's file system. +// It accepts a list of glob patterns. +// (Note that most file names serve as glob patterns matching only themselves.) +func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error) { + return parseFS(t, fs, patterns) +} + +func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) { + var filenames []string + for _, pattern := range patterns { + list, err := fs.Glob(fsys, pattern) + if err != nil { + return nil, err + } + if len(list) == 0 { + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) + } + filenames = append(filenames, list...) + } + return parseFiles(t, readFileFS(fsys), filenames...) +} + +func readFileOS(file string) (name string, b []byte, err error) { + name = filepath.Base(file) + b, err = os.ReadFile(file) + return +} + +func readFileFS(fsys fs.FS) func(string) (string, []byte, error) { + return func(file string) (name string, b []byte, err error) { + name = filepath.ToSlash(file) + // compatible yap template name for older versions of yap, without the suffix "_ yap.html" + name = strings.TrimSuffix(name, "_yap.html") + b, err = fs.ReadFile(fsys, file) + return + } +} diff --git a/yap.go b/yap.go index 9380086..64eb3e8 100644 --- a/yap.go +++ b/yap.go @@ -17,6 +17,7 @@ package yap import ( + "html/template" "io/fs" "log" "net/http" @@ -32,9 +33,9 @@ type Engine struct { router Mux *http.ServeMux - tpls map[string]Template - fs fs.FS - las func(addr string, handler http.Handler) error + tpl *Template + fs fs.FS + las func(addr string, handler http.Handler) error } // New creates a YAP engine. @@ -65,7 +66,15 @@ func (p *Engine) initYapFS(fsys fs.FS) { } } p.fs = fsys - p.tpls = make(map[string]Template) +} + +// Load template +func (p *Engine) loadTemplate() { + t, err := parseFS(NewTemplate(""), p.yapFS(), []string{"*_yap.html"}) + if err != nil { + log.Panicln(err) + } + p.tpl = t } func (p *Engine) yapFS() fs.FS { @@ -154,20 +163,11 @@ func (p *Engine) SetLAS(listenAndServe func(addr string, handler http.Handler) e p.las = listenAndServe } -func (p *Engine) templ(path string) (t Template, err error) { - fsys := p.yapFS() - if p.tpls == nil { - return Template{}, os.ErrNotExist - } - t, ok := p.tpls[path] - if !ok { - t, err = ParseFSFile(fsys, path+"_yap.html") - if err != nil { - return - } - p.tpls[path] = t +func (p *Engine) templ(path string) *template.Template { + if p.tpl == nil { + p.loadTemplate() } - return + return p.tpl.Lookup(path) } // SubFS returns a sub filesystem by specified a dir.