| // Copyright 2021 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package tmplfunc | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "reflect" | |
| "regexp" | |
| "strings" | |
| "text/template/parse" | |
| htmltemplate "html/template" | |
| texttemplate "text/template" | |
| ) | |
| var validNameRE = regexp.MustCompile(`\A[_\pL][_\pL\p{Nd}]*\z`) | |
| var validArgNameRE = regexp.MustCompile(`\A[_\pL][_\pL\p{Nd}]*(\.\.\.|\?)?\z`) | |
| func funcs(t Template, names, texts []string) error { | |
| var leftDelim, rightDelim string | |
| switch t := t.(type) { | |
| case nil: | |
| return fmt.Errorf("tmplfunc: nil Template") | |
| default: | |
| return fmt.Errorf("tmplfunc: non-template type %T", t) | |
| case *texttemplate.Template: | |
| leftDelim = reflect.ValueOf(t).Elem().FieldByName("leftDelim").String() | |
| rightDelim = reflect.ValueOf(t).Elem().FieldByName("rightDelim").String() | |
| case *htmltemplate.Template: | |
| leftDelim = reflect.ValueOf(t).Elem().FieldByName("text").Elem().FieldByName("leftDelim").String() | |
| rightDelim = reflect.ValueOf(t).Elem().FieldByName("text").Elem().FieldByName("rightDelim").String() | |
| } | |
| trees := make(map[string]*parse.Tree) | |
| for i, text := range texts { | |
| t := parse.New(names[i], nil) | |
| t.Mode = parse.SkipFuncCheck | |
| _, err := t.Parse(text, leftDelim, rightDelim, trees) | |
| if err != nil { | |
| return err | |
| } | |
| } | |
| // Install functions for named templates as appropriate. | |
| funcs := make(map[string]interface{}) | |
| for name := range trees { | |
| if err := addFunc(t, name, funcs); err != nil { | |
| return err | |
| } | |
| } | |
| switch t := t.(type) { | |
| case *texttemplate.Template: | |
| t.Funcs(funcs) | |
| case *htmltemplate.Template: | |
| t.Funcs(funcs) | |
| } | |
| return nil | |
| } | |
| // Funcs installs functions for all the templates in the set containing t. | |
| // After using t.Clone it is necessary to call Funcs on the result to arrange | |
| // for the functions to invoke the cloned templates and not the originals. | |
| func Funcs(t Template) error { | |
| funcs := make(map[string]interface{}) | |
| switch t := t.(type) { | |
| case *texttemplate.Template: | |
| for _, t1 := range t.Templates() { | |
| if err := addFunc(t, t1.Name(), funcs); err != nil { | |
| return err | |
| } | |
| } | |
| t.Funcs(funcs) | |
| case *htmltemplate.Template: | |
| for _, t1 := range t.Templates() { | |
| if err := addFunc(t, t1.Name(), funcs); err != nil { | |
| return err | |
| } | |
| } | |
| t.Funcs(funcs) | |
| } | |
| return nil | |
| } | |
| func addFunc(t Template, name string, funcs map[string]interface{}) error { | |
| fn, bundle, err := bundler(name) | |
| if err != nil { | |
| return err | |
| } | |
| if fn == "" { | |
| return nil | |
| } | |
| switch t := t.(type) { | |
| case *texttemplate.Template: | |
| funcs[fn] = func(args ...interface{}) (string, error) { | |
| t := t.Lookup(name) | |
| if t == nil { | |
| return "", fmt.Errorf("lost template %q", name) | |
| } | |
| arg, err := bundle(args) | |
| if err != nil { | |
| return "", err | |
| } | |
| var buf bytes.Buffer | |
| err = t.Execute(&buf, arg) | |
| if err != nil { | |
| return "", err | |
| } | |
| return buf.String(), nil | |
| } | |
| case *htmltemplate.Template: | |
| funcs[fn] = func(args ...interface{}) (htmltemplate.HTML, error) { | |
| t := t.Lookup(name) | |
| if t == nil { | |
| return "", fmt.Errorf("lost template %q", name) | |
| } | |
| arg, err := bundle(args) | |
| if err != nil { | |
| return "", err | |
| } | |
| var buf bytes.Buffer | |
| err = t.Execute(&buf, arg) | |
| if err != nil { | |
| return "", err | |
| } | |
| return htmltemplate.HTML(buf.String()), nil | |
| } | |
| } | |
| return nil | |
| } | |
| func bundler(name string) (fn string, bundle func(args []interface{}) (interface{}, error), err error) { | |
| f := strings.Fields(name) | |
| if len(f) == 0 || !validNameRE.MatchString(f[0]) { | |
| return "", nil, nil | |
| } | |
| fn = f[0] | |
| if len(f) == 1 { | |
| bundle = func(args []interface{}) (interface{}, error) { | |
| if len(args) == 0 { | |
| return nil, nil | |
| } | |
| if len(args) == 1 { | |
| return args[0], nil | |
| } | |
| return nil, fmt.Errorf("too many arguments in call to template %s", fn) | |
| } | |
| } else { | |
| sawQ := false | |
| for i, argName := range f[1:] { | |
| if !validArgNameRE.MatchString(argName) { | |
| return "", nil, fmt.Errorf("invalid template name %q: invalid argument name %s", name, argName) | |
| } | |
| if strings.HasSuffix(argName, "...") { | |
| if i != len(f)-2 { | |
| return "", nil, fmt.Errorf("invalid template name %q: %s is not last argument", name, argName) | |
| } | |
| break | |
| } | |
| if strings.HasSuffix(argName, "?") { | |
| sawQ = true | |
| continue | |
| } | |
| if sawQ { | |
| return "", nil, fmt.Errorf("invalid template name %q: required %s after optional %s", name, argName, f[i]) | |
| } | |
| } | |
| bundle = func(args []interface{}) (interface{}, error) { | |
| m := make(map[string]interface{}) | |
| for _, argName := range f[1:] { | |
| if strings.HasSuffix(argName, "...") { | |
| m[strings.TrimSuffix(argName, "...")] = args | |
| args = nil | |
| break | |
| } | |
| if strings.HasSuffix(argName, "?") { | |
| prefix := strings.TrimSuffix(argName, "?") | |
| if len(args) == 0 { | |
| m[prefix] = nil | |
| } else { | |
| m[prefix], args = args[0], args[1:] | |
| } | |
| continue | |
| } | |
| if len(args) == 0 { | |
| return nil, fmt.Errorf("too few arguments in call to template %s", fn) | |
| } | |
| m[argName], args = args[0], args[1:] | |
| } | |
| if len(args) > 0 { | |
| return nil, fmt.Errorf("too many arguments in call to template %s", fn) | |
| } | |
| return m, nil | |
| } | |
| } | |
| return fn, bundle, nil | |
| } |