diff --git a/demo/classfile_nestetemplate/gop_autogen.go b/demo/classfile_nestetemplate/gop_autogen.go index 9bea1c4..c2a1ec9 100644 --- a/demo/classfile_nestetemplate/gop_autogen.go +++ b/demo/classfile_nestetemplate/gop_autogen.go @@ -24,6 +24,9 @@ func (this *blog) MainEntry() { //line demo/classfile_nestetemplate/blog_yap.gox:10:1 this.Run(":8888") } +func (this *blog) Main() { + yap.Gopt_App_Main(this) +} func main() { - yap.Gopt_App_Main(new(blog)) + new(blog).Main() } diff --git a/internal/htmltempl/template.go b/internal/htmltempl/template.go new file mode 100644 index 0000000..93e463c --- /dev/null +++ b/internal/htmltempl/template.go @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package htmltempl + +import ( + "bytes" + "fmt" + "html/template" + "io/fs" + "log" + "path" + "strings" + _ "unsafe" + + "github.com/goplus/yap/internal/templ" +) + +// Template is the representation of a parsed template. The *parse.Tree +// field is exported only for use by html/template and should be treated +// as unexported by all other clients. +type Template struct { + *template.Template +} + +func (p *Template) InitTemplates(fsys fs.FS, delimLeft, delimRight, suffix string) { + tpl, err := parseFS(fsys, delimLeft, delimRight, suffix) + if err != nil { + log.Panicln(err) + } + p.Template = tpl +} + +//go:linkname parseFiles html/template.parseFiles +func parseFiles(t *template.Template, readFile func(string) (string, []byte, error), filenames ...string) (*template.Template, error) + +func parseFS(fsys fs.FS, delimLeft, delimRight, suffix string) (*template.Template, error) { + pattern := "*" + suffix + filenames, err := fs.Glob(fsys, pattern) + if err != nil { + return nil, err + } + if len(filenames) == 0 { + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) + } + t := template.New("").Delims(delimLeft, delimRight) + return parseFiles(t, readFileFS(fsys, delimLeft, delimRight, suffix), filenames...) +} + +func readFileFS(fsys fs.FS, delimLeft, delimRight, suffix string) func(string) (string, []byte, error) { + return func(file string) (name string, b []byte, err error) { + name = strings.TrimSuffix(path.Base(file), suffix) + if b, err = fs.ReadFile(fsys, file); err != nil { + return + } + var buf bytes.Buffer + if templ.TranslateEx(&buf, string(b), delimLeft, delimRight) { + b = buf.Bytes() + } + return + } +} diff --git a/internal/templ/template.go b/internal/templ/template.go index c497fd6..a8837fb 100644 --- a/internal/templ/template.go +++ b/internal/templ/template.go @@ -21,15 +21,35 @@ import ( ) func Translate(text string) string { + var b strings.Builder + if TranslateEx(&b, text, "{{", "}}") { + return b.String() + } + return text +} + +type iBuilder interface { + Grow(n int) + WriteString(s string) (int, error) + String() string +} + +func TranslateEx[Builder iBuilder](b Builder, text, delimLeft, delimRight string) bool { offs := make([]int, 0, 16) base := 0 + if delimLeft == "" { + delimLeft = "{{" + } + if delimRight == "" { + delimRight = "}}" + } for { - pos := strings.Index(text[base:], "{{") + pos := strings.Index(text[base:], delimLeft) if pos < 0 { break } begin := base + pos + 2 // script begin - n := strings.Index(text[begin:], "}}") + n := strings.Index(text[begin:], delimRight) if n < 0 { n = len(text) - begin // script length } @@ -51,19 +71,19 @@ func Translate(text string) string { } n := len(offs) if n == 0 { - return text + return false } - var b strings.Builder b.Grow(len(text) + n*4) base = 0 + delimRightLeft := delimRight + delimLeft for i := 0; i < n; i++ { off := offs[i] b.WriteString(text[base:off]) - b.WriteString("}}{{") + b.WriteString(delimRightLeft) base = off } b.WriteString(text[base:]) - return b.String() + return true } func isSpace(c byte) bool { diff --git a/internal/templ/template_test.go b/internal/templ/template_test.go index 53e97ec..1d9f26e 100644 --- a/internal/templ/template_test.go +++ b/internal/templ/template_test.go @@ -16,7 +16,10 @@ package templ -import "testing" +import ( + "bytes" + "testing" +) const yapScriptIn = ` @@ -95,4 +98,9 @@ efg if ret := Translate(noScriptEnd); ret != noScriptEndOut { t.Fatal("translate(noScriptEnd):", ret) } + var b bytes.Buffer + TranslateEx(&b, yapScriptIn, "", "") + if b.String() != yapScriptOut { + t.Fatal("TranslateEx:", b.String()) + } } diff --git a/template.go b/template.go deleted file mode 100644 index 883358f..0000000 --- a/template.go +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package yap - -import ( - "fmt" - "html/template" - "io/fs" - "log" - "os" - "path/filepath" - "strings" - - "github.com/goplus/yap/internal/templ" -) - -// Template is the representation of a parsed template. The *parse.Tree -// field is exported only for use by html/template and should be treated -// as unexported by all other clients. -type Template struct { - *template.Template -} - -// NewTemplate allocates a new, undefined template with the given 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) { - ret.Template, err = t.Template.Parse(templ.Translate(text)) - return -} - -func ParseFSFile(f fs.FS, file string) (t Template, err error) { - b, err := fs.ReadFile(f, file) - if err != nil { - return - } - 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 64eb3e8..adc010a 100644 --- a/yap.go +++ b/yap.go @@ -24,6 +24,7 @@ import ( "os" "strings" + "github.com/goplus/yap/internal/htmltempl" "github.com/goplus/yap/noredirect" ) @@ -33,7 +34,9 @@ type Engine struct { router Mux *http.ServeMux - tpl *Template + delimLeft, delimRight string + + tpl htmltempl.Template fs fs.FS las func(addr string, handler http.Handler) error } @@ -68,15 +71,6 @@ func (p *Engine) initYapFS(fsys fs.FS) { p.fs = fsys } -// 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 { if p.fs == nil { p.initYapFS(os.DirFS(".")) @@ -163,9 +157,15 @@ func (p *Engine) SetLAS(listenAndServe func(addr string, handler http.Handler) e p.las = listenAndServe } +// SetDelims sets the action delimiters to the specified strings. Nested template definitions +// will inherit the settings. An empty delimiter stands for the corresponding default: {{ or }}. +func (p *Engine) SetDelims(left, right string) { + p.delimLeft, p.delimRight = left, right +} + func (p *Engine) templ(path string) *template.Template { - if p.tpl == nil { - p.loadTemplate() + if p.tpl.Template == nil { + p.tpl.InitTemplates(p.yapFS(), p.delimLeft, p.delimRight, "_yap.html") } return p.tpl.Lookup(path) } diff --git a/ytest/demo/match/hello/gop_autogen.go b/ytest/demo/match/hello/gop_autogen.go index 6e47be0..bf095be 100644 --- a/ytest/demo/match/hello/gop_autogen.go +++ b/ytest/demo/match/hello/gop_autogen.go @@ -43,11 +43,11 @@ func (this *hello) Main() { id := "123" //line ytest/demo/match/hello/hello_yapt.gox:4:1 this.Get(stringutil.Concat("http://foo.com/p/", id)) -//line ytest/demo/match/hello/hello_yapt.gox:5:1 - this.RetWith(200) //line ytest/demo/match/hello/hello_yapt.gox:6:1 + this.RetWith(200) +//line ytest/demo/match/hello/hello_yapt.gox:7:1 this.Json(map[string]string{"id": id}) -//line ytest/demo/match/hello/hello_yapt.gox:9:1 +//line ytest/demo/match/hello/hello_yapt.gox:11:1 fmt.Println("OK") } func (this *hello) Classfname() string {