Skip to content

Commit c9d0020

Browse files
authored
Merge pull request #49 from LiusCraft/feat/neste-template
feat: support nested templates
2 parents d30157e + b2081d7 commit c9d0020

File tree

10 files changed

+228
-23
lines changed

10 files changed

+228
-23
lines changed

context.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,13 @@ func (p *Context) JSON(code int, data interface{}) {
142142

143143
func (p *Context) YAP(code int, yapFile string, data interface{}) {
144144
w := p.ResponseWriter
145-
t, err := p.engine.templ(yapFile)
146-
if err != nil {
147-
log.Panicf("YAP `%s`: %v\n", yapFile, err)
145+
t := p.engine.templ(yapFile)
146+
if t == nil {
147+
log.Panicln("YAP: not find template:", yapFile)
148148
}
149149
h := w.Header()
150150
h.Set("Content-Type", "text/html")
151-
err = t.Execute(w, data)
151+
err := t.Execute(w, data)
152152
if err != nil {
153153
log.Panicln("YAP:", err)
154154
}

demo/blog_nestetemplate/blog.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/goplus/yap"
7+
)
8+
9+
func main() {
10+
y := yap.New(os.DirFS("."))
11+
12+
y.GET("/", func(ctx *yap.Context) {
13+
ctx.TEXT(200, "text/html", `<html><body>Hello, <a href="/p/123">YAP</a>!</body></html>`)
14+
})
15+
y.GET("/p/:id", func(ctx *yap.Context) {
16+
ctx.YAP(200, "blog", yap.H{
17+
"Id": ctx.Param("id"),
18+
})
19+
})
20+
y.Run(":8888")
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>neste-template</title>
7+
</head>
8+
<body>
9+
{{ template "header" }}
10+
<h1>neste-template</h1>
11+
<h3>{{ .Id }}</h3>
12+
</body>
13+
</html>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{ define "header"}}
2+
<h1>header</h1>
3+
{{ end}}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
get "/", ctx => {
2+
ctx.html `<html><body>Hello, <a href="/p/123">YAP</a>!</body></html>`
3+
}
4+
get "/p/:id", ctx => {
5+
ctx.yap "blog", {
6+
"Id": ctx.param("id"),
7+
}
8+
}
9+
10+
run ":8888"

demo/classfile_nestetemplate/gop_autogen.go

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>neste-template</title>
7+
</head>
8+
<body>
9+
{{ template "header" }}
10+
<h1>neste-template</h1>
11+
<h3>{{ .Id }}</h3>
12+
</body>
13+
</html>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{ define "header"}}
2+
<h1>header</h1>
3+
{{ end}}

template.go

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
package yap
1818

1919
import (
20+
"fmt"
2021
"html/template"
2122
"io/fs"
23+
"log"
24+
"os"
2225
"path/filepath"
26+
"strings"
2327

2428
"github.com/goplus/yap/internal/templ"
2529
)
@@ -32,8 +36,11 @@ type Template struct {
3236
}
3337

3438
// NewTemplate allocates a new, undefined template with the given name.
35-
func NewTemplate(name string) Template {
36-
return Template{template.New(name)}
39+
func NewTemplate(name string) *Template {
40+
return &Template{template.New(name)}
41+
}
42+
func (t *Template) NewTemplate(name string) *Template {
43+
return &Template{Template: t.Template.New(name)}
3744
}
3845

3946
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) {
4956
name := filepath.Base(file)
5057
return NewTemplate(name).Parse(string(b))
5158
}
59+
60+
func ParseFiles(filenames ...string) (*Template, error) {
61+
return parseFiles(nil, readFileOS, filenames...)
62+
}
63+
64+
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
65+
return parseFiles(t, readFileOS, filenames...)
66+
}
67+
68+
func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
69+
70+
if len(filenames) == 0 {
71+
return nil, fmt.Errorf("yap/template: no files named in call to ParseFiles")
72+
}
73+
for _, filename := range filenames {
74+
name, b, err := readFile(filename)
75+
if err != nil {
76+
return nil, err
77+
}
78+
s := string(b)
79+
var tmpl *Template
80+
if t == nil {
81+
t = NewTemplate(name)
82+
}
83+
if name == t.Name() {
84+
tmpl = t
85+
} else {
86+
tmpl = t.NewTemplate(name)
87+
}
88+
_, err = tmpl.Parse(s)
89+
if err != nil {
90+
return nil, err
91+
}
92+
}
93+
log.Println("yap/template list:")
94+
for i, t2 := range t.Templates() {
95+
log.Println(i, t2.Name())
96+
}
97+
return t, nil
98+
}
99+
100+
func ParseGlob(pattern string) (*Template, error) {
101+
return parseGlob(nil, pattern)
102+
}
103+
104+
func (t *Template) ParseGlob(pattern string) (*Template, error) {
105+
return parseGlob(t, pattern)
106+
}
107+
108+
func parseGlob(t *Template, pattern string) (*Template, error) {
109+
filenames, err := filepath.Glob(pattern)
110+
if err != nil {
111+
return nil, err
112+
}
113+
if len(filenames) == 0 {
114+
return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern)
115+
}
116+
return parseFiles(t, readFileOS, filenames...)
117+
}
118+
119+
// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
120+
// instead of the host operating system's file system.
121+
// It accepts a list of glob patterns.
122+
// (Note that most file names serve as glob patterns matching only themselves.)
123+
func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
124+
return parseFS(nil, fs, patterns)
125+
}
126+
127+
// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
128+
// instead of the host operating system's file system.
129+
// It accepts a list of glob patterns.
130+
// (Note that most file names serve as glob patterns matching only themselves.)
131+
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
132+
return parseFS(t, fs, patterns)
133+
}
134+
135+
func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
136+
var filenames []string
137+
for _, pattern := range patterns {
138+
list, err := fs.Glob(fsys, pattern)
139+
if err != nil {
140+
return nil, err
141+
}
142+
if len(list) == 0 {
143+
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
144+
}
145+
filenames = append(filenames, list...)
146+
}
147+
return parseFiles(t, readFileFS(fsys), filenames...)
148+
}
149+
150+
func readFileOS(file string) (name string, b []byte, err error) {
151+
name = filepath.Base(file)
152+
b, err = os.ReadFile(file)
153+
return
154+
}
155+
156+
func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
157+
return func(file string) (name string, b []byte, err error) {
158+
name = filepath.ToSlash(file)
159+
// compatible yap template name for older versions of yap, without the suffix "_ yap.html"
160+
name = strings.TrimSuffix(name, "_yap.html")
161+
b, err = fs.ReadFile(fsys, file)
162+
return
163+
}
164+
}

yap.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package yap
1818

1919
import (
20+
"html/template"
2021
"io/fs"
2122
"log"
2223
"net/http"
@@ -32,9 +33,9 @@ type Engine struct {
3233
router
3334
Mux *http.ServeMux
3435

35-
tpls map[string]Template
36-
fs fs.FS
37-
las func(addr string, handler http.Handler) error
36+
tpl *Template
37+
fs fs.FS
38+
las func(addr string, handler http.Handler) error
3839
}
3940

4041
// New creates a YAP engine.
@@ -65,7 +66,15 @@ func (p *Engine) initYapFS(fsys fs.FS) {
6566
}
6667
}
6768
p.fs = fsys
68-
p.tpls = make(map[string]Template)
69+
}
70+
71+
// Load template
72+
func (p *Engine) loadTemplate() {
73+
t, err := parseFS(NewTemplate(""), p.yapFS(), []string{"*_yap.html"})
74+
if err != nil {
75+
log.Panicln(err)
76+
}
77+
p.tpl = t
6978
}
7079

7180
func (p *Engine) yapFS() fs.FS {
@@ -154,20 +163,11 @@ func (p *Engine) SetLAS(listenAndServe func(addr string, handler http.Handler) e
154163
p.las = listenAndServe
155164
}
156165

157-
func (p *Engine) templ(path string) (t Template, err error) {
158-
fsys := p.yapFS()
159-
if p.tpls == nil {
160-
return Template{}, os.ErrNotExist
161-
}
162-
t, ok := p.tpls[path]
163-
if !ok {
164-
t, err = ParseFSFile(fsys, path+"_yap.html")
165-
if err != nil {
166-
return
167-
}
168-
p.tpls[path] = t
166+
func (p *Engine) templ(path string) *template.Template {
167+
if p.tpl == nil {
168+
p.loadTemplate()
169169
}
170-
return
170+
return p.tpl.Lookup(path)
171171
}
172172

173173
// SubFS returns a sub filesystem by specified a dir.

0 commit comments

Comments
 (0)