mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
tpl: Sync go_templates for Go 1.18
Using Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774 Updates #9677
This commit is contained in:
parent
4d6d1d08da
commit
65a78cae1e
48 changed files with 697 additions and 223 deletions
|
@ -17,8 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// TODO(bep) git checkout tag
|
// The current is built with Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774
|
||||||
// The current is built with Go version 2f0da6d9e29d9b9d5a4d10427ca9f71d12bbacc8 / go1.16
|
|
||||||
fmt.Println("Forking ...")
|
fmt.Println("Forking ...")
|
||||||
defer fmt.Println("Done ...")
|
defer fmt.Println("Done ...")
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ func main() {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TODO(bep)
|
// TODO(bep)
|
||||||
goSource = "/Users/bep/dev/go/dump/go/src"
|
goSource = "/Users/bep/dev/go/misc/go/src"
|
||||||
forkRoot = "../../tpl/internal/go_templates"
|
forkRoot = "../../tpl/internal/go_templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -33,12 +33,14 @@ const KnownEnv = `
|
||||||
GCCGO
|
GCCGO
|
||||||
GO111MODULE
|
GO111MODULE
|
||||||
GO386
|
GO386
|
||||||
|
GOAMD64
|
||||||
GOARCH
|
GOARCH
|
||||||
GOARM
|
GOARM
|
||||||
GOBIN
|
GOBIN
|
||||||
GOCACHE
|
GOCACHE
|
||||||
GOENV
|
GOENV
|
||||||
GOEXE
|
GOEXE
|
||||||
|
GOEXPERIMENT
|
||||||
GOFLAGS
|
GOFLAGS
|
||||||
GOGCCFLAGS
|
GOGCCFLAGS
|
||||||
GOHOSTARCH
|
GOHOSTARCH
|
||||||
|
@ -60,6 +62,7 @@ const KnownEnv = `
|
||||||
GOTOOLDIR
|
GOTOOLDIR
|
||||||
GOVCS
|
GOVCS
|
||||||
GOWASM
|
GOWASM
|
||||||
|
GOWORK
|
||||||
GO_EXTLINK_ENABLED
|
GO_EXTLINK_ENABLED
|
||||||
PKG_CONFIG
|
PKG_CONFIG
|
||||||
`
|
`
|
||||||
|
|
|
@ -130,7 +130,7 @@ func compare(aVal, bVal reflect.Value) int {
|
||||||
default:
|
default:
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
case reflect.Ptr, reflect.UnsafePointer:
|
case reflect.Pointer, reflect.UnsafePointer:
|
||||||
a, b := aVal.Pointer(), bVal.Pointer()
|
a, b := aVal.Pointer(), bVal.Pointer()
|
||||||
switch {
|
switch {
|
||||||
case a < b:
|
case a < b:
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -37,12 +38,12 @@ var compareTests = [][]reflect.Value{
|
||||||
ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]),
|
ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]),
|
||||||
ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}),
|
ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}),
|
||||||
ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}),
|
ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}),
|
||||||
ct(reflect.TypeOf(interface{}(interface{}(0))), iFace, 1, 2, 3),
|
ct(reflect.TypeOf(any(any(0))), iFace, 1, 2, 3),
|
||||||
}
|
}
|
||||||
|
|
||||||
var iFace interface{}
|
var iFace any
|
||||||
|
|
||||||
func ct(typ reflect.Type, args ...interface{}) []reflect.Value {
|
func ct(typ reflect.Type, args ...any) []reflect.Value {
|
||||||
value := make([]reflect.Value, len(args))
|
value := make([]reflect.Value, len(args))
|
||||||
for i, v := range args {
|
for i, v := range args {
|
||||||
x := reflect.ValueOf(v)
|
x := reflect.ValueOf(v)
|
||||||
|
@ -83,7 +84,7 @@ func TestCompare(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sortTest struct {
|
type sortTest struct {
|
||||||
data interface{} // Always a map.
|
data any // Always a map.
|
||||||
print string // Printed result using our custom printer.
|
print string // Printed result using our custom printer.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ var sortTests = []sortTest{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func sprint(data interface{}) string {
|
func sprint(data any) string {
|
||||||
om := fmtsort.Sort(reflect.ValueOf(data))
|
om := fmtsort.Sort(reflect.ValueOf(data))
|
||||||
if om == nil {
|
if om == nil {
|
||||||
return "nil"
|
return "nil"
|
||||||
|
@ -188,9 +189,19 @@ func sprintKey(key reflect.Value) string {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ints [3]int
|
ints [3]int
|
||||||
chans = [3]chan int{make(chan int), make(chan int), make(chan int)}
|
chans = makeChans()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func makeChans() []chan int {
|
||||||
|
cs := []chan int{make(chan int), make(chan int), make(chan int)}
|
||||||
|
// Order channels by address. See issue #49431.
|
||||||
|
// TODO: pin these pointers once pinning is available (#46787).
|
||||||
|
sort.Slice(cs, func(i, j int) bool {
|
||||||
|
return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
|
||||||
|
})
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
func pointerMap() map[*int]string {
|
func pointerMap() map[*int]string {
|
||||||
m := make(map[*int]string)
|
m := make(map[*int]string)
|
||||||
for i := 2; i >= 0; i-- {
|
for i := 2; i >= 0; i-- {
|
||||||
|
@ -233,7 +244,7 @@ func TestInterface(t *testing.T) {
|
||||||
// A map containing multiple concrete types should be sorted by type,
|
// A map containing multiple concrete types should be sorted by type,
|
||||||
// then value. However, the relative ordering of types is unspecified,
|
// then value. However, the relative ordering of types is unspecified,
|
||||||
// so test this by checking the presence of sorted subgroups.
|
// so test this by checking the presence of sorted subgroups.
|
||||||
m := map[interface{}]string{
|
m := map[any]string{
|
||||||
[2]int{1, 0}: "",
|
[2]int{1, 0}: "",
|
||||||
[2]int{0, 1}: "",
|
[2]int{0, 1}: "",
|
||||||
true: "",
|
true: "",
|
||||||
|
|
|
@ -143,12 +143,12 @@ func attrType(name string) contentType {
|
||||||
// widely applied.
|
// widely applied.
|
||||||
// Treat data-action as URL below.
|
// Treat data-action as URL below.
|
||||||
name = name[5:]
|
name = name[5:]
|
||||||
} else if colon := strings.IndexRune(name, ':'); colon != -1 {
|
} else if prefix, short, ok := strings.Cut(name, ":"); ok {
|
||||||
if name[:colon] == "xmlns" {
|
if prefix == "xmlns" {
|
||||||
return contentTypeURL
|
return contentTypeURL
|
||||||
}
|
}
|
||||||
// Treat svg:href and xlink:href as href below.
|
// Treat svg:href and xlink:href as href below.
|
||||||
name = name[colon+1:]
|
name = short
|
||||||
}
|
}
|
||||||
if t, ok := attrTypeMap[name]; ok {
|
if t, ok := attrTypeMap[name]; ok {
|
||||||
return t
|
return t
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
|
@ -29,16 +29,16 @@ const (
|
||||||
|
|
||||||
// indirect returns the value, after dereferencing as many times
|
// indirect returns the value, after dereferencing as many times
|
||||||
// as necessary to reach the base type (or nil).
|
// as necessary to reach the base type (or nil).
|
||||||
func indirect(a interface{}) interface{} {
|
func indirect(a any) any {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
|
if t := reflect.TypeOf(a); t.Kind() != reflect.Pointer {
|
||||||
// Avoid creating a reflect.Value if it's not a pointer.
|
// Avoid creating a reflect.Value if it's not a pointer.
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
v := reflect.ValueOf(a)
|
v := reflect.ValueOf(a)
|
||||||
for v.Kind() == reflect.Ptr && !v.IsNil() {
|
for v.Kind() == reflect.Pointer && !v.IsNil() {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
return v.Interface()
|
return v.Interface()
|
||||||
|
@ -52,12 +52,12 @@ var (
|
||||||
// indirectToStringerOrError returns the value, after dereferencing as many times
|
// indirectToStringerOrError returns the value, after dereferencing as many times
|
||||||
// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
|
// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
|
||||||
// or error,
|
// or error,
|
||||||
func indirectToStringerOrError(a interface{}) interface{} {
|
func indirectToStringerOrError(a any) any {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
v := reflect.ValueOf(a)
|
v := reflect.ValueOf(a)
|
||||||
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
|
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Pointer && !v.IsNil() {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
return v.Interface()
|
return v.Interface()
|
||||||
|
@ -65,7 +65,7 @@ func indirectToStringerOrError(a interface{}) interface{} {
|
||||||
|
|
||||||
// stringify converts its arguments to a string and the type of the content.
|
// stringify converts its arguments to a string and the type of the content.
|
||||||
// All pointers are dereferenced, as in the text/template package.
|
// All pointers are dereferenced, as in the text/template package.
|
||||||
func stringify(args ...interface{}) (string, contentType) {
|
func stringify(args ...any) (string, contentType) {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
switch s := indirect(args[0]).(type) {
|
switch s := indirect(args[0]).(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTypedContent(t *testing.T) {
|
func TestTypedContent(t *testing.T) {
|
||||||
data := []interface{}{
|
data := []any{
|
||||||
`<b> "foo%" O'Reilly &bar;`,
|
`<b> "foo%" O'Reilly &bar;`,
|
||||||
htmltemplate.CSS(`a[href =~ "//example.com"]#foo`),
|
htmltemplate.CSS(`a[href =~ "//example.com"]#foo`),
|
||||||
htmltemplate.HTML(`Hello, <b>World</b> &tc!`),
|
htmltemplate.HTML(`Hello, <b>World</b> &tc!`),
|
||||||
|
@ -452,7 +453,7 @@ func TestEscapingNilNonemptyInterfaces(t *testing.T) {
|
||||||
|
|
||||||
// A non-empty interface should print like an empty interface.
|
// A non-empty interface should print like an empty interface.
|
||||||
want := new(bytes.Buffer)
|
want := new(bytes.Buffer)
|
||||||
data := struct{ E interface{} }{}
|
data := struct{ E any }{}
|
||||||
tmpl.Execute(want, data)
|
tmpl.Execute(want, data)
|
||||||
|
|
||||||
if !bytes.Equal(want.Bytes(), got.Bytes()) {
|
if !bytes.Equal(want.Bytes(), got.Bytes()) {
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||||
|
)
|
||||||
|
|
||||||
// context describes the state an HTML parser must be in when it reaches the
|
// context describes the state an HTML parser must be in when it reaches the
|
||||||
// portion of HTML produced by evaluating a particular template node.
|
// portion of HTML produced by evaluating a particular template node.
|
||||||
|
@ -20,6 +24,7 @@ type context struct {
|
||||||
jsCtx jsCtx
|
jsCtx jsCtx
|
||||||
attr attr
|
attr attr
|
||||||
element element
|
element element
|
||||||
|
n parse.Node // for range break/continue
|
||||||
err *Error
|
err *Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +144,8 @@ const (
|
||||||
// stateError is an infectious error state outside any valid
|
// stateError is an infectious error state outside any valid
|
||||||
// HTML/CSS/JS construct.
|
// HTML/CSS/JS construct.
|
||||||
stateError
|
stateError
|
||||||
|
// stateDead marks unreachable code after a {{break}} or {{continue}}.
|
||||||
|
stateDead
|
||||||
)
|
)
|
||||||
|
|
||||||
// isComment is true for any state that contains content meant for template
|
// isComment is true for any state that contains content meant for template
|
||||||
|
|
|
@ -155,7 +155,7 @@ func isCSSSpace(b byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes.
|
// cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes.
|
||||||
func cssEscaper(args ...interface{}) string {
|
func cssEscaper(args ...any) string {
|
||||||
s, _ := stringify(args...)
|
s, _ := stringify(args...)
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
r, w, written := rune(0), 0, 0
|
r, w, written := rune(0), 0, 0
|
||||||
|
@ -218,7 +218,7 @@ var mozBindingBytes = []byte("mozbinding")
|
||||||
// (inherit, blue), and colors (#888).
|
// (inherit, blue), and colors (#888).
|
||||||
// It filters out unsafe values, such as those that affect token boundaries,
|
// It filters out unsafe values, such as those that affect token boundaries,
|
||||||
// and anything that might execute scripts.
|
// and anything that might execute scripts.
|
||||||
func cssValueFilter(args ...interface{}) string {
|
func cssValueFilter(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeCSS {
|
if t == contentTypeCSS {
|
||||||
return s
|
return s
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
|
@ -229,6 +229,6 @@ func (e *Error) Error() string {
|
||||||
|
|
||||||
// errorf creates an error given a format string f and args.
|
// errorf creates an error given a format string f and args.
|
||||||
// The template Name still needs to be supplied.
|
// The template Name still needs to be supplied.
|
||||||
func errorf(k ErrorCode, node parse.Node, line int, f string, args ...interface{}) *Error {
|
func errorf(k ErrorCode, node parse.Node, line int, f string, args ...any) *Error {
|
||||||
return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
|
return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
|
||||||
|
|
||||||
// evalArgs formats the list of arguments into a string. It is equivalent to
|
// evalArgs formats the list of arguments into a string. It is equivalent to
|
||||||
// fmt.Sprint(args...), except that it deferences all pointers.
|
// fmt.Sprint(args...), except that it deferences all pointers.
|
||||||
func evalArgs(args ...interface{}) string {
|
func evalArgs(args ...any) string {
|
||||||
// Optimization for simple common case of a single string argument.
|
// Optimization for simple common case of a single string argument.
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
if s, ok := args[0].(string); ok {
|
if s, ok := args[0].(string); ok {
|
||||||
|
@ -98,6 +98,15 @@ type escaper struct {
|
||||||
actionNodeEdits map[*parse.ActionNode][]string
|
actionNodeEdits map[*parse.ActionNode][]string
|
||||||
templateNodeEdits map[*parse.TemplateNode]string
|
templateNodeEdits map[*parse.TemplateNode]string
|
||||||
textNodeEdits map[*parse.TextNode][]byte
|
textNodeEdits map[*parse.TextNode][]byte
|
||||||
|
// rangeContext holds context about the current range loop.
|
||||||
|
rangeContext *rangeContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// rangeContext holds information about the current range loop.
|
||||||
|
type rangeContext struct {
|
||||||
|
outer *rangeContext // outer loop
|
||||||
|
breaks []context // context at each break action
|
||||||
|
continues []context // context at each continue action
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeEscaper creates a blank escaper for the given set.
|
// makeEscaper creates a blank escaper for the given set.
|
||||||
|
@ -110,6 +119,7 @@ func makeEscaper(n *nameSpace) escaper {
|
||||||
map[*parse.ActionNode][]string{},
|
map[*parse.ActionNode][]string{},
|
||||||
map[*parse.TemplateNode]string{},
|
map[*parse.TemplateNode]string{},
|
||||||
map[*parse.TextNode][]byte{},
|
map[*parse.TextNode][]byte{},
|
||||||
|
nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +135,16 @@ func (e *escaper) escape(c context, n parse.Node) context {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *parse.ActionNode:
|
case *parse.ActionNode:
|
||||||
return e.escapeAction(c, n)
|
return e.escapeAction(c, n)
|
||||||
|
case *parse.BreakNode:
|
||||||
|
c.n = n
|
||||||
|
e.rangeContext.breaks = append(e.rangeContext.breaks, c)
|
||||||
|
return context{state: stateDead}
|
||||||
case *parse.CommentNode:
|
case *parse.CommentNode:
|
||||||
return c
|
return c
|
||||||
|
case *parse.ContinueNode:
|
||||||
|
c.n = n
|
||||||
|
e.rangeContext.continues = append(e.rangeContext.breaks, c)
|
||||||
|
return context{state: stateDead}
|
||||||
case *parse.IfNode:
|
case *parse.IfNode:
|
||||||
return e.escapeBranch(c, &n.BranchNode, "if")
|
return e.escapeBranch(c, &n.BranchNode, "if")
|
||||||
case *parse.ListNode:
|
case *parse.ListNode:
|
||||||
|
@ -428,6 +446,12 @@ func join(a, b context, node parse.Node, nodeName string) context {
|
||||||
if b.state == stateError {
|
if b.state == stateError {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
if a.state == stateDead {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
if b.state == stateDead {
|
||||||
|
return a
|
||||||
|
}
|
||||||
if a.eq(b) {
|
if a.eq(b) {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
@ -467,14 +491,27 @@ func join(a, b context, node parse.Node, nodeName string) context {
|
||||||
|
|
||||||
// escapeBranch escapes a branch template node: "if", "range" and "with".
|
// escapeBranch escapes a branch template node: "if", "range" and "with".
|
||||||
func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
|
func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
|
||||||
|
if nodeName == "range" {
|
||||||
|
e.rangeContext = &rangeContext{outer: e.rangeContext}
|
||||||
|
}
|
||||||
c0 := e.escapeList(c, n.List)
|
c0 := e.escapeList(c, n.List)
|
||||||
if nodeName == "range" && c0.state != stateError {
|
if nodeName == "range" {
|
||||||
|
if c0.state != stateError {
|
||||||
|
c0 = joinRange(c0, e.rangeContext)
|
||||||
|
}
|
||||||
|
e.rangeContext = e.rangeContext.outer
|
||||||
|
if c0.state == stateError {
|
||||||
|
return c0
|
||||||
|
}
|
||||||
|
|
||||||
// The "true" branch of a "range" node can execute multiple times.
|
// The "true" branch of a "range" node can execute multiple times.
|
||||||
// We check that executing n.List once results in the same context
|
// We check that executing n.List once results in the same context
|
||||||
// as executing n.List twice.
|
// as executing n.List twice.
|
||||||
|
e.rangeContext = &rangeContext{outer: e.rangeContext}
|
||||||
c1, _ := e.escapeListConditionally(c0, n.List, nil)
|
c1, _ := e.escapeListConditionally(c0, n.List, nil)
|
||||||
c0 = join(c0, c1, n, nodeName)
|
c0 = join(c0, c1, n, nodeName)
|
||||||
if c0.state == stateError {
|
if c0.state == stateError {
|
||||||
|
e.rangeContext = e.rangeContext.outer
|
||||||
// Make clear that this is a problem on loop re-entry
|
// Make clear that this is a problem on loop re-entry
|
||||||
// since developers tend to overlook that branch when
|
// since developers tend to overlook that branch when
|
||||||
// debugging templates.
|
// debugging templates.
|
||||||
|
@ -482,11 +519,39 @@ func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string)
|
||||||
c0.err.Description = "on range loop re-entry: " + c0.err.Description
|
c0.err.Description = "on range loop re-entry: " + c0.err.Description
|
||||||
return c0
|
return c0
|
||||||
}
|
}
|
||||||
|
c0 = joinRange(c0, e.rangeContext)
|
||||||
|
e.rangeContext = e.rangeContext.outer
|
||||||
|
if c0.state == stateError {
|
||||||
|
return c0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c1 := e.escapeList(c, n.ElseList)
|
c1 := e.escapeList(c, n.ElseList)
|
||||||
return join(c0, c1, n, nodeName)
|
return join(c0, c1, n, nodeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func joinRange(c0 context, rc *rangeContext) context {
|
||||||
|
// Merge contexts at break and continue statements into overall body context.
|
||||||
|
// In theory we could treat breaks differently from continues, but for now it is
|
||||||
|
// enough to treat them both as going back to the start of the loop (which may then stop).
|
||||||
|
for _, c := range rc.breaks {
|
||||||
|
c0 = join(c0, c, c.n, "range")
|
||||||
|
if c0.state == stateError {
|
||||||
|
c0.err.Line = c.n.(*parse.BreakNode).Line
|
||||||
|
c0.err.Description = "at range loop break: " + c0.err.Description
|
||||||
|
return c0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range rc.continues {
|
||||||
|
c0 = join(c0, c, c.n, "range")
|
||||||
|
if c0.state == stateError {
|
||||||
|
c0.err.Line = c.n.(*parse.ContinueNode).Line
|
||||||
|
c0.err.Description = "at range loop continue: " + c0.err.Description
|
||||||
|
return c0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c0
|
||||||
|
}
|
||||||
|
|
||||||
// escapeList escapes a list template node.
|
// escapeList escapes a list template node.
|
||||||
func (e *escaper) escapeList(c context, n *parse.ListNode) context {
|
func (e *escaper) escapeList(c context, n *parse.ListNode) context {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
|
@ -494,6 +559,9 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context {
|
||||||
}
|
}
|
||||||
for _, m := range n.Nodes {
|
for _, m := range n.Nodes {
|
||||||
c = e.escape(c, m)
|
c = e.escape(c, m)
|
||||||
|
if c.state == stateDead {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
@ -504,6 +572,7 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context {
|
||||||
// which is the same as whether e was updated.
|
// which is the same as whether e was updated.
|
||||||
func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
|
func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
|
||||||
e1 := makeEscaper(e.ns)
|
e1 := makeEscaper(e.ns)
|
||||||
|
e1.rangeContext = e.rangeContext
|
||||||
// Make type inferences available to f.
|
// Make type inferences available to f.
|
||||||
for k, v := range e.output {
|
for k, v := range e.output {
|
||||||
e1.output[k] = v
|
e1.output[k] = v
|
||||||
|
@ -866,7 +935,7 @@ func HTMLEscapeString(s string) string {
|
||||||
|
|
||||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||||
// representation of its arguments.
|
// representation of its arguments.
|
||||||
func HTMLEscaper(args ...interface{}) string {
|
func HTMLEscaper(args ...any) string {
|
||||||
return template.HTMLEscaper(args...)
|
return template.HTMLEscaper(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,12 +951,12 @@ func JSEscapeString(s string) string {
|
||||||
|
|
||||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||||
// representation of its arguments.
|
// representation of its arguments.
|
||||||
func JSEscaper(args ...interface{}) string {
|
func JSEscaper(args ...any) string {
|
||||||
return template.JSEscaper(args...)
|
return template.JSEscaper(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||||
// its arguments in a form suitable for embedding in a URL query.
|
// its arguments in a form suitable for embedding in a URL query.
|
||||||
func URLQueryEscaper(args ...interface{}) string {
|
func URLQueryEscaper(args ...any) string {
|
||||||
return template.URLQueryEscaper(args...)
|
return template.URLQueryEscaper(args...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -39,7 +40,7 @@ func TestEscape(t *testing.T) {
|
||||||
A, E []string
|
A, E []string
|
||||||
B, M json.Marshaler
|
B, M json.Marshaler
|
||||||
N int
|
N int
|
||||||
U interface{} // untyped nil
|
U any // untyped nil
|
||||||
Z *int // typed nil
|
Z *int // typed nil
|
||||||
W htmltemplate.HTML
|
W htmltemplate.HTML
|
||||||
}{
|
}{
|
||||||
|
@ -862,7 +863,7 @@ func TestEscapeSet(t *testing.T) {
|
||||||
|
|
||||||
// pred is a template function that returns the predecessor of a
|
// pred is a template function that returns the predecessor of a
|
||||||
// natural number for testing recursive templates.
|
// natural number for testing recursive templates.
|
||||||
fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
|
fns := FuncMap{"pred": func(a ...any) (any, error) {
|
||||||
if len(a) == 1 {
|
if len(a) == 1 {
|
||||||
if i, _ := a[0].(int); i > 0 {
|
if i, _ := a[0].(int); i > 0 {
|
||||||
return i - 1, nil
|
return i - 1, nil
|
||||||
|
@ -924,6 +925,22 @@ func TestErrors(t *testing.T) {
|
||||||
"<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>",
|
"<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>",
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}<a{{if .X}}{{end}}>{{end}}",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}<a{{if .X}}{{end}}>{{continue}}{{end}}",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}<a{{if .X}}{{end}}>{{break}}{{end}}",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}<a{{if .X}}{{end}}>{{if .X}}{{break}}{{end}}{{end}}",
|
||||||
|
"",
|
||||||
|
},
|
||||||
// Error cases.
|
// Error cases.
|
||||||
{
|
{
|
||||||
"{{if .Cond}}<a{{end}}",
|
"{{if .Cond}}<a{{end}}",
|
||||||
|
@ -959,6 +976,14 @@ func TestErrors(t *testing.T) {
|
||||||
"\n{{range .Items}} x='<a{{end}}",
|
"\n{{range .Items}} x='<a{{end}}",
|
||||||
"z:2:8: on range loop re-entry: {{range}} branches",
|
"z:2:8: on range loop re-entry: {{range}} branches",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}<a{{if .X}}{{break}}{{end}}>{{end}}",
|
||||||
|
"z:1:29: at range loop break: {{range}} branches end in different contexts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"{{range .Items}}<a{{if .X}}{{continue}}{{end}}>{{end}}",
|
||||||
|
"z:1:29: at range loop continue: {{range}} branches end in different contexts",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"<a b=1 c={{.H}}",
|
"<a b=1 c={{.H}}",
|
||||||
"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
|
"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
|
||||||
|
@ -1768,7 +1793,7 @@ func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedundantFuncs(t *testing.T) {
|
func TestRedundantFuncs(t *testing.T) {
|
||||||
inputs := []interface{}{
|
inputs := []any{
|
||||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
|
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
|
||||||
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
|
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
|
||||||
` !"#$%&'()*+,-./` +
|
` !"#$%&'()*+,-./` +
|
||||||
|
@ -1788,9 +1813,9 @@ func TestRedundantFuncs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for n0, m := range redundantFuncs {
|
for n0, m := range redundantFuncs {
|
||||||
f0 := funcMap[n0].(func(...interface{}) string)
|
f0 := funcMap[n0].(func(...any) string)
|
||||||
for n1 := range m {
|
for n1 := range m {
|
||||||
f1 := funcMap[n1].(func(...interface{}) string)
|
f1 := funcMap[n1].(func(...any) string)
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
want := f0(input)
|
want := f0(input)
|
||||||
if got := f1(want); want != got {
|
if got := f1(want); want != got {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
@ -101,7 +102,7 @@ func Example_autoescaping() {
|
||||||
|
|
||||||
func Example_escape() {
|
func Example_escape() {
|
||||||
const s = `"Fran & Freddie's Diner" <tasty@example.com>`
|
const s = `"Fran & Freddie's Diner" <tasty@example.com>`
|
||||||
v := []interface{}{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}
|
v := []any{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}
|
||||||
|
|
||||||
fmt.Println(template.HTMLEscapeString(s))
|
fmt.Println(template.HTMLEscapeString(s))
|
||||||
template.HTMLEscape(os.Stdout, []byte(s))
|
template.HTMLEscape(os.Stdout, []byte(s))
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
// Tests for template execution, copied from text/template.
|
// Tests for template execution, copied from text/template.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -53,7 +54,7 @@ type T struct {
|
||||||
MSI map[string]int
|
MSI map[string]int
|
||||||
MSIone map[string]int // one element, for deterministic output
|
MSIone map[string]int // one element, for deterministic output
|
||||||
MSIEmpty map[string]int
|
MSIEmpty map[string]int
|
||||||
MXI map[interface{}]int
|
MXI map[any]int
|
||||||
MII map[int]int
|
MII map[int]int
|
||||||
MI32S map[int32]string
|
MI32S map[int32]string
|
||||||
MI64S map[int64]string
|
MI64S map[int64]string
|
||||||
|
@ -63,11 +64,11 @@ type T struct {
|
||||||
MUI8S map[uint8]string
|
MUI8S map[uint8]string
|
||||||
SMSI []map[string]int
|
SMSI []map[string]int
|
||||||
// Empty interfaces; used to see if we can dig inside one.
|
// Empty interfaces; used to see if we can dig inside one.
|
||||||
Empty0 interface{} // nil
|
Empty0 any // nil
|
||||||
Empty1 interface{}
|
Empty1 any
|
||||||
Empty2 interface{}
|
Empty2 any
|
||||||
Empty3 interface{}
|
Empty3 any
|
||||||
Empty4 interface{}
|
Empty4 any
|
||||||
// Non-empty interfaces.
|
// Non-empty interfaces.
|
||||||
NonEmptyInterface I
|
NonEmptyInterface I
|
||||||
NonEmptyInterfacePtS *I
|
NonEmptyInterfacePtS *I
|
||||||
|
@ -145,7 +146,7 @@ var tVal = &T{
|
||||||
SB: []bool{true, false},
|
SB: []bool{true, false},
|
||||||
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
|
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
|
||||||
MSIone: map[string]int{"one": 1},
|
MSIone: map[string]int{"one": 1},
|
||||||
MXI: map[interface{}]int{"one": 1},
|
MXI: map[any]int{"one": 1},
|
||||||
MII: map[int]int{1: 1},
|
MII: map[int]int{1: 1},
|
||||||
MI32S: map[int32]string{1: "one", 2: "two"},
|
MI32S: map[int32]string{1: "one", 2: "two"},
|
||||||
MI64S: map[int64]string{2: "i642", 3: "i643"},
|
MI64S: map[int64]string{2: "i642", 3: "i643"},
|
||||||
|
@ -216,7 +217,7 @@ func (t *T) Method2(a uint16, b string) string {
|
||||||
return fmt.Sprintf("Method2: %d %s", a, b)
|
return fmt.Sprintf("Method2: %d %s", a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *T) Method3(v interface{}) string {
|
func (t *T) Method3(v any) string {
|
||||||
return fmt.Sprintf("Method3: %v", v)
|
return fmt.Sprintf("Method3: %v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +257,7 @@ func (u *U) TrueFalse(b bool) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func typeOf(arg interface{}) string {
|
func typeOf(arg any) string {
|
||||||
return fmt.Sprintf("%T", arg)
|
return fmt.Sprintf("%T", arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +265,7 @@ type execTest struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
output string
|
output string
|
||||||
data interface{}
|
data any
|
||||||
ok bool
|
ok bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +398,7 @@ var execTests = []execTest{
|
||||||
{".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true},
|
{".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true},
|
||||||
{"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true},
|
{"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true},
|
||||||
{"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
|
{"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
|
||||||
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true},
|
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]any{"S": bytes.NewBufferString("foozle")}, true},
|
||||||
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
|
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
|
||||||
{"call nil", "{{call nil}}", "", tVal, false},
|
{"call nil", "{{call nil}}", "", tVal, false},
|
||||||
|
|
||||||
|
@ -571,6 +572,8 @@ var execTests = []execTest{
|
||||||
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
|
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
|
||||||
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
||||||
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
||||||
|
{"range []int break else", "{{range .SI}}-{{.}}-{{break}}NOTREACHED{{else}}EMPTY{{end}}", "-3-", tVal, true},
|
||||||
|
{"range []int continue else", "{{range .SI}}-{{.}}-{{continue}}NOTREACHED{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
||||||
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
|
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
|
||||||
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
|
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
|
||||||
{"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
|
{"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
|
||||||
|
@ -742,7 +745,7 @@ func add(args ...int) int {
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
func echo(arg interface{}) interface{} {
|
func echo(arg any) any {
|
||||||
return arg
|
return arg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,7 +764,7 @@ func stringer(s fmt.Stringer) string {
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapOfThree() interface{} {
|
func mapOfThree() any {
|
||||||
return map[string]int{"three": 3}
|
return map[string]int{"three": 3}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1440,7 +1443,7 @@ func TestBlock(t *testing.T) {
|
||||||
func TestEvalFieldErrors(t *testing.T) {
|
func TestEvalFieldErrors(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name, src string
|
name, src string
|
||||||
value interface{}
|
value any
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -1583,7 +1586,7 @@ func TestInterfaceValues(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tmpl := Must(New("tmpl").Parse(tt.text))
|
tmpl := Must(New("tmpl").Parse(tt.text))
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := tmpl.Execute(&buf, map[string]interface{}{
|
err := tmpl.Execute(&buf, map[string]any{
|
||||||
"PlusOne": func(n int) int {
|
"PlusOne": func(n int) int {
|
||||||
return n + 1
|
return n + 1
|
||||||
},
|
},
|
||||||
|
@ -1612,7 +1615,7 @@ func TestInterfaceValues(t *testing.T) {
|
||||||
|
|
||||||
// Check that panics during calls are recovered and returned as errors.
|
// Check that panics during calls are recovered and returned as errors.
|
||||||
func TestExecutePanicDuringCall(t *testing.T) {
|
func TestExecutePanicDuringCall(t *testing.T) {
|
||||||
funcs := map[string]interface{}{
|
funcs := map[string]any{
|
||||||
"doPanic": func() string {
|
"doPanic": func() string {
|
||||||
panic("custom panic string")
|
panic("custom panic string")
|
||||||
},
|
},
|
||||||
|
@ -1620,7 +1623,7 @@ func TestExecutePanicDuringCall(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
data interface{}
|
data any
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -1724,8 +1727,6 @@ var v = "v";
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestEscapeRace(t *testing.T) {
|
func TestEscapeRace(t *testing.T) {
|
||||||
// t.Skip("this test currently fails with -race; see issue #39807")
|
|
||||||
|
|
||||||
tmpl := New("")
|
tmpl := New("")
|
||||||
_, err := tmpl.New("templ.html").Parse(raceText)
|
_, err := tmpl.New("templ.html").Parse(raceText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1820,7 +1821,7 @@ func TestRecursiveExecuteViaMethod(t *testing.T) {
|
||||||
func TestTemplateFuncsAfterClone(t *testing.T) {
|
func TestTemplateFuncsAfterClone(t *testing.T) {
|
||||||
s := `{{ f . }}`
|
s := `{{ f . }}`
|
||||||
want := "test"
|
want := "test"
|
||||||
orig := New("orig").Funcs(map[string]interface{}{
|
orig := New("orig").Funcs(map[string]any{
|
||||||
"f": func(in string) string {
|
"f": func(in string) string {
|
||||||
return in
|
return in
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// htmlNospaceEscaper escapes for inclusion in unquoted attribute values.
|
// htmlNospaceEscaper escapes for inclusion in unquoted attribute values.
|
||||||
func htmlNospaceEscaper(args ...interface{}) string {
|
func htmlNospaceEscaper(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeHTML {
|
if t == contentTypeHTML {
|
||||||
return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
|
return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
|
||||||
|
@ -21,7 +21,7 @@ func htmlNospaceEscaper(args ...interface{}) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// attrEscaper escapes for inclusion in quoted attribute values.
|
// attrEscaper escapes for inclusion in quoted attribute values.
|
||||||
func attrEscaper(args ...interface{}) string {
|
func attrEscaper(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeHTML {
|
if t == contentTypeHTML {
|
||||||
return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
|
return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
|
||||||
|
@ -30,7 +30,7 @@ func attrEscaper(args ...interface{}) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// rcdataEscaper escapes for inclusion in an RCDATA element body.
|
// rcdataEscaper escapes for inclusion in an RCDATA element body.
|
||||||
func rcdataEscaper(args ...interface{}) string {
|
func rcdataEscaper(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeHTML {
|
if t == contentTypeHTML {
|
||||||
return htmlReplacer(s, htmlNormReplacementTable, true)
|
return htmlReplacer(s, htmlNormReplacementTable, true)
|
||||||
|
@ -39,7 +39,7 @@ func rcdataEscaper(args ...interface{}) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// htmlEscaper escapes for inclusion in HTML text.
|
// htmlEscaper escapes for inclusion in HTML text.
|
||||||
func htmlEscaper(args ...interface{}) string {
|
func htmlEscaper(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeHTML {
|
if t == contentTypeHTML {
|
||||||
return s
|
return s
|
||||||
|
@ -225,7 +225,7 @@ func stripTags(html string) string {
|
||||||
|
|
||||||
// htmlNameFilter accepts valid parts of an HTML attribute or tag name or
|
// htmlNameFilter accepts valid parts of an HTML attribute or tag name or
|
||||||
// a known-safe HTML attribute.
|
// a known-safe HTML attribute.
|
||||||
func htmlNameFilter(args ...interface{}) string {
|
func htmlNameFilter(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeHTMLAttr {
|
if t == contentTypeHTMLAttr {
|
||||||
return s
|
return s
|
||||||
|
@ -260,6 +260,6 @@ func htmlNameFilter(args ...interface{}) string {
|
||||||
// content interpolated into comments.
|
// content interpolated into comments.
|
||||||
// This approach is equally valid whether or not static comment content is
|
// This approach is equally valid whether or not static comment content is
|
||||||
// removed from the template.
|
// removed from the template.
|
||||||
func commentEscaper(args ...interface{}) string {
|
func commentEscaper(args ...any) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
|
@ -123,7 +123,7 @@ var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
||||||
|
|
||||||
// indirectToJSONMarshaler returns the value, after dereferencing as many times
|
// indirectToJSONMarshaler returns the value, after dereferencing as many times
|
||||||
// as necessary to reach the base type (or nil) or an implementation of json.Marshal.
|
// as necessary to reach the base type (or nil) or an implementation of json.Marshal.
|
||||||
func indirectToJSONMarshaler(a interface{}) interface{} {
|
func indirectToJSONMarshaler(a any) any {
|
||||||
// text/template now supports passing untyped nil as a func call
|
// text/template now supports passing untyped nil as a func call
|
||||||
// argument, so we must support it. Otherwise we'd panic below, as one
|
// argument, so we must support it. Otherwise we'd panic below, as one
|
||||||
// cannot call the Type or Interface methods on an invalid
|
// cannot call the Type or Interface methods on an invalid
|
||||||
|
@ -133,7 +133,7 @@ func indirectToJSONMarshaler(a interface{}) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
v := reflect.ValueOf(a)
|
v := reflect.ValueOf(a)
|
||||||
for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
|
for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Pointer && !v.IsNil() {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
return v.Interface()
|
return v.Interface()
|
||||||
|
@ -141,8 +141,8 @@ func indirectToJSONMarshaler(a interface{}) interface{} {
|
||||||
|
|
||||||
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
|
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
|
||||||
// neither side-effects nor free variables outside (NaN, Infinity).
|
// neither side-effects nor free variables outside (NaN, Infinity).
|
||||||
func jsValEscaper(args ...interface{}) string {
|
func jsValEscaper(args ...any) string {
|
||||||
var a interface{}
|
var a any
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
a = indirectToJSONMarshaler(args[0])
|
a = indirectToJSONMarshaler(args[0])
|
||||||
switch t := a.(type) {
|
switch t := a.(type) {
|
||||||
|
@ -225,7 +225,7 @@ func jsValEscaper(args ...interface{}) string {
|
||||||
// jsStrEscaper produces a string that can be included between quotes in
|
// jsStrEscaper produces a string that can be included between quotes in
|
||||||
// JavaScript source, in JavaScript embedded in an HTML5 <script> element,
|
// JavaScript source, in JavaScript embedded in an HTML5 <script> element,
|
||||||
// or in an HTML5 event handler attribute such as onclick.
|
// or in an HTML5 event handler attribute such as onclick.
|
||||||
func jsStrEscaper(args ...interface{}) string {
|
func jsStrEscaper(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeJSStr {
|
if t == contentTypeJSStr {
|
||||||
return replace(s, jsStrNormReplacementTable)
|
return replace(s, jsStrNormReplacementTable)
|
||||||
|
@ -237,7 +237,7 @@ func jsStrEscaper(args ...interface{}) string {
|
||||||
// specials so the result is treated literally when included in a regular
|
// specials so the result is treated literally when included in a regular
|
||||||
// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
|
// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
|
||||||
// the literal text of {{.X}} followed by the string "bar".
|
// the literal text of {{.X}} followed by the string "bar".
|
||||||
func jsRegexpEscaper(args ...interface{}) string {
|
func jsRegexpEscaper(args ...any) string {
|
||||||
s, _ := stringify(args...)
|
s, _ := stringify(args...)
|
||||||
s = replace(s, jsRegexpReplacementTable)
|
s = replace(s, jsRegexpReplacementTable)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
@ -399,9 +399,7 @@ func isJSType(mimeType string) bool {
|
||||||
// https://tools.ietf.org/html/rfc4329#section-3
|
// https://tools.ietf.org/html/rfc4329#section-3
|
||||||
// https://www.ietf.org/rfc/rfc4627.txt
|
// https://www.ietf.org/rfc/rfc4627.txt
|
||||||
// discard parameters
|
// discard parameters
|
||||||
if i := strings.Index(mimeType, ";"); i >= 0 {
|
mimeType, _, _ = strings.Cut(mimeType, ";")
|
||||||
mimeType = mimeType[:i]
|
|
||||||
}
|
|
||||||
mimeType = strings.ToLower(mimeType)
|
mimeType = strings.ToLower(mimeType)
|
||||||
mimeType = strings.TrimSpace(mimeType)
|
mimeType = strings.TrimSpace(mimeType)
|
||||||
switch mimeType {
|
switch mimeType {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -105,7 +106,7 @@ func TestNextJsCtx(t *testing.T) {
|
||||||
|
|
||||||
func TestJSValEscaper(t *testing.T) {
|
func TestJSValEscaper(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
x interface{}
|
x any
|
||||||
js string
|
js string
|
||||||
}{
|
}{
|
||||||
{int(42), " 42 "},
|
{int(42), " 42 "},
|
||||||
|
@ -142,8 +143,8 @@ func TestJSValEscaper(t *testing.T) {
|
||||||
// "\v" == "v" on IE 6 so use "\u000b" instead.
|
// "\v" == "v" on IE 6 so use "\u000b" instead.
|
||||||
{"\t\x0b", `"\t\u000b"`},
|
{"\t\x0b", `"\t\u000b"`},
|
||||||
{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
|
{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
|
||||||
{[]interface{}{}, "[]"},
|
{[]any{}, "[]"},
|
||||||
{[]interface{}{42, "foo", nil}, `[42,"foo",null]`},
|
{[]any{42, "foo", nil}, `[42,"foo",null]`},
|
||||||
{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
|
{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
|
||||||
{"<!--", `"\u003c!--"`},
|
{"<!--", `"\u003c!--"`},
|
||||||
{"-->", `"--\u003e"`},
|
{"-->", `"--\u003e"`},
|
||||||
|
@ -160,7 +161,7 @@ func TestJSValEscaper(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Make sure that escaping corner cases are not broken
|
// Make sure that escaping corner cases are not broken
|
||||||
// by nesting.
|
// by nesting.
|
||||||
a := []interface{}{test.x}
|
a := []any{test.x}
|
||||||
want := "[" + strings.TrimSpace(test.js) + "]"
|
want := "[" + strings.TrimSpace(test.js) + "]"
|
||||||
if js := jsValEscaper(a); js != want {
|
if js := jsValEscaper(a); js != want {
|
||||||
t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
|
t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
|
||||||
|
@ -170,7 +171,7 @@ func TestJSValEscaper(t *testing.T) {
|
||||||
|
|
||||||
func TestJSStrEscaper(t *testing.T) {
|
func TestJSStrEscaper(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
x interface{}
|
x any
|
||||||
esc string
|
esc string
|
||||||
}{
|
}{
|
||||||
{"", ``},
|
{"", ``},
|
||||||
|
@ -225,7 +226,7 @@ func TestJSStrEscaper(t *testing.T) {
|
||||||
|
|
||||||
func TestJSRegexpEscaper(t *testing.T) {
|
func TestJSRegexpEscaper(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
x interface{}
|
x any
|
||||||
esc string
|
esc string
|
||||||
}{
|
}{
|
||||||
{"", `(?:)`},
|
{"", `(?:)`},
|
||||||
|
@ -280,7 +281,7 @@ func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
escaper func(...interface{}) string
|
escaper func(...any) string
|
||||||
escaped string
|
escaped string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
// Tests for multiple-template execution, copied from text/template.
|
// Tests for multiple-template execution, copied from text/template.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
|
@ -118,7 +118,7 @@ func (t *Template) escape() error {
|
||||||
// the output writer.
|
// the output writer.
|
||||||
// A template may be executed safely in parallel, although if parallel
|
// A template may be executed safely in parallel, although if parallel
|
||||||
// executions share a Writer the output may be interleaved.
|
// executions share a Writer the output may be interleaved.
|
||||||
func (t *Template) Execute(wr io.Writer, data interface{}) error {
|
func (t *Template) Execute(wr io.Writer, data any) error {
|
||||||
if err := t.escape(); err != nil {
|
if err := t.escape(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (t *Template) Execute(wr io.Writer, data interface{}) error {
|
||||||
// the output writer.
|
// the output writer.
|
||||||
// A template may be executed safely in parallel, although if parallel
|
// A template may be executed safely in parallel, although if parallel
|
||||||
// executions share a Writer the output may be interleaved.
|
// executions share a Writer the output may be interleaved.
|
||||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
|
||||||
tmpl, err := t.lookupAndEscapeTemplate(name)
|
tmpl, err := t.lookupAndEscapeTemplate(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -336,7 +336,7 @@ func (t *Template) Name() string {
|
||||||
// terminates and Execute returns that error. FuncMap has the same base type
|
// terminates and Execute returns that error. FuncMap has the same base type
|
||||||
// as FuncMap in "text/template", copied here so clients need not import
|
// as FuncMap in "text/template", copied here so clients need not import
|
||||||
// "text/template".
|
// "text/template".
|
||||||
type FuncMap map[string]interface{}
|
type FuncMap map[string]any
|
||||||
|
|
||||||
// Funcs adds the elements of the argument map to the template's function map.
|
// Funcs adds the elements of the argument map to the template's function map.
|
||||||
// It must be called before the template is parsed.
|
// It must be called before the template is parsed.
|
||||||
|
@ -487,7 +487,7 @@ func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||||
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||||
// and whether the value has a meaningful truth value. This is the definition of
|
// and whether the value has a meaningful truth value. This is the definition of
|
||||||
// truth used by if and other such actions.
|
// truth used by if and other such actions.
|
||||||
func IsTrue(val interface{}) (truth, ok bool) {
|
func IsTrue(val any) (truth, ok bool) {
|
||||||
return template.IsTrue(val)
|
return template.IsTrue(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
@ -209,7 +210,7 @@ func (c *testCase) mustNotParse(t *Template, text string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *testCase) mustExecute(t *Template, val interface{}, want string) {
|
func (c *testCase) mustExecute(t *Template, val any, want string) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := t.Execute(&buf, val)
|
err := t.Execute(&buf, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
|
@ -32,7 +32,7 @@ import (
|
||||||
// To allow URLs containing other schemes to bypass this filter, developers must
|
// To allow URLs containing other schemes to bypass this filter, developers must
|
||||||
// explicitly indicate that such a URL is expected and safe by encapsulating it
|
// explicitly indicate that such a URL is expected and safe by encapsulating it
|
||||||
// in a template.URL value.
|
// in a template.URL value.
|
||||||
func urlFilter(args ...interface{}) string {
|
func urlFilter(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeURL {
|
if t == contentTypeURL {
|
||||||
return s
|
return s
|
||||||
|
@ -46,9 +46,7 @@ func urlFilter(args ...interface{}) string {
|
||||||
// isSafeURL is true if s is a relative URL or if URL has a protocol in
|
// isSafeURL is true if s is a relative URL or if URL has a protocol in
|
||||||
// (http, https, mailto).
|
// (http, https, mailto).
|
||||||
func isSafeURL(s string) bool {
|
func isSafeURL(s string) bool {
|
||||||
if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
|
if protocol, _, ok := strings.Cut(s, ":"); ok && !strings.Contains(protocol, "/") {
|
||||||
|
|
||||||
protocol := s[:i]
|
|
||||||
if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
|
if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -58,7 +56,7 @@ func isSafeURL(s string) bool {
|
||||||
|
|
||||||
// urlEscaper produces an output that can be embedded in a URL query.
|
// urlEscaper produces an output that can be embedded in a URL query.
|
||||||
// The output can be embedded in an HTML attribute without further escaping.
|
// The output can be embedded in an HTML attribute without further escaping.
|
||||||
func urlEscaper(args ...interface{}) string {
|
func urlEscaper(args ...any) string {
|
||||||
return urlProcessor(false, args...)
|
return urlProcessor(false, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,13 +65,13 @@ func urlEscaper(args ...interface{}) string {
|
||||||
// The normalizer does not encode all HTML specials. Specifically, it does not
|
// The normalizer does not encode all HTML specials. Specifically, it does not
|
||||||
// encode '&' so correct embedding in an HTML attribute requires escaping of
|
// encode '&' so correct embedding in an HTML attribute requires escaping of
|
||||||
// '&' to '&'.
|
// '&' to '&'.
|
||||||
func urlNormalizer(args ...interface{}) string {
|
func urlNormalizer(args ...any) string {
|
||||||
return urlProcessor(true, args...)
|
return urlProcessor(true, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// urlProcessor normalizes (when norm is true) or escapes its input to produce
|
// urlProcessor normalizes (when norm is true) or escapes its input to produce
|
||||||
// a valid hierarchical or opaque URL part.
|
// a valid hierarchical or opaque URL part.
|
||||||
func urlProcessor(norm bool, args ...interface{}) string {
|
func urlProcessor(norm bool, args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
if t == contentTypeURL {
|
if t == contentTypeURL {
|
||||||
norm = true
|
norm = true
|
||||||
|
@ -143,7 +141,7 @@ func processURLOnto(s string, norm bool, b *bytes.Buffer) bool {
|
||||||
|
|
||||||
// Filters and normalizes srcset values which are comma separated
|
// Filters and normalizes srcset values which are comma separated
|
||||||
// URLs followed by metadata.
|
// URLs followed by metadata.
|
||||||
func srcsetFilterAndEscaper(args ...interface{}) string {
|
func srcsetFilterAndEscaper(args ...any) string {
|
||||||
s, t := stringify(args...)
|
s, t := stringify(args...)
|
||||||
switch t {
|
switch t {
|
||||||
case contentTypeSrcset:
|
case contentTypeSrcset:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -50,7 +51,7 @@ func TestURLFilters(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
escaper func(...interface{}) string
|
escaper func(...any) string
|
||||||
escaped string
|
escaped string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
package testenv
|
package testenv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
|
||||||
|
@ -22,6 +23,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Builder reports the name of the builder running this test
|
// Builder reports the name of the builder running this test
|
||||||
|
@ -306,3 +308,59 @@ func SkipIfShortAndSlow(t testing.TB) {
|
||||||
t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
|
t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunWithTimeout runs cmd and returns its combined output. If the
|
||||||
|
// subprocess exits with a non-zero status, it will log that status
|
||||||
|
// and return a non-nil error, but this is not considered fatal.
|
||||||
|
func RunWithTimeout(t testing.TB, cmd *exec.Cmd) ([]byte, error) {
|
||||||
|
args := cmd.Args
|
||||||
|
if args == nil {
|
||||||
|
args = []string{cmd.Path}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
cmd.Stdout = &b
|
||||||
|
cmd.Stderr = &b
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Fatalf("starting %s: %v", args, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the process doesn't complete within 1 minute,
|
||||||
|
// assume it is hanging and kill it to get a stack trace.
|
||||||
|
p := cmd.Process
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
scale := 1
|
||||||
|
// This GOARCH/GOOS test is copied from cmd/dist/test.go.
|
||||||
|
// TODO(iant): Have cmd/dist update the environment variable.
|
||||||
|
if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
|
||||||
|
scale = 2
|
||||||
|
}
|
||||||
|
if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
|
||||||
|
if sc, err := strconv.Atoi(s); err == nil {
|
||||||
|
scale = sc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(time.Duration(scale) * time.Minute):
|
||||||
|
p.Signal(Sigquit)
|
||||||
|
// If SIGQUIT doesn't do it after a little
|
||||||
|
// while, kill the process.
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(time.Duration(scale) * 30 * time.Second):
|
||||||
|
p.Signal(os.Kill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("%s exit status: %v", args, err)
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build cgo
|
//go:build cgo
|
||||||
|
|
||||||
package testenv
|
package testenv
|
||||||
|
|
||||||
|
|
13
tpl/internal/go_templates/testenv/testenv_notunix.go
Normal file
13
tpl/internal/go_templates/testenv/testenv_notunix.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build windows || plan9 || (js && wasm)
|
||||||
|
|
||||||
|
package testenv
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// Sigquit is the signal to send to kill a hanging subprocess.
|
||||||
|
// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill.
|
||||||
|
var Sigquit = os.Kill
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package testenv
|
package testenv
|
||||||
|
|
||||||
|
|
13
tpl/internal/go_templates/testenv/testenv_unix.go
Normal file
13
tpl/internal/go_templates/testenv/testenv_unix.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
|
|
||||||
|
package testenv
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// Sigquit is the signal to send to kill a hanging subprocess.
|
||||||
|
// Send SIGQUIT to get a stack trace.
|
||||||
|
var Sigquit = syscall.SIGQUIT
|
|
@ -112,6 +112,14 @@ data, defined in detail in the corresponding sections that follow.
|
||||||
T0 is executed; otherwise, dot is set to the successive elements
|
T0 is executed; otherwise, dot is set to the successive elements
|
||||||
of the array, slice, or map and T1 is executed.
|
of the array, slice, or map and T1 is executed.
|
||||||
|
|
||||||
|
{{break}}
|
||||||
|
The innermost {{range pipeline}} loop is ended early, stopping the
|
||||||
|
current iteration and bypassing all remaining iterations.
|
||||||
|
|
||||||
|
{{continue}}
|
||||||
|
The current iteration of the innermost {{range pipeline}} loop is
|
||||||
|
stopped, and the loop starts the next iteration.
|
||||||
|
|
||||||
{{template "name"}}
|
{{template "name"}}
|
||||||
The template with the specified name is executed with nil data.
|
The template with the specified name is executed with nil data.
|
||||||
|
|
||||||
|
@ -307,9 +315,10 @@ Predefined global functions are named as follows.
|
||||||
|
|
||||||
and
|
and
|
||||||
Returns the boolean AND of its arguments by returning the
|
Returns the boolean AND of its arguments by returning the
|
||||||
first empty argument or the last argument, that is,
|
first empty argument or the last argument. That is,
|
||||||
"and x y" behaves as "if x then y else x". All the
|
"and x y" behaves as "if x then y else x."
|
||||||
arguments are evaluated.
|
Evaluation proceeds through the arguments left to right
|
||||||
|
and returns when the result is determined.
|
||||||
call
|
call
|
||||||
Returns the result of calling the first argument, which
|
Returns the result of calling the first argument, which
|
||||||
must be a function, with the remaining arguments as parameters.
|
must be a function, with the remaining arguments as parameters.
|
||||||
|
@ -344,8 +353,9 @@ Predefined global functions are named as follows.
|
||||||
or
|
or
|
||||||
Returns the boolean OR of its arguments by returning the
|
Returns the boolean OR of its arguments by returning the
|
||||||
first non-empty argument or the last argument, that is,
|
first non-empty argument or the last argument, that is,
|
||||||
"or x y" behaves as "if x then x else y". All the
|
"or x y" behaves as "if x then x else y".
|
||||||
arguments are evaluated.
|
Evaluation proceeds through the arguments left to right
|
||||||
|
and returns when the result is determined.
|
||||||
print
|
print
|
||||||
An alias for fmt.Sprint
|
An alias for fmt.Sprint
|
||||||
printf
|
printf
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxExecDepth specifies the maximum stack depth of templates within
|
// maxExecDepth specifies the maximum stack depth of templates within
|
||||||
|
@ -126,7 +126,7 @@ func (e ExecError) Unwrap() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// errorf records an ExecError and terminates processing.
|
// errorf records an ExecError and terminates processing.
|
||||||
func (s *state) errorf(format string, args ...interface{}) {
|
func (s *state) errorf(format string, args ...any) {
|
||||||
name := doublePercent(s.tmpl.Name())
|
name := doublePercent(s.tmpl.Name())
|
||||||
if s.node == nil {
|
if s.node == nil {
|
||||||
format = fmt.Sprintf("template: %s: %s", name, format)
|
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||||
|
@ -179,7 +179,7 @@ func errRecover(errp *error) {
|
||||||
// the output writer.
|
// the output writer.
|
||||||
// A template may be executed safely in parallel, although if parallel
|
// A template may be executed safely in parallel, although if parallel
|
||||||
// executions share a Writer the output may be interleaved.
|
// executions share a Writer the output may be interleaved.
|
||||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
|
||||||
tmpl := t.Lookup(name)
|
tmpl := t.Lookup(name)
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||||
|
@ -197,11 +197,11 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
|
||||||
//
|
//
|
||||||
// If data is a reflect.Value, the template applies to the concrete
|
// If data is a reflect.Value, the template applies to the concrete
|
||||||
// value that the reflect.Value holds, as in fmt.Print.
|
// value that the reflect.Value holds, as in fmt.Print.
|
||||||
func (t *Template) Execute(wr io.Writer, data interface{}) error {
|
func (t *Template) Execute(wr io.Writer, data any) error {
|
||||||
return t.execute(wr, data)
|
return t.execute(wr, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
|
func (t *Template) execute(wr io.Writer, data any) (err error) {
|
||||||
defer errRecover(&err)
|
defer errRecover(&err)
|
||||||
value, ok := data.(reflect.Value)
|
value, ok := data.(reflect.Value)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -228,7 +228,6 @@ func (t *Template) DefinedTemplates() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
// temporary Hugo-fix
|
|
||||||
t.muTmpl.RLock()
|
t.muTmpl.RLock()
|
||||||
defer t.muTmpl.RUnlock()
|
defer t.muTmpl.RUnlock()
|
||||||
for name, tmpl := range t.tmpl {
|
for name, tmpl := range t.tmpl {
|
||||||
|
@ -245,6 +244,12 @@ func (t *Template) DefinedTemplates() string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sentinel errors for use with panic to signal early exits from range loops.
|
||||||
|
var (
|
||||||
|
walkBreak = errors.New("break")
|
||||||
|
walkContinue = errors.New("continue")
|
||||||
|
)
|
||||||
|
|
||||||
// Walk functions step through the major pieces of the template structure,
|
// Walk functions step through the major pieces of the template structure,
|
||||||
// generating output as they go.
|
// generating output as they go.
|
||||||
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||||
|
@ -257,7 +262,11 @@ func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||||
if len(node.Pipe.Decl) == 0 {
|
if len(node.Pipe.Decl) == 0 {
|
||||||
s.printValue(node, val)
|
s.printValue(node, val)
|
||||||
}
|
}
|
||||||
|
case *parse.BreakNode:
|
||||||
|
panic(walkBreak)
|
||||||
case *parse.CommentNode:
|
case *parse.CommentNode:
|
||||||
|
case *parse.ContinueNode:
|
||||||
|
panic(walkContinue)
|
||||||
case *parse.IfNode:
|
case *parse.IfNode:
|
||||||
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||||
case *parse.ListNode:
|
case *parse.ListNode:
|
||||||
|
@ -302,7 +311,7 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.
|
||||||
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||||
// and whether the value has a meaningful truth value. This is the definition of
|
// and whether the value has a meaningful truth value. This is the definition of
|
||||||
// truth used by if and other such actions.
|
// truth used by if and other such actions.
|
||||||
func IsTrue(val interface{}) (truth, ok bool) {
|
func IsTrue(val any) (truth, ok bool) {
|
||||||
return isTrue(reflect.ValueOf(val))
|
return isTrue(reflect.ValueOf(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +327,7 @@ func isTrueOld(val reflect.Value) (truth, ok bool) {
|
||||||
truth = val.Bool()
|
truth = val.Bool()
|
||||||
case reflect.Complex64, reflect.Complex128:
|
case reflect.Complex64, reflect.Complex128:
|
||||||
truth = val.Complex() != 0
|
truth = val.Complex() != 0
|
||||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
case reflect.Chan, reflect.Func, reflect.Pointer, reflect.Interface:
|
||||||
truth = !val.IsNil()
|
truth = !val.IsNil()
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
truth = val.Int() != 0
|
truth = val.Int() != 0
|
||||||
|
@ -336,6 +345,11 @@ func isTrueOld(val reflect.Value) (truth, ok bool) {
|
||||||
|
|
||||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
s.at(r)
|
s.at(r)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil && r != walkBreak {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
defer s.pop(s.mark())
|
defer s.pop(s.mark())
|
||||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||||
// mark top of stack before any variables in the body are pushed.
|
// mark top of stack before any variables in the body are pushed.
|
||||||
|
@ -349,8 +363,14 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
if len(r.Pipe.Decl) > 1 {
|
if len(r.Pipe.Decl) > 1 {
|
||||||
s.setTopVar(2, index)
|
s.setTopVar(2, index)
|
||||||
}
|
}
|
||||||
|
defer s.pop(mark)
|
||||||
|
defer func() {
|
||||||
|
// Consume panic(walkContinue)
|
||||||
|
if r := recover(); r != nil && r != walkContinue {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
s.walk(elem, r.List)
|
s.walk(elem, r.List)
|
||||||
s.pop(mark)
|
|
||||||
}
|
}
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
|
@ -574,11 +594,11 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ide
|
||||||
func (s *state) evalFunctionOld(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
func (s *state) evalFunctionOld(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
s.at(node)
|
s.at(node)
|
||||||
name := node.Ident
|
name := node.Ident
|
||||||
function, ok := findFunction(name, s.tmpl)
|
function, isBuiltin, ok := findFunction(name, s.tmpl)
|
||||||
if !ok {
|
if !ok {
|
||||||
s.errorf("%q is not a defined function", name)
|
s.errorf("%q is not a defined function", name)
|
||||||
}
|
}
|
||||||
return s.evalCall(dot, function, cmd, name, args, final)
|
return s.evalCall(dot, function, isBuiltin, cmd, name, args, final)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||||
|
@ -603,11 +623,11 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
|
||||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||||
// we see all methods of T and *T.
|
// we see all methods of T and *T.
|
||||||
ptr := receiver
|
ptr := receiver
|
||||||
if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
|
if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Pointer && ptr.CanAddr() {
|
||||||
ptr = ptr.Addr()
|
ptr = ptr.Addr()
|
||||||
}
|
}
|
||||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
return s.evalCall(dot, method, false, node, fieldName, args, final)
|
||||||
}
|
}
|
||||||
hasArgs := len(args) > 1 || final != missingVal
|
hasArgs := len(args) > 1 || final != missingVal
|
||||||
// It's not a method; must be a field of a struct or an element of a map.
|
// It's not a method; must be a field of a struct or an element of a map.
|
||||||
|
@ -615,10 +635,13 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
tField, ok := receiver.Type().FieldByName(fieldName)
|
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||||
if ok {
|
if ok {
|
||||||
field := receiver.FieldByIndex(tField.Index)
|
field, err := receiver.FieldByIndexErr(tField.Index)
|
||||||
if tField.PkgPath != "" { // field is unexported
|
if !tField.IsExported() {
|
||||||
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.errorf("%v", err)
|
||||||
|
}
|
||||||
// If it's a function, we must call it.
|
// If it's a function, we must call it.
|
||||||
if hasArgs {
|
if hasArgs {
|
||||||
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||||
|
@ -645,7 +668,7 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
case reflect.Ptr:
|
case reflect.Pointer:
|
||||||
etyp := receiver.Type().Elem()
|
etyp := receiver.Type().Elem()
|
||||||
if etyp.Kind() == reflect.Struct {
|
if etyp.Kind() == reflect.Struct {
|
||||||
if _, ok := etyp.FieldByName(fieldName); !ok {
|
if _, ok := etyp.FieldByName(fieldName); !ok {
|
||||||
|
@ -671,7 +694,7 @@ var (
|
||||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||||
// as the function itself.
|
// as the function itself.
|
||||||
func (s *state) evalCallOld(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
func (s *state) evalCallOld(dot, fun reflect.Value, isBuiltin bool, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
if args != nil {
|
if args != nil {
|
||||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||||
}
|
}
|
||||||
|
@ -693,6 +716,38 @@ func (s *state) evalCallOld(dot, fun reflect.Value, node parse.Node, name string
|
||||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unwrap := func(v reflect.Value) reflect.Value {
|
||||||
|
if v.Type() == reflectValueType {
|
||||||
|
v = v.Interface().(reflect.Value)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for builtin and/or, which short-circuit.
|
||||||
|
if isBuiltin && (name == "and" || name == "or") {
|
||||||
|
argType := typ.In(0)
|
||||||
|
var v reflect.Value
|
||||||
|
for _, arg := range args {
|
||||||
|
v = s.evalArg(dot, argType, arg).Interface().(reflect.Value)
|
||||||
|
if truth(v) == (name == "or") {
|
||||||
|
// This value was already unwrapped
|
||||||
|
// by the .Interface().(reflect.Value).
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if final != missingVal {
|
||||||
|
// The last argument to and/or is coming from
|
||||||
|
// the pipeline. We didn't short circuit on an earlier
|
||||||
|
// argument, so we are going to return this one.
|
||||||
|
// We don't have to evaluate final, but we do
|
||||||
|
// have to check its type. Then, since we are
|
||||||
|
// going to return it, we have to unwrap it.
|
||||||
|
v = unwrap(s.validateType(final, argType))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// Build the arg list.
|
// Build the arg list.
|
||||||
argv := make([]reflect.Value, numIn)
|
argv := make([]reflect.Value, numIn)
|
||||||
// Args must be evaluated. Fixed args first.
|
// Args must be evaluated. Fixed args first.
|
||||||
|
@ -728,18 +783,15 @@ func (s *state) evalCallOld(dot, fun reflect.Value, node parse.Node, name string
|
||||||
// error to the caller.
|
// error to the caller.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.at(node)
|
s.at(node)
|
||||||
s.errorf("error calling %s: %v", name, err)
|
s.errorf("error calling %s: %w", name, err)
|
||||||
}
|
}
|
||||||
if v.Type() == reflectValueType {
|
return unwrap(v)
|
||||||
v = v.Interface().(reflect.Value)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||||
func canBeNil(typ reflect.Type) bool {
|
func canBeNil(typ reflect.Type) bool {
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
||||||
return true
|
return true
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return typ == reflectValueType
|
return typ == reflectValueType
|
||||||
|
@ -776,15 +828,13 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
|
||||||
// are much more constrained, so it makes more sense there than here.
|
// are much more constrained, so it makes more sense there than here.
|
||||||
// Besides, one is almost always all you need.
|
// Besides, one is almost always all you need.
|
||||||
switch {
|
switch {
|
||||||
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
case value.Kind() == reflect.Pointer && value.Type().Elem().AssignableTo(typ):
|
||||||
value = value.Elem()
|
value = value.Elem()
|
||||||
if !value.IsValid() {
|
if !value.IsValid() {
|
||||||
s.errorf("dereference of nil pointer of type %s", typ)
|
s.errorf("dereference of nil pointer of type %s", typ)
|
||||||
}
|
}
|
||||||
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
case reflect.PointerTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||||
value = value.Addr()
|
value = value.Addr()
|
||||||
case value.IsZero():
|
|
||||||
s.errorf("got <nil>, expected %s", typ)
|
|
||||||
default:
|
default:
|
||||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||||
}
|
}
|
||||||
|
@ -935,7 +985,7 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu
|
||||||
// if it's nil. If the returned bool is true, the returned value's kind will be
|
// if it's nil. If the returned bool is true, the returned value's kind will be
|
||||||
// either a pointer or interface.
|
// either a pointer or interface.
|
||||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
for ; v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return v, true
|
return v, true
|
||||||
}
|
}
|
||||||
|
@ -973,8 +1023,8 @@ func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||||
|
|
||||||
// printableValue returns the, possibly indirected, interface value inside v that
|
// printableValue returns the, possibly indirected, interface value inside v that
|
||||||
// is best for a call to formatted printer.
|
// is best for a call to formatted printer.
|
||||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
func printableValue(v reflect.Value) (any, bool) {
|
||||||
if v.Kind() == reflect.Ptr {
|
if v.Kind() == reflect.Pointer {
|
||||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||||
}
|
}
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
|
@ -982,7 +1032,7 @@ func printableValue(v reflect.Value) (interface{}, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
if v.CanAddr() && (reflect.PointerTo(v.Type()).Implements(errorType) || reflect.PointerTo(v.Type()).Implements(fmtStringerType)) {
|
||||||
v = v.Addr()
|
v = v.Addr()
|
||||||
} else {
|
} else {
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,7 +49,7 @@ type T struct {
|
||||||
MSI map[string]int
|
MSI map[string]int
|
||||||
MSIone map[string]int // one element, for deterministic output
|
MSIone map[string]int // one element, for deterministic output
|
||||||
MSIEmpty map[string]int
|
MSIEmpty map[string]int
|
||||||
MXI map[interface{}]int
|
MXI map[any]int
|
||||||
MII map[int]int
|
MII map[int]int
|
||||||
MI32S map[int32]string
|
MI32S map[int32]string
|
||||||
MI64S map[int64]string
|
MI64S map[int64]string
|
||||||
|
@ -57,11 +59,11 @@ type T struct {
|
||||||
MUI8S map[uint8]string
|
MUI8S map[uint8]string
|
||||||
SMSI []map[string]int
|
SMSI []map[string]int
|
||||||
// Empty interfaces; used to see if we can dig inside one.
|
// Empty interfaces; used to see if we can dig inside one.
|
||||||
Empty0 interface{} // nil
|
Empty0 any // nil
|
||||||
Empty1 interface{}
|
Empty1 any
|
||||||
Empty2 interface{}
|
Empty2 any
|
||||||
Empty3 interface{}
|
Empty3 any
|
||||||
Empty4 interface{}
|
Empty4 any
|
||||||
// Non-empty interfaces.
|
// Non-empty interfaces.
|
||||||
NonEmptyInterface I
|
NonEmptyInterface I
|
||||||
NonEmptyInterfacePtS *I
|
NonEmptyInterfacePtS *I
|
||||||
|
@ -139,7 +141,7 @@ var tVal = &T{
|
||||||
SB: []bool{true, false},
|
SB: []bool{true, false},
|
||||||
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
|
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
|
||||||
MSIone: map[string]int{"one": 1},
|
MSIone: map[string]int{"one": 1},
|
||||||
MXI: map[interface{}]int{"one": 1},
|
MXI: map[any]int{"one": 1},
|
||||||
MII: map[int]int{1: 1},
|
MII: map[int]int{1: 1},
|
||||||
MI32S: map[int32]string{1: "one", 2: "two"},
|
MI32S: map[int32]string{1: "one", 2: "two"},
|
||||||
MI64S: map[int64]string{2: "i642", 3: "i643"},
|
MI64S: map[int64]string{2: "i642", 3: "i643"},
|
||||||
|
@ -210,7 +212,7 @@ func (t *T) Method2(a uint16, b string) string {
|
||||||
return fmt.Sprintf("Method2: %d %s", a, b)
|
return fmt.Sprintf("Method2: %d %s", a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *T) Method3(v interface{}) string {
|
func (t *T) Method3(v any) string {
|
||||||
return fmt.Sprintf("Method3: %v", v)
|
return fmt.Sprintf("Method3: %v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +252,7 @@ func (u *U) TrueFalse(b bool) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func typeOf(arg interface{}) string {
|
func typeOf(arg any) string {
|
||||||
return fmt.Sprintf("%T", arg)
|
return fmt.Sprintf("%T", arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +260,7 @@ type execTest struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
output string
|
output string
|
||||||
data interface{}
|
data any
|
||||||
ok bool
|
ok bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +393,7 @@ var execTests = []execTest{
|
||||||
{".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true},
|
{".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true},
|
||||||
{"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true},
|
{"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true},
|
||||||
{"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
|
{"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
|
||||||
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true},
|
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]any{"S": bytes.NewBufferString("foozle")}, true},
|
||||||
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
|
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
|
||||||
{"call nil", "{{call nil}}", "", tVal, false},
|
{"call nil", "{{call nil}}", "", tVal, false},
|
||||||
|
|
||||||
|
@ -482,8 +484,19 @@ var execTests = []execTest{
|
||||||
{"not", "{{not true}} {{not false}}", "false true", nil, true},
|
{"not", "{{not true}} {{not false}}", "false true", nil, true},
|
||||||
{"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true},
|
{"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true},
|
||||||
{"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true},
|
{"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true},
|
||||||
|
{"or short-circuit", "{{or 0 1 (die)}}", "1", nil, true},
|
||||||
|
{"and short-circuit", "{{and 1 0 (die)}}", "0", nil, true},
|
||||||
|
{"or short-circuit2", "{{or 0 0 (die)}}", "", nil, false},
|
||||||
|
{"and short-circuit2", "{{and 1 1 (die)}}", "", nil, false},
|
||||||
|
{"and pipe-true", "{{1 | and 1}}", "1", nil, true},
|
||||||
|
{"and pipe-false", "{{0 | and 1}}", "0", nil, true},
|
||||||
|
{"or pipe-true", "{{1 | or 0}}", "1", nil, true},
|
||||||
|
{"or pipe-false", "{{0 | or 0}}", "0", nil, true},
|
||||||
|
{"and undef", "{{and 1 .Unknown}}", "<no value>", nil, true},
|
||||||
|
{"or undef", "{{or 0 .Unknown}}", "<no value>", nil, true},
|
||||||
{"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
|
{"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
|
||||||
{"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
|
{"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
|
||||||
|
{"boolean if pipe", "{{if true | not | and 1}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
|
||||||
|
|
||||||
// Indexing.
|
// Indexing.
|
||||||
{"slice[0]", "{{index .SI 0}}", "3", tVal, true},
|
{"slice[0]", "{{index .SI 0}}", "3", tVal, true},
|
||||||
|
@ -565,6 +578,8 @@ var execTests = []execTest{
|
||||||
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
|
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
|
||||||
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
||||||
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
||||||
|
{"range []int break else", "{{range .SI}}-{{.}}-{{break}}NOTREACHED{{else}}EMPTY{{end}}", "-3-", tVal, true},
|
||||||
|
{"range []int continue else", "{{range .SI}}-{{.}}-{{continue}}NOTREACHED{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
|
||||||
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
|
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
|
||||||
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
|
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
|
||||||
{"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
|
{"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
|
||||||
|
@ -736,7 +751,7 @@ func add(args ...int) int {
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
func echo(arg interface{}) interface{} {
|
func echo(arg any) any {
|
||||||
return arg
|
return arg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,7 +770,7 @@ func stringer(s fmt.Stringer) string {
|
||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapOfThree() interface{} {
|
func mapOfThree() any {
|
||||||
return map[string]int{"three": 3}
|
return map[string]int{"three": 3}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,6 +780,7 @@ func testExecute(execTests []execTest, template *Template, t *testing.T) {
|
||||||
"add": add,
|
"add": add,
|
||||||
"count": count,
|
"count": count,
|
||||||
"dddArg": dddArg,
|
"dddArg": dddArg,
|
||||||
|
"die": func() bool { panic("die") },
|
||||||
"echo": echo,
|
"echo": echo,
|
||||||
"makemap": makemap,
|
"makemap": makemap,
|
||||||
"mapOfThree": mapOfThree,
|
"mapOfThree": mapOfThree,
|
||||||
|
@ -904,6 +920,28 @@ func TestExecError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomError struct{}
|
||||||
|
|
||||||
|
func (*CustomError) Error() string { return "heyo !" }
|
||||||
|
|
||||||
|
// Check that a custom error can be returned.
|
||||||
|
func TestExecError_CustomError(t *testing.T) {
|
||||||
|
failingFunc := func() (string, error) {
|
||||||
|
return "", &CustomError{}
|
||||||
|
}
|
||||||
|
tmpl := Must(New("top").Funcs(FuncMap{
|
||||||
|
"err": failingFunc,
|
||||||
|
}).Parse("{{ err }}"))
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := tmpl.Execute(&b, nil)
|
||||||
|
|
||||||
|
var e *CustomError
|
||||||
|
if !errors.As(err, &e) {
|
||||||
|
t.Fatalf("expected custom error; got %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestJSEscaping(t *testing.T) {
|
func TestJSEscaping(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
in, exp string
|
in, exp string
|
||||||
|
@ -1180,8 +1218,11 @@ var cmpTests = []cmpTest{
|
||||||
{"eq .Ptr .NilPtr", "false", true},
|
{"eq .Ptr .NilPtr", "false", true},
|
||||||
{"eq .NilPtr .NilPtr", "true", true},
|
{"eq .NilPtr .NilPtr", "true", true},
|
||||||
{"eq .Iface1 .Iface1", "true", true},
|
{"eq .Iface1 .Iface1", "true", true},
|
||||||
{"eq .Iface1 .Iface2", "false", true},
|
{"eq .Iface1 .NilIface", "false", true},
|
||||||
{"eq .Iface2 .Iface2", "true", true},
|
{"eq .NilIface .NilIface", "true", true},
|
||||||
|
{"eq .NilIface .Iface1", "false", true},
|
||||||
|
{"eq .NilIface 0", "false", true},
|
||||||
|
{"eq 0 .NilIface", "false", true},
|
||||||
// Errors
|
// Errors
|
||||||
{"eq `xy` 1", "", false}, // Different types.
|
{"eq `xy` 1", "", false}, // Different types.
|
||||||
{"eq 2 2.0", "", false}, // Different types.
|
{"eq 2 2.0", "", false}, // Different types.
|
||||||
|
@ -1201,7 +1242,7 @@ func TestComparison(t *testing.T) {
|
||||||
Ptr, NilPtr *int
|
Ptr, NilPtr *int
|
||||||
Map map[int]int
|
Map map[int]int
|
||||||
V1, V2 V
|
V1, V2 V
|
||||||
Iface1, Iface2 fmt.Stringer
|
Iface1, NilIface fmt.Stringer
|
||||||
}{
|
}{
|
||||||
Uthree: 3,
|
Uthree: 3,
|
||||||
Ufour: 4,
|
Ufour: 4,
|
||||||
|
@ -1430,7 +1471,7 @@ func TestBlock(t *testing.T) {
|
||||||
func TestEvalFieldErrors(t *testing.T) {
|
func TestEvalFieldErrors(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name, src string
|
name, src string
|
||||||
value interface{}
|
value any
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -1573,7 +1614,7 @@ func TestInterfaceValues(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
tmpl := Must(New("tmpl").Parse(tt.text))
|
tmpl := Must(New("tmpl").Parse(tt.text))
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := tmpl.Execute(&buf, map[string]interface{}{
|
err := tmpl.Execute(&buf, map[string]any{
|
||||||
"PlusOne": func(n int) int {
|
"PlusOne": func(n int) int {
|
||||||
return n + 1
|
return n + 1
|
||||||
},
|
},
|
||||||
|
@ -1602,7 +1643,7 @@ func TestInterfaceValues(t *testing.T) {
|
||||||
|
|
||||||
// Check that panics during calls are recovered and returned as errors.
|
// Check that panics during calls are recovered and returned as errors.
|
||||||
func TestExecutePanicDuringCall(t *testing.T) {
|
func TestExecutePanicDuringCall(t *testing.T) {
|
||||||
funcs := map[string]interface{}{
|
funcs := map[string]any{
|
||||||
"doPanic": func() string {
|
"doPanic": func() string {
|
||||||
panic("custom panic string")
|
panic("custom panic string")
|
||||||
},
|
},
|
||||||
|
@ -1610,7 +1651,7 @@ func TestExecutePanicDuringCall(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
data interface{}
|
data any
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -1712,3 +1753,63 @@ func TestIssue43065(t *testing.T) {
|
||||||
t.Errorf("%s", err)
|
t.Errorf("%s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 39807: data race in html/template & text/template
|
||||||
|
func TestIssue39807(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
tplFoo, err := New("foo").Parse(`{{ template "bar" . }}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tplBar, err := New("bar").Parse("bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gofuncs := 10
|
||||||
|
numTemplates := 10
|
||||||
|
|
||||||
|
for i := 1; i <= gofuncs; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for j := 0; j < numTemplates; j++ {
|
||||||
|
_, err := tplFoo.AddParseTree(tplBar.Name(), tplBar.Tree)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = tplFoo.Execute(io.Discard, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 48215: embedded nil pointer causes panic.
|
||||||
|
// Fixed by adding FieldByIndexErr to the reflect package.
|
||||||
|
func TestIssue48215(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
type B struct {
|
||||||
|
*A
|
||||||
|
}
|
||||||
|
tmpl, err := New("").Parse(`{{ .S }}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(io.Discard, B{})
|
||||||
|
// We expect an error, not a panic.
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("did not get error for nil embedded struct")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "reflect: indirection through nil pointer to embedded struct field A") {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,12 +23,15 @@ import (
|
||||||
// return value evaluates to non-nil during execution, execution terminates and
|
// return value evaluates to non-nil during execution, execution terminates and
|
||||||
// Execute returns that error.
|
// Execute returns that error.
|
||||||
//
|
//
|
||||||
|
// Errors returned by Execute wrap the underlying error; call errors.As to
|
||||||
|
// uncover them.
|
||||||
|
//
|
||||||
// When template execution invokes a function with an argument list, that list
|
// When template execution invokes a function with an argument list, that list
|
||||||
// must be assignable to the function's parameter types. Functions meant to
|
// must be assignable to the function's parameter types. Functions meant to
|
||||||
// apply to arguments of arbitrary type can use parameters of type interface{} or
|
// apply to arguments of arbitrary type can use parameters of type interface{} or
|
||||||
// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
|
// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
|
||||||
// type can return interface{} or reflect.Value.
|
// type can return interface{} or reflect.Value.
|
||||||
type FuncMap map[string]interface{}
|
type FuncMap map[string]any
|
||||||
|
|
||||||
// builtins returns the FuncMap.
|
// builtins returns the FuncMap.
|
||||||
// It is not a global variable so the linker can dead code eliminate
|
// It is not a global variable so the linker can dead code eliminate
|
||||||
|
@ -136,18 +139,18 @@ func goodName(name string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// findFunction looks for a function in the template, and global map.
|
// findFunction looks for a function in the template, and global map.
|
||||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
func findFunction(name string, tmpl *Template) (v reflect.Value, isBuiltin, ok bool) {
|
||||||
if tmpl != nil && tmpl.common != nil {
|
if tmpl != nil && tmpl.common != nil {
|
||||||
tmpl.muFuncs.RLock()
|
tmpl.muFuncs.RLock()
|
||||||
defer tmpl.muFuncs.RUnlock()
|
defer tmpl.muFuncs.RUnlock()
|
||||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||||
return fn, true
|
return fn, false, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fn := builtinFuncs()[name]; fn.IsValid() {
|
if fn := builtinFuncs()[name]; fn.IsValid() {
|
||||||
return fn, true
|
return fn, true, true
|
||||||
}
|
}
|
||||||
return reflect.Value{}, false
|
return reflect.Value{}, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareArg checks if value can be used as an argument of type argType, and
|
// prepareArg checks if value can be used as an argument of type argType, and
|
||||||
|
@ -344,7 +347,7 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if argv[i], err = prepareArg(arg, argType); err != nil {
|
if argv[i], err = prepareArg(arg, argType); err != nil {
|
||||||
return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
|
return reflect.Value{}, fmt.Errorf("arg %d: %w", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return safeCall(fn, argv)
|
return safeCall(fn, argv)
|
||||||
|
@ -379,31 +382,13 @@ func truth(arg reflect.Value) bool {
|
||||||
// and computes the Boolean AND of its arguments, returning
|
// and computes the Boolean AND of its arguments, returning
|
||||||
// the first false argument it encounters, or the last argument.
|
// the first false argument it encounters, or the last argument.
|
||||||
func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
|
func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
|
||||||
if !truth(arg0) {
|
panic("unreachable") // implemented as a special case in evalCall
|
||||||
return arg0
|
|
||||||
}
|
|
||||||
for i := range args {
|
|
||||||
arg0 = args[i]
|
|
||||||
if !truth(arg0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arg0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// or computes the Boolean OR of its arguments, returning
|
// or computes the Boolean OR of its arguments, returning
|
||||||
// the first true argument it encounters, or the last argument.
|
// the first true argument it encounters, or the last argument.
|
||||||
func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
|
func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
|
||||||
if truth(arg0) {
|
panic("unreachable") // implemented as a special case in evalCall
|
||||||
return arg0
|
|
||||||
}
|
|
||||||
for i := range args {
|
|
||||||
arg0 = args[i]
|
|
||||||
if truth(arg0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arg0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// not returns the Boolean negation of its argument.
|
// not returns the Boolean negation of its argument.
|
||||||
|
@ -475,8 +460,10 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
||||||
case k1 == uintKind && k2 == intKind:
|
case k1 == uintKind && k2 == intKind:
|
||||||
truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
|
truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
|
||||||
default:
|
default:
|
||||||
|
if arg1 != zero && arg != zero {
|
||||||
return false, errBadComparison
|
return false, errBadComparison
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch k1 {
|
switch k1 {
|
||||||
case boolKind:
|
case boolKind:
|
||||||
|
@ -492,7 +479,7 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
||||||
case uintKind:
|
case uintKind:
|
||||||
truth = arg1.Uint() == arg.Uint()
|
truth = arg1.Uint() == arg.Uint()
|
||||||
default:
|
default:
|
||||||
if arg == zero {
|
if arg == zero || arg1 == zero {
|
||||||
truth = arg1 == arg
|
truth = arg1 == arg
|
||||||
} else {
|
} else {
|
||||||
if t2 := arg.Type(); !t2.Comparable() {
|
if t2 := arg.Type(); !t2.Comparable() {
|
||||||
|
@ -640,7 +627,7 @@ func HTMLEscapeString(s string) string {
|
||||||
|
|
||||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||||
// representation of its arguments.
|
// representation of its arguments.
|
||||||
func HTMLEscaper(args ...interface{}) string {
|
func HTMLEscaper(args ...any) string {
|
||||||
return HTMLEscapeString(evalArgs(args))
|
return HTMLEscapeString(evalArgs(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,13 +718,13 @@ func jsIsSpecial(r rune) bool {
|
||||||
|
|
||||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||||
// representation of its arguments.
|
// representation of its arguments.
|
||||||
func JSEscaper(args ...interface{}) string {
|
func JSEscaper(args ...any) string {
|
||||||
return JSEscapeString(evalArgs(args))
|
return JSEscapeString(evalArgs(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||||
// its arguments in a form suitable for embedding in a URL query.
|
// its arguments in a form suitable for embedding in a URL query.
|
||||||
func URLQueryEscaper(args ...interface{}) string {
|
func URLQueryEscaper(args ...any) string {
|
||||||
return url.QueryEscape(evalArgs(args))
|
return url.QueryEscape(evalArgs(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,7 +733,7 @@ func URLQueryEscaper(args ...interface{}) string {
|
||||||
// except that each argument is indirected (if a pointer), as required,
|
// except that each argument is indirected (if a pointer), as required,
|
||||||
// using the same rules as the default string evaluation during template
|
// using the same rules as the default string evaluation during template
|
||||||
// execution.
|
// execution.
|
||||||
func evalArgs(args []interface{}) string {
|
func evalArgs(args []any) string {
|
||||||
ok := false
|
ok := false
|
||||||
var s string
|
var s string
|
||||||
// Fast path for simple common case.
|
// Fast path for simple common case.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package template_test
|
package template_test
|
||||||
|
@ -41,11 +42,7 @@ func main() {
|
||||||
t.Used()
|
t.Used()
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
td, err := os.MkdirTemp("", "text_template_TestDeadCodeElimination")
|
td := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(td)
|
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil {
|
if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13 && !windows
|
||||||
// +build go1.13,!windows
|
// +build go1.13,!windows
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
@ -454,3 +455,13 @@ func TestIssue19294(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 48436
|
||||||
|
func TestAddToZeroTemplate(t *testing.T) {
|
||||||
|
tree, err := parse.Parse("c", cloneText3, "", "", nil, builtins())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var tmpl Template
|
||||||
|
tmpl.AddParseTree("x", tree["c"])
|
||||||
|
}
|
||||||
|
|
|
@ -51,13 +51,11 @@ func (t *Template) setOption(opt string) {
|
||||||
if opt == "" {
|
if opt == "" {
|
||||||
panic("empty option string")
|
panic("empty option string")
|
||||||
}
|
}
|
||||||
elems := strings.Split(opt, "=")
|
|
||||||
switch len(elems) {
|
|
||||||
case 2:
|
|
||||||
// key=value
|
// key=value
|
||||||
switch elems[0] {
|
if key, value, ok := strings.Cut(opt, "="); ok {
|
||||||
|
switch key {
|
||||||
case "missingkey":
|
case "missingkey":
|
||||||
switch elems[1] {
|
switch value {
|
||||||
case "invalid", "default":
|
case "invalid", "default":
|
||||||
t.option.missingKey = mapInvalid
|
t.option.missingKey = mapInvalid
|
||||||
return
|
return
|
||||||
|
|
|
@ -62,6 +62,8 @@ const (
|
||||||
// Keywords appear after all the rest.
|
// Keywords appear after all the rest.
|
||||||
itemKeyword // used only to delimit the keywords
|
itemKeyword // used only to delimit the keywords
|
||||||
itemBlock // block keyword
|
itemBlock // block keyword
|
||||||
|
itemBreak // break keyword
|
||||||
|
itemContinue // continue keyword
|
||||||
itemDot // the cursor, spelled '.'
|
itemDot // the cursor, spelled '.'
|
||||||
itemDefine // define keyword
|
itemDefine // define keyword
|
||||||
itemElse // else keyword
|
itemElse // else keyword
|
||||||
|
@ -76,6 +78,8 @@ const (
|
||||||
var key = map[string]itemType{
|
var key = map[string]itemType{
|
||||||
".": itemDot,
|
".": itemDot,
|
||||||
"block": itemBlock,
|
"block": itemBlock,
|
||||||
|
"break": itemBreak,
|
||||||
|
"continue": itemContinue,
|
||||||
"define": itemDefine,
|
"define": itemDefine,
|
||||||
"else": itemElse,
|
"else": itemElse,
|
||||||
"end": itemEnd,
|
"end": itemEnd,
|
||||||
|
@ -119,6 +123,8 @@ type lexer struct {
|
||||||
parenDepth int // nesting depth of ( ) exprs
|
parenDepth int // nesting depth of ( ) exprs
|
||||||
line int // 1+number of newlines seen
|
line int // 1+number of newlines seen
|
||||||
startLine int // start line of this item
|
startLine int // start line of this item
|
||||||
|
breakOK bool // break keyword allowed
|
||||||
|
continueOK bool // continue keyword allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
// next returns the next rune in the input.
|
// next returns the next rune in the input.
|
||||||
|
@ -184,7 +190,7 @@ func (l *lexer) acceptRun(valid string) {
|
||||||
|
|
||||||
// errorf returns an error token and terminates the scan by passing
|
// errorf returns an error token and terminates the scan by passing
|
||||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
func (l *lexer) errorf(format string, args ...any) stateFn {
|
||||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
|
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -461,7 +467,12 @@ Loop:
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case key[word] > itemKeyword:
|
case key[word] > itemKeyword:
|
||||||
l.emit(key[word])
|
item := key[word]
|
||||||
|
if item == itemBreak && !l.breakOK || item == itemContinue && !l.continueOK {
|
||||||
|
l.emit(itemIdentifier)
|
||||||
|
} else {
|
||||||
|
l.emit(item)
|
||||||
|
}
|
||||||
case word[0] == '.':
|
case word[0] == '.':
|
||||||
l.emit(itemField)
|
l.emit(itemField)
|
||||||
case word == "true", word == "false":
|
case word == "true", word == "false":
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package parse
|
package parse
|
||||||
|
@ -37,6 +38,8 @@ var itemName = map[itemType]string{
|
||||||
// keywords
|
// keywords
|
||||||
itemDot: ".",
|
itemDot: ".",
|
||||||
itemBlock: "block",
|
itemBlock: "block",
|
||||||
|
itemBreak: "break",
|
||||||
|
itemContinue: "continue",
|
||||||
itemDefine: "define",
|
itemDefine: "define",
|
||||||
itemElse: "else",
|
itemElse: "else",
|
||||||
itemIf: "if",
|
itemIf: "if",
|
||||||
|
|
|
@ -71,6 +71,8 @@ const (
|
||||||
NodeVariable // A $ variable.
|
NodeVariable // A $ variable.
|
||||||
NodeWith // A with action.
|
NodeWith // A with action.
|
||||||
NodeComment // A comment.
|
NodeComment // A comment.
|
||||||
|
NodeBreak // A break action.
|
||||||
|
NodeContinue // A continue action.
|
||||||
)
|
)
|
||||||
|
|
||||||
// Nodes.
|
// Nodes.
|
||||||
|
@ -907,6 +909,40 @@ func (i *IfNode) Copy() Node {
|
||||||
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BreakNode represents a {{break}} action.
|
||||||
|
type BreakNode struct {
|
||||||
|
tr *Tree
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newBreak(pos Pos, line int) *BreakNode {
|
||||||
|
return &BreakNode{tr: t, NodeType: NodeBreak, Pos: pos, Line: line}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BreakNode) Copy() Node { return b.tr.newBreak(b.Pos, b.Line) }
|
||||||
|
func (b *BreakNode) String() string { return "{{break}}" }
|
||||||
|
func (b *BreakNode) tree() *Tree { return b.tr }
|
||||||
|
func (b *BreakNode) writeTo(sb *strings.Builder) { sb.WriteString("{{break}}") }
|
||||||
|
|
||||||
|
// ContinueNode represents a {{continue}} action.
|
||||||
|
type ContinueNode struct {
|
||||||
|
tr *Tree
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newContinue(pos Pos, line int) *ContinueNode {
|
||||||
|
return &ContinueNode{tr: t, NodeType: NodeContinue, Pos: pos, Line: line}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContinueNode) Copy() Node { return c.tr.newContinue(c.Pos, c.Line) }
|
||||||
|
func (c *ContinueNode) String() string { return "{{continue}}" }
|
||||||
|
func (c *ContinueNode) tree() *Tree { return c.tr }
|
||||||
|
func (c *ContinueNode) writeTo(sb *strings.Builder) { sb.WriteString("{{continue}}") }
|
||||||
|
|
||||||
// RangeNode represents a {{range}} action and its commands.
|
// RangeNode represents a {{range}} action and its commands.
|
||||||
type RangeNode struct {
|
type RangeNode struct {
|
||||||
BranchNode
|
BranchNode
|
||||||
|
|
|
@ -24,14 +24,14 @@ type Tree struct {
|
||||||
Mode Mode // parsing mode.
|
Mode Mode // parsing mode.
|
||||||
text string // text parsed to create the template (or its parent)
|
text string // text parsed to create the template (or its parent)
|
||||||
// Parsing only; cleared after parse.
|
// Parsing only; cleared after parse.
|
||||||
funcs []map[string]interface{}
|
funcs []map[string]any
|
||||||
lex *lexer
|
lex *lexer
|
||||||
token [3]item // three-token lookahead for parser.
|
token [3]item // three-token lookahead for parser.
|
||||||
peekCount int
|
peekCount int
|
||||||
vars []string // variables defined at the moment.
|
vars []string // variables defined at the moment.
|
||||||
treeSet map[string]*Tree
|
treeSet map[string]*Tree
|
||||||
actionLine int // line of left delim starting action
|
actionLine int // line of left delim starting action
|
||||||
mode Mode
|
rangeDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
// A mode value is a set of flags (or 0). Modes control parser behavior.
|
// A mode value is a set of flags (or 0). Modes control parser behavior.
|
||||||
|
@ -39,6 +39,7 @@ type Mode uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ParseComments Mode = 1 << iota // parse comments and add them to AST
|
ParseComments Mode = 1 << iota // parse comments and add them to AST
|
||||||
|
SkipFuncCheck // do not check that functions are defined
|
||||||
)
|
)
|
||||||
|
|
||||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||||
|
@ -58,7 +59,7 @@ func (t *Tree) Copy() *Tree {
|
||||||
// templates described in the argument string. The top-level template will be
|
// templates described in the argument string. The top-level template will be
|
||||||
// given the specified name. If an error is encountered, parsing stops and an
|
// given the specified name. If an error is encountered, parsing stops and an
|
||||||
// empty map is returned with the error.
|
// empty map is returned with the error.
|
||||||
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error) {
|
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]any) (map[string]*Tree, error) {
|
||||||
treeSet := make(map[string]*Tree)
|
treeSet := make(map[string]*Tree)
|
||||||
t := New(name)
|
t := New(name)
|
||||||
t.text = text
|
t.text = text
|
||||||
|
@ -127,7 +128,7 @@ func (t *Tree) peekNonSpace() item {
|
||||||
// Parsing.
|
// Parsing.
|
||||||
|
|
||||||
// New allocates a new parse tree with the given name.
|
// New allocates a new parse tree with the given name.
|
||||||
func New(name string, funcs ...map[string]interface{}) *Tree {
|
func New(name string, funcs ...map[string]any) *Tree {
|
||||||
return &Tree{
|
return &Tree{
|
||||||
Name: name,
|
Name: name,
|
||||||
funcs: funcs,
|
funcs: funcs,
|
||||||
|
@ -157,7 +158,7 @@ func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// errorf formats the error and terminates processing.
|
// errorf formats the error and terminates processing.
|
||||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
func (t *Tree) errorf(format string, args ...any) {
|
||||||
t.Root = nil
|
t.Root = nil
|
||||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
|
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
|
||||||
panic(fmt.Errorf(format, args...))
|
panic(fmt.Errorf(format, args...))
|
||||||
|
@ -217,12 +218,14 @@ func (t *Tree) recover(errp *error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// startParse initializes the parser, using the lexer.
|
// startParse initializes the parser, using the lexer.
|
||||||
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) {
|
func (t *Tree) startParse(funcs []map[string]any, lex *lexer, treeSet map[string]*Tree) {
|
||||||
t.Root = nil
|
t.Root = nil
|
||||||
t.lex = lex
|
t.lex = lex
|
||||||
t.vars = []string{"$"}
|
t.vars = []string{"$"}
|
||||||
t.funcs = funcs
|
t.funcs = funcs
|
||||||
t.treeSet = treeSet
|
t.treeSet = treeSet
|
||||||
|
lex.breakOK = !t.hasFunction("break")
|
||||||
|
lex.continueOK = !t.hasFunction("continue")
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopParse terminates parsing.
|
// stopParse terminates parsing.
|
||||||
|
@ -237,7 +240,7 @@ func (t *Tree) stopParse() {
|
||||||
// the template for execution. If either action delimiter string is empty, the
|
// the template for execution. If either action delimiter string is empty, the
|
||||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||||
// the treeSet map.
|
// the treeSet map.
|
||||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error) {
|
||||||
defer t.recover(&err)
|
defer t.recover(&err)
|
||||||
t.ParseName = t.Name
|
t.ParseName = t.Name
|
||||||
emitComment := t.Mode&ParseComments != 0
|
emitComment := t.Mode&ParseComments != 0
|
||||||
|
@ -385,6 +388,10 @@ func (t *Tree) action() (n Node) {
|
||||||
switch token := t.nextNonSpace(); token.typ {
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
case itemBlock:
|
case itemBlock:
|
||||||
return t.blockControl()
|
return t.blockControl()
|
||||||
|
case itemBreak:
|
||||||
|
return t.breakControl(token.pos, token.line)
|
||||||
|
case itemContinue:
|
||||||
|
return t.continueControl(token.pos, token.line)
|
||||||
case itemElse:
|
case itemElse:
|
||||||
return t.elseControl()
|
return t.elseControl()
|
||||||
case itemEnd:
|
case itemEnd:
|
||||||
|
@ -404,6 +411,32 @@ func (t *Tree) action() (n Node) {
|
||||||
return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim))
|
return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Break:
|
||||||
|
// {{break}}
|
||||||
|
// Break keyword is past.
|
||||||
|
func (t *Tree) breakControl(pos Pos, line int) Node {
|
||||||
|
if token := t.next(); token.typ != itemRightDelim {
|
||||||
|
t.unexpected(token, "in {{break}}")
|
||||||
|
}
|
||||||
|
if t.rangeDepth == 0 {
|
||||||
|
t.errorf("{{break}} outside {{range}}")
|
||||||
|
}
|
||||||
|
return t.newBreak(pos, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue:
|
||||||
|
// {{continue}}
|
||||||
|
// Continue keyword is past.
|
||||||
|
func (t *Tree) continueControl(pos Pos, line int) Node {
|
||||||
|
if token := t.next(); token.typ != itemRightDelim {
|
||||||
|
t.unexpected(token, "in {{continue}}")
|
||||||
|
}
|
||||||
|
if t.rangeDepth == 0 {
|
||||||
|
t.errorf("{{continue}} outside {{range}}")
|
||||||
|
}
|
||||||
|
return t.newContinue(pos, line)
|
||||||
|
}
|
||||||
|
|
||||||
// Pipeline:
|
// Pipeline:
|
||||||
// declarations? command ('|' command)*
|
// declarations? command ('|' command)*
|
||||||
func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) {
|
func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) {
|
||||||
|
@ -479,8 +512,14 @@ func (t *Tree) checkPipeline(pipe *PipeNode, context string) {
|
||||||
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||||
defer t.popVars(len(t.vars))
|
defer t.popVars(len(t.vars))
|
||||||
pipe = t.pipeline(context, itemRightDelim)
|
pipe = t.pipeline(context, itemRightDelim)
|
||||||
|
if context == "range" {
|
||||||
|
t.rangeDepth++
|
||||||
|
}
|
||||||
var next Node
|
var next Node
|
||||||
list, next = t.itemList()
|
list, next = t.itemList()
|
||||||
|
if context == "range" {
|
||||||
|
t.rangeDepth--
|
||||||
|
}
|
||||||
switch next.Type() {
|
switch next.Type() {
|
||||||
case nodeEnd: //done
|
case nodeEnd: //done
|
||||||
case nodeElse:
|
case nodeElse:
|
||||||
|
@ -522,7 +561,8 @@ func (t *Tree) ifControl() Node {
|
||||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||||
// Range keyword is past.
|
// Range keyword is past.
|
||||||
func (t *Tree) rangeControl() Node {
|
func (t *Tree) rangeControl() Node {
|
||||||
return t.newRange(t.parseControl(false, "range"))
|
r := t.newRange(t.parseControl(false, "range"))
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// With:
|
// With:
|
||||||
|
@ -689,7 +729,8 @@ func (t *Tree) operand() Node {
|
||||||
func (t *Tree) term() Node {
|
func (t *Tree) term() Node {
|
||||||
switch token := t.nextNonSpace(); token.typ {
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
case itemIdentifier:
|
case itemIdentifier:
|
||||||
if !t.hasFunction(token.val) {
|
checkFunc := t.Mode&SkipFuncCheck == 0
|
||||||
|
if checkFunc && !t.hasFunction(token.val) {
|
||||||
t.errorf("function %q not defined", token.val)
|
t.errorf("function %q not defined", token.val)
|
||||||
}
|
}
|
||||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package parse
|
package parse
|
||||||
|
@ -232,6 +233,10 @@ var parseTests = []parseTest{
|
||||||
`{{range $x := .SI}}{{.}}{{end}}`},
|
`{{range $x := .SI}}{{.}}{{end}}`},
|
||||||
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
||||||
`{{range $x, $y := .SI}}{{.}}{{end}}`},
|
`{{range $x, $y := .SI}}{{.}}{{end}}`},
|
||||||
|
{"range with break", "{{range .SI}}{{.}}{{break}}{{end}}", noError,
|
||||||
|
`{{range .SI}}{{.}}{{break}}{{end}}`},
|
||||||
|
{"range with continue", "{{range .SI}}{{.}}{{continue}}{{end}}", noError,
|
||||||
|
`{{range .SI}}{{.}}{{continue}}{{end}}`},
|
||||||
{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
|
{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
|
||||||
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
||||||
{"template", "{{template `x`}}", noError,
|
{"template", "{{template `x`}}", noError,
|
||||||
|
@ -281,6 +286,10 @@ var parseTests = []parseTest{
|
||||||
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
|
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
|
||||||
{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
|
{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
|
||||||
{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
|
{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
|
||||||
|
{"break outside range", "{{range .}}{{end}} {{break}}", hasError, ""},
|
||||||
|
{"continue outside range", "{{range .}}{{end}} {{continue}}", hasError, ""},
|
||||||
|
{"break in range else", "{{range .}}{{else}}{{break}}{{end}}", hasError, ""},
|
||||||
|
{"continue in range else", "{{range .}}{{else}}{{continue}}{{end}}", hasError, ""},
|
||||||
// Other kinds of assignments and operators aren't available yet.
|
// Other kinds of assignments and operators aren't available yet.
|
||||||
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
|
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
|
||||||
{"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
|
{"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
|
||||||
|
@ -312,7 +321,7 @@ var parseTests = []parseTest{
|
||||||
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
|
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
var builtins = map[string]interface{}{
|
var builtins = map[string]any{
|
||||||
"printf": fmt.Sprintf,
|
"printf": fmt.Sprintf,
|
||||||
"contains": strings.Contains,
|
"contains": strings.Contains,
|
||||||
}
|
}
|
||||||
|
@ -381,6 +390,22 @@ func TestParseWithComments(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSkipFuncCheck(t *testing.T) {
|
||||||
|
oldTextFormat := textFormat
|
||||||
|
textFormat = "%q"
|
||||||
|
defer func() { textFormat = oldTextFormat }()
|
||||||
|
tr := New("skip func check")
|
||||||
|
tr.Mode = SkipFuncCheck
|
||||||
|
tmpl, err := tr.Parse("{{fn 1 2}}", "", "", make(map[string]*Tree))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
expected := "{{fn 1 2}}"
|
||||||
|
if result := tmpl.Root.String(); result != expected {
|
||||||
|
t.Errorf("got\n\t%v\nexpected\n\t%v", result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type isEmptyTest struct {
|
type isEmptyTest struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
|
@ -5,16 +5,15 @@
|
||||||
package template
|
package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// common holds the information shared by related templates.
|
// common holds the information shared by related templates.
|
||||||
type common struct {
|
type common struct {
|
||||||
muTmpl sync.RWMutex // protects tmpl (temporary Hugo-fix)
|
|
||||||
tmpl map[string]*Template // Map from name to defined templates.
|
tmpl map[string]*Template // Map from name to defined templates.
|
||||||
|
muTmpl sync.RWMutex // protects tmpl
|
||||||
option option
|
option option
|
||||||
// We use two maps, one for parsing and one for execution.
|
// We use two maps, one for parsing and one for execution.
|
||||||
// This separation makes the API cleaner since it doesn't
|
// This separation makes the API cleaner since it doesn't
|
||||||
|
@ -90,7 +89,6 @@ func (t *Template) Clone() (*Template, error) {
|
||||||
if t.common == nil {
|
if t.common == nil {
|
||||||
return nt, nil
|
return nt, nil
|
||||||
}
|
}
|
||||||
// temporary Hugo-fix
|
|
||||||
t.muTmpl.RLock()
|
t.muTmpl.RLock()
|
||||||
defer t.muTmpl.RUnlock()
|
defer t.muTmpl.RUnlock()
|
||||||
for k, v := range t.tmpl {
|
for k, v := range t.tmpl {
|
||||||
|
@ -129,10 +127,9 @@ func (t *Template) copy(c *common) *Template {
|
||||||
// its definition. If it has been defined and already has that name, the existing
|
// its definition. If it has been defined and already has that name, the existing
|
||||||
// definition is replaced; otherwise a new template is created, defined, and returned.
|
// definition is replaced; otherwise a new template is created, defined, and returned.
|
||||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||||
// temporary Hugo-fix
|
t.init()
|
||||||
t.muTmpl.Lock()
|
t.muTmpl.Lock()
|
||||||
defer t.muTmpl.Unlock()
|
defer t.muTmpl.Unlock()
|
||||||
t.init()
|
|
||||||
nt := t
|
nt := t
|
||||||
if name != t.name {
|
if name != t.name {
|
||||||
nt = t.New(name)
|
nt = t.New(name)
|
||||||
|
@ -150,7 +147,6 @@ func (t *Template) Templates() []*Template {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Return a slice so we don't expose the map.
|
// Return a slice so we don't expose the map.
|
||||||
// temporary Hugo-fix
|
|
||||||
t.muTmpl.RLock()
|
t.muTmpl.RLock()
|
||||||
defer t.muTmpl.RUnlock()
|
defer t.muTmpl.RUnlock()
|
||||||
m := make([]*Template, 0, len(t.tmpl))
|
m := make([]*Template, 0, len(t.tmpl))
|
||||||
|
@ -193,7 +189,6 @@ func (t *Template) Lookup(name string) *Template {
|
||||||
if t.common == nil {
|
if t.common == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// temporary Hugo-fix
|
|
||||||
t.muTmpl.RLock()
|
t.muTmpl.RLock()
|
||||||
defer t.muTmpl.RUnlock()
|
defer t.muTmpl.RUnlock()
|
||||||
return t.tmpl[name]
|
return t.tmpl[name]
|
||||||
|
|
Loading…
Reference in a new issue