mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 07:18:54 +00:00
committed by
GitHub
parent
87260e4a60
commit
e31ff547d2
@@ -22,8 +22,9 @@ const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcsetat
|
||||
var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58, 73}
|
||||
|
||||
func (i attr) String() string {
|
||||
if i >= attr(len(_attr_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_attr_index)-1 {
|
||||
return "attr(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _attr_name[_attr_index[i]:_attr_index[i+1]]
|
||||
return _attr_name[_attr_index[idx]:_attr_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -428,7 +427,7 @@ func TestStringer(t *testing.T) {
|
||||
if err := tmpl.Execute(b, s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect := "string=3"
|
||||
var expect = "string=3"
|
||||
if b.String() != expect {
|
||||
t.Errorf("expected %q got %q", expect, b.String())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ const _delim_name = "delimNonedelimDoubleQuotedelimSingleQuotedelimSpaceOrTagEnd
|
||||
var _delim_index = [...]uint8{0, 9, 25, 41, 59}
|
||||
|
||||
func (i delim) String() string {
|
||||
if i >= delim(len(_delim_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_delim_index)-1 {
|
||||
return "delim(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _delim_name[_delim_index[i]:_delim_index[i+1]]
|
||||
return _delim_name[_delim_index[idx]:_delim_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ const _element_name = "elementNoneelementScriptelementStyleelementTextareaelemen
|
||||
var _element_index = [...]uint8{0, 11, 24, 36, 51, 63, 74}
|
||||
|
||||
func (i element) String() string {
|
||||
if i >= element(len(_element_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_element_index)-1 {
|
||||
return "element(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _element_name[_element_index[i]:_element_index[i+1]]
|
||||
return _element_name[_element_index[idx]:_element_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
//"internal/godebug"
|
||||
"io"
|
||||
"maps"
|
||||
"regexp"
|
||||
@@ -165,9 +164,7 @@ func (e *escaper) escape(c context, n parse.Node) context {
|
||||
panic("escaping " + n.String() + " is unimplemented")
|
||||
}
|
||||
|
||||
//var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
|
||||
|
||||
var htmlmetacontenturlescape = true //godebug.New("htmlmetacontenturlescape")
|
||||
var htmlmetacontenturlescape = true
|
||||
|
||||
// escapeAction escapes an action template node.
|
||||
func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -1866,7 +1865,7 @@ func TestEscapeText(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"<script>function f() {`${ function f() { `${1}` } }`}",
|
||||
context{state: stateJS, element: elementScript, jsCtx: jsCtxDivOp},
|
||||
context{state: stateJS, element: elementScript, jsCtx: jsCtxRegexp},
|
||||
},
|
||||
{
|
||||
"<script>`${ { `` }",
|
||||
@@ -2266,3 +2265,55 @@ func TestAliasedParseTreeDoesNotOverescape(t *testing.T) {
|
||||
t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCVE202656858(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tmpl string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "regexp after open brace in if block",
|
||||
tmpl: `<script>if(true){/{{.}}/g.test("x")}</script>`,
|
||||
input: "a.b",
|
||||
want: `<script>if(true){/a\.b/g.test("x")}</script>`,
|
||||
},
|
||||
{
|
||||
name: "regexp after close brace",
|
||||
tmpl: `<script>if(true){x=1}/{{.}}/g.test("x")</script>`,
|
||||
input: "a.b",
|
||||
want: `<script>if(true){x=1}/a\.b/g.test("x")</script>`,
|
||||
},
|
||||
{
|
||||
name: "regexp pathological attacker input",
|
||||
tmpl: `<script>if(true){/{{.}}/g.test("x")}</script>`,
|
||||
input: `./;alert(1);var q=/.`,
|
||||
want: `<script>if(true){/\.\/;alert\(1\);var q=\/\./g.test("x")}</script>`,
|
||||
},
|
||||
{
|
||||
name: "regexp after open brace in template literal",
|
||||
tmpl: "<script>`${ (function(){/{{.}}/g.test(x)}) }`</script>",
|
||||
input: "a.b",
|
||||
want: "<script>`${ (function(){/a\\.b/g.test(x)}) }`</script>",
|
||||
},
|
||||
{
|
||||
name: "regexp after close brace in template literal",
|
||||
tmpl: "<script>`${ (function(){}/{{.}}/g.test(x)) }`</script>",
|
||||
input: "a.b",
|
||||
want: "<script>`${ (function(){}/a\\.b/g.test(x)) }`</script>",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpl := Must(New("test").Parse(tt.tmpl))
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, tt.input); err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
if got := buf.String(); got != tt.want {
|
||||
t.Errorf("got: %s\nwant: %s", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Tests for template execution, copied from text/template.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -325,16 +324,12 @@ var execTests = []execTest{
|
||||
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
||||
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
||||
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
||||
{
|
||||
"nested assignment",
|
||||
{"nested assignment",
|
||||
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
||||
"3", tVal, true,
|
||||
},
|
||||
{
|
||||
"nested assignment changes the last declaration",
|
||||
"3", tVal, true},
|
||||
{"nested assignment changes the last declaration",
|
||||
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
||||
"1", tVal, true,
|
||||
},
|
||||
"1", tVal, true},
|
||||
|
||||
// Type with String method.
|
||||
{"V{6666}.String()", "-{{.V0}}-", "-{6666}-", tVal, true}, // NOTE: -<6666>- in text/template
|
||||
@@ -381,21 +376,15 @@ var execTests = []execTest{
|
||||
{".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
|
||||
{".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
|
||||
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
||||
{
|
||||
"method on chained var",
|
||||
{"method on chained var",
|
||||
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||
"true", tVal, true,
|
||||
},
|
||||
{
|
||||
"chained method",
|
||||
"true", tVal, true},
|
||||
{"chained method",
|
||||
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||
"true", tVal, true,
|
||||
},
|
||||
{
|
||||
"chained method on variable",
|
||||
"true", tVal, true},
|
||||
{"chained method on variable",
|
||||
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
||||
"true", tVal, true,
|
||||
},
|
||||
"true", tVal, true},
|
||||
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
||||
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
||||
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
|
||||
@@ -481,14 +470,10 @@ var execTests = []execTest{
|
||||
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
||||
|
||||
// HTML.
|
||||
{
|
||||
"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||
"<script>alert("XSS");</script>", nil, true,
|
||||
},
|
||||
{
|
||||
"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||
"<script>alert("XSS");</script>", nil, true,
|
||||
},
|
||||
{"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||
"<script>alert("XSS");</script>", nil, true},
|
||||
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||
"<script>alert("XSS");</script>", nil, true},
|
||||
{"html", `{{html .PS}}`, "a string", tVal, true},
|
||||
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
||||
{"html untyped nil", `{{html .Empty0}}`, "<nil>", tVal, true}, // NOTE: "<no value>" in text/template
|
||||
@@ -854,7 +839,7 @@ var delimPairs = []string{
|
||||
|
||||
func TestDelims(t *testing.T) {
|
||||
const hello = "Hello, world"
|
||||
value := struct{ Str string }{hello}
|
||||
var value = struct{ Str string }{hello}
|
||||
for i := 0; i < len(delimPairs); i += 2 {
|
||||
text := ".Str"
|
||||
left := delimPairs[i+0]
|
||||
@@ -877,7 +862,7 @@ func TestDelims(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
||||
}
|
||||
b := new(strings.Builder)
|
||||
var b = new(strings.Builder)
|
||||
err = tmpl.Execute(b, value)
|
||||
if err != nil {
|
||||
t.Fatalf("delim %q exec err %s", left, err)
|
||||
@@ -978,7 +963,7 @@ const treeTemplate = `
|
||||
`
|
||||
|
||||
func TestTree(t *testing.T) {
|
||||
tree := &Tree{
|
||||
var tree = &Tree{
|
||||
1,
|
||||
&Tree{
|
||||
2, &Tree{
|
||||
@@ -1229,7 +1214,7 @@ var cmpTests = []cmpTest{
|
||||
|
||||
func TestComparison(t *testing.T) {
|
||||
b := new(strings.Builder)
|
||||
cmpStruct := struct {
|
||||
var cmpStruct = struct {
|
||||
Uthree, Ufour uint
|
||||
NegOne, Three int
|
||||
Ptr, NilPtr *int
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -221,8 +220,7 @@ func TestJSStrEscaper(t *testing.T) {
|
||||
{"<!--", `\u003c!--`},
|
||||
{"-->", `--\u003e`},
|
||||
// From https://code.google.com/p/doctype/wiki/ArticleUtf7
|
||||
{
|
||||
"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
|
||||
{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
|
||||
`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
|
||||
},
|
||||
// Invalid UTF-8 sequence
|
||||
|
||||
@@ -18,8 +18,9 @@ const _jsCtx_name = "jsCtxRegexpjsCtxDivOpjsCtxUnknown"
|
||||
var _jsCtx_index = [...]uint8{0, 11, 21, 33}
|
||||
|
||||
func (i jsCtx) String() string {
|
||||
if i >= jsCtx(len(_jsCtx_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_jsCtx_index)-1 {
|
||||
return "jsCtx(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _jsCtx_name[_jsCtx_index[i]:_jsCtx_index[i+1]]
|
||||
return _jsCtx_name[_jsCtx_index[idx]:_jsCtx_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Tests for multiple-template execution, copied from text/template.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -268,7 +267,7 @@ func TestIssue19294(t *testing.T) {
|
||||
// by the contents of "stylesheet", but if the internal map associating
|
||||
// names with templates is built in the wrong order, the empty block
|
||||
// looks non-empty and this doesn't happen.
|
||||
inlined := map[string]string{
|
||||
var inlined = map[string]string{
|
||||
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
||||
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
||||
}
|
||||
|
||||
@@ -46,8 +46,9 @@ const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValu
|
||||
var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 156, 169, 184, 198, 216, 235, 243, 256, 269, 282, 295, 306, 322, 337, 347, 363, 382, 391}
|
||||
|
||||
func (i state) String() string {
|
||||
if i >= state(len(_state_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_state_index)-1 {
|
||||
return "state(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _state_name[_state_index[i]:_state_index[i+1]]
|
||||
return _state_name[_state_index[idx]:_state_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ type Template struct {
|
||||
// we need to keep our version of the name space and the underlying
|
||||
// template's in sync.
|
||||
text *template.Template
|
||||
// The underlying template's parse tree, updated to be HTML-safe.
|
||||
// The underlying template's parse tree, updated to be HTML-safe
|
||||
// after the first execution.
|
||||
Tree *parse.Tree
|
||||
*nameSpace // common to all associated templates
|
||||
}
|
||||
@@ -332,10 +333,12 @@ func (t *Template) Name() string {
|
||||
type FuncMap = template.FuncMap
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// It must be called before the template is parsed.
|
||||
// Any function used in the template must be added before the template is
|
||||
// parsed. Funcs may be called more than once, including after parsing (for
|
||||
// example, after [Template.Clone]), to replace a function of the same name;
|
||||
// the replacement is used when the template is executed.
|
||||
// It panics if a value in the map is not a function with appropriate return
|
||||
// type. However, it is legal to overwrite elements of the map. The return
|
||||
// value is the template, so calls can be chained.
|
||||
// type. The return value is the template, so calls can be chained.
|
||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||
t.text.Funcs(template.FuncMap(funcMap))
|
||||
return t
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -336,11 +336,14 @@ func tJS(c context, s []byte) (context, int) {
|
||||
// We only care about tracking brace depth if we are inside of a
|
||||
// template literal.
|
||||
if len(c.jsBraceDepth) == 0 {
|
||||
c.jsCtx = nextJSCtx(s[i:i+1], c.jsCtx)
|
||||
return c, i + 1
|
||||
}
|
||||
c.jsBraceDepth[len(c.jsBraceDepth)-1]++
|
||||
c.jsCtx = nextJSCtx(s[i:i+1], c.jsCtx)
|
||||
case '}':
|
||||
if len(c.jsBraceDepth) == 0 {
|
||||
c.jsCtx = nextJSCtx(s[i:i+1], c.jsCtx)
|
||||
return c, i + 1
|
||||
}
|
||||
// There are no cases where a brace can be escaped in the JS context
|
||||
@@ -349,6 +352,7 @@ func tJS(c context, s []byte) (context, int) {
|
||||
// fully fledged parsers will just fail anyway.
|
||||
c.jsBraceDepth[len(c.jsBraceDepth)-1]--
|
||||
if c.jsBraceDepth[len(c.jsBraceDepth)-1] >= 0 {
|
||||
c.jsCtx = nextJSCtx(s[i:i+1], c.jsCtx)
|
||||
return c, i + 1
|
||||
}
|
||||
c.jsBraceDepth = c.jsBraceDepth[:len(c.jsBraceDepth)-1]
|
||||
@@ -426,7 +430,7 @@ func tJSDelimited(c context, s []byte) (context, int) {
|
||||
// If "</script" appears in a regex literal, the '/' should not
|
||||
// close the regex literal, and it will later be escaped to
|
||||
// "\x3C/script" in escapeText.
|
||||
if i > 0 && i+7 <= len(s) && bytes.Equal(bytes.ToLower(s[i-1:i+7]), []byte("</script")) {
|
||||
if i > 0 && i+7 <= len(s) && bytes.EqualFold(s[i-1:i+7], []byte("</script")) {
|
||||
i++
|
||||
} else if !inCharset {
|
||||
c.state, c.jsCtx = stateJS, jsCtxDivOp
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -43,6 +42,7 @@ func TestFindEndTag(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkTemplateSpecialTags(b *testing.B) {
|
||||
|
||||
r := struct {
|
||||
Name, Gift string
|
||||
}{"Aunt Mildred", "bone china tea set"}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ const _urlPart_name = "urlPartNoneurlPartPreQueryurlPartQueryOrFragurlPartUnknow
|
||||
var _urlPart_index = [...]uint8{0, 11, 26, 44, 58}
|
||||
|
||||
func (i urlPart) String() string {
|
||||
if i >= urlPart(len(_urlPart_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_urlPart_index)-1 {
|
||||
return "urlPart(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _urlPart_name[_urlPart_index[i]:_urlPart_index[i+1]]
|
||||
return _urlPart_name[_urlPart_index[idx]:_urlPart_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -19,5 +21,22 @@ func syscallIsNotSupported(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if errno, ok := errors.AsType[syscall.Errno](err); ok {
|
||||
switch errno {
|
||||
case syscall.EPERM, syscall.EROFS:
|
||||
// User lacks permission: either the call requires root permission and the
|
||||
// user is not root, or the call is denied by a container security policy.
|
||||
return true
|
||||
case syscall.EINVAL:
|
||||
// Some containers return EINVAL instead of EPERM if a system call is
|
||||
// denied by security policy.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if errors.Is(err, fs.ErrPermission) || errors.Is(err, errors.ErrUnsupported) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
@@ -57,7 +54,7 @@ Output 2: {{printf "%q" . | title}}
|
||||
}
|
||||
|
||||
// This example demonstrates registering two custom template functions
|
||||
// and how to overwite one of the functions after the template has been
|
||||
// and how to overwrite one of the functions after the template has been
|
||||
// parsed. Overwriting can be used, for example, to alter the operation
|
||||
// of cloned templates.
|
||||
func ExampleTemplate_funcs() {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -337,16 +336,12 @@ var execTests = []execTest{
|
||||
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
|
||||
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
|
||||
{"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
|
||||
{
|
||||
"nested assignment",
|
||||
{"nested assignment",
|
||||
"{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
|
||||
"3", tVal, true,
|
||||
},
|
||||
{
|
||||
"nested assignment changes the last declaration",
|
||||
"3", tVal, true},
|
||||
{"nested assignment changes the last declaration",
|
||||
"{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
|
||||
"1", tVal, true,
|
||||
},
|
||||
"1", tVal, true},
|
||||
|
||||
// Type with String method.
|
||||
{"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true},
|
||||
@@ -393,21 +388,15 @@ var execTests = []execTest{
|
||||
{".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
|
||||
{".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
|
||||
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
|
||||
{
|
||||
"method on chained var",
|
||||
{"method on chained var",
|
||||
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||
"true", tVal, true,
|
||||
},
|
||||
{
|
||||
"chained method",
|
||||
"true", tVal, true},
|
||||
{"chained method",
|
||||
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
|
||||
"true", tVal, true,
|
||||
},
|
||||
{
|
||||
"chained method on variable",
|
||||
"true", tVal, true},
|
||||
{"chained method on variable",
|
||||
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
|
||||
"true", tVal, true,
|
||||
},
|
||||
"true", tVal, true},
|
||||
{".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
|
||||
{".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
|
||||
{"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
|
||||
@@ -500,14 +489,10 @@ var execTests = []execTest{
|
||||
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
|
||||
|
||||
// HTML.
|
||||
{
|
||||
"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||
"<script>alert("XSS");</script>", nil, true,
|
||||
},
|
||||
{
|
||||
"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||
"<script>alert("XSS");</script>", nil, true,
|
||||
},
|
||||
{"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
|
||||
"<script>alert("XSS");</script>", nil, true},
|
||||
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||
"<script>alert("XSS");</script>", nil, true},
|
||||
{"html", `{{html .PS}}`, "a string", tVal, true},
|
||||
{"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true},
|
||||
{"html untyped nil", `{{html .Empty0}}`, "<no value>", tVal, true},
|
||||
@@ -942,9 +927,8 @@ var delimPairs = []string{
|
||||
|
||||
func TestDelims(t *testing.T) {
|
||||
const hello = "Hello, world"
|
||||
value := struct{ Str string }{hello}
|
||||
var value = struct{ Str string }{hello}
|
||||
for i := 0; i < len(delimPairs); i += 2 {
|
||||
text := ".Str"
|
||||
left := delimPairs[i+0]
|
||||
trueLeft := left
|
||||
right := delimPairs[i+1]
|
||||
@@ -955,17 +939,23 @@ func TestDelims(t *testing.T) {
|
||||
if right == "" { // default case
|
||||
trueRight = "}}"
|
||||
}
|
||||
text = trueLeft + text + trueRight
|
||||
// Now add a comment
|
||||
text += trueLeft + "/*comment*/" + trueRight
|
||||
// Now add an action containing a string.
|
||||
text += trueLeft + `"` + trueLeft + `"` + trueRight
|
||||
action := trueLeft + ".Str" + trueRight
|
||||
// A comment, which is not preserved in the parse tree.
|
||||
comment := trueLeft + "/*comment*/" + trueRight
|
||||
// An action containing a string that looks like the left delimiter.
|
||||
strAction := trueLeft + `"` + trueLeft + `"` + trueRight
|
||||
text := action + comment + strAction
|
||||
// At this point text looks like `{{.Str}}{{/*comment*/}}{{"{{"}}`.
|
||||
tmpl, err := New("delims").Delims(left, right).Parse(text)
|
||||
if err != nil {
|
||||
t.Fatalf("delim %q text %q parse err %s", left, text, err)
|
||||
}
|
||||
b := new(strings.Builder)
|
||||
// The parse tree's String form should roundtrip back to the input,
|
||||
// using the custom delimiters, modulo the dropped comment.
|
||||
if got, want := tmpl.Root.String(), action+strAction; got != want {
|
||||
t.Errorf("delim %q: String() = %q, want %q", left, got, want)
|
||||
}
|
||||
var b = new(strings.Builder)
|
||||
err = tmpl.Execute(b, value)
|
||||
if err != nil {
|
||||
t.Fatalf("delim %q exec err %s", left, err)
|
||||
@@ -1025,6 +1015,23 @@ 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)
|
||||
|
||||
if _, ok := errors.AsType[*CustomError](err); !ok {
|
||||
t.Fatalf("expected custom error; got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSEscaping(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in, exp string
|
||||
@@ -1070,7 +1077,7 @@ const treeTemplate = `
|
||||
`
|
||||
|
||||
func TestTree(t *testing.T) {
|
||||
tree := &Tree{
|
||||
var tree = &Tree{
|
||||
1,
|
||||
&Tree{
|
||||
2, &Tree{
|
||||
@@ -1323,7 +1330,7 @@ var cmpTests = []cmpTest{
|
||||
|
||||
func TestComparison(t *testing.T) {
|
||||
b := new(strings.Builder)
|
||||
cmpStruct := struct {
|
||||
var cmpStruct = struct {
|
||||
Uthree, Ufour uint
|
||||
NegOne, Three int
|
||||
Ptr, NilPtr *int
|
||||
@@ -1836,13 +1843,12 @@ func TestFunctionCheckDuringCall(t *testing.T) {
|
||||
input string
|
||||
data any
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "call nothing",
|
||||
input: `{{call}}`,
|
||||
data: tVal,
|
||||
wantErr: "wrong number of args for call: want at least 1 got 0",
|
||||
},
|
||||
}{{
|
||||
name: "call nothing",
|
||||
input: `{{call}}`,
|
||||
data: tVal,
|
||||
wantErr: "wrong number of args for call: want at least 1 got 0",
|
||||
},
|
||||
{
|
||||
name: "call non-function",
|
||||
input: "{{call .True}}",
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package template
|
||||
|
||||
@@ -11,11 +10,10 @@ package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,32 +30,22 @@ type multiParseTest struct {
|
||||
}
|
||||
|
||||
var multiParseTests = []multiParseTest{
|
||||
{
|
||||
"empty", "", noError,
|
||||
{"empty", "", noError,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"one", `{{define "foo"}} FOO {{end}}`, noError,
|
||||
nil},
|
||||
{"one", `{{define "foo"}} FOO {{end}}`, noError,
|
||||
[]string{"foo"},
|
||||
[]string{" FOO "},
|
||||
},
|
||||
{
|
||||
"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
|
||||
[]string{" FOO "}},
|
||||
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
|
||||
[]string{"foo", "bar"},
|
||||
[]string{" FOO ", " BAR "},
|
||||
},
|
||||
[]string{" FOO ", " BAR "}},
|
||||
// errors
|
||||
{
|
||||
"missing end", `{{define "foo"}} FOO `, hasError,
|
||||
{"missing end", `{{define "foo"}} FOO `, hasError,
|
||||
nil,
|
||||
nil},
|
||||
{"malformed name", `{{define "foo}} FOO `, hasError,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"malformed name", `{{define "foo}} FOO `, hasError,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
nil},
|
||||
}
|
||||
|
||||
func TestMultiParse(t *testing.T) {
|
||||
@@ -454,7 +442,7 @@ func TestIssue19294(t *testing.T) {
|
||||
// by the contents of "stylesheet", but if the internal map associating
|
||||
// names with templates is built in the wrong order, the empty block
|
||||
// looks non-empty and this doesn't happen.
|
||||
inlined := map[string]string{
|
||||
var inlined = map[string]string{
|
||||
"stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
|
||||
"xhtml": `{{block "stylesheet" .}}{{end}}`,
|
||||
}
|
||||
|
||||
@@ -240,10 +240,10 @@ func (l *lexer) nextItem() item {
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(name, input, left, right string) *lexer {
|
||||
if left == "" {
|
||||
left = leftDelim
|
||||
left = defaultLeftDelim
|
||||
}
|
||||
if right == "" {
|
||||
right = rightDelim
|
||||
right = defaultRightDelim
|
||||
}
|
||||
l := &lexer{
|
||||
name: name,
|
||||
@@ -260,10 +260,10 @@ func lex(name, input, left, right string) *lexer {
|
||||
// state functions
|
||||
|
||||
const (
|
||||
leftDelim = "{{"
|
||||
rightDelim = "}}"
|
||||
leftComment = "/*"
|
||||
rightComment = "*/"
|
||||
defaultLeftDelim = "{{"
|
||||
defaultRightDelim = "}}"
|
||||
leftComment = "/*"
|
||||
rightComment = "*/"
|
||||
)
|
||||
|
||||
// lexText scans until an opening action delimiter, "{{".
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
|
||||
@@ -171,9 +171,9 @@ func (c *CommentNode) String() string {
|
||||
}
|
||||
|
||||
func (c *CommentNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString("{{")
|
||||
sb.WriteString(c.tr.leftDelim)
|
||||
sb.WriteString(c.Text)
|
||||
sb.WriteString("}}")
|
||||
sb.WriteString(c.tr.rightDelim)
|
||||
}
|
||||
|
||||
func (c *CommentNode) tree() *Tree {
|
||||
@@ -277,9 +277,9 @@ func (a *ActionNode) String() string {
|
||||
}
|
||||
|
||||
func (a *ActionNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString("{{")
|
||||
sb.WriteString(a.tr.leftDelim)
|
||||
a.Pipe.writeTo(sb)
|
||||
sb.WriteString("}}")
|
||||
sb.WriteString(a.tr.rightDelim)
|
||||
}
|
||||
|
||||
func (a *ActionNode) tree() *Tree {
|
||||
@@ -793,7 +793,7 @@ func (t *Tree) newEnd(pos Pos) *endNode {
|
||||
}
|
||||
|
||||
func (e *endNode) String() string {
|
||||
return "{{end}}"
|
||||
return e.tr.leftDelim + "end" + e.tr.rightDelim
|
||||
}
|
||||
|
||||
func (e *endNode) writeTo(sb *strings.Builder) {
|
||||
@@ -825,7 +825,7 @@ func (e *elseNode) Type() NodeType {
|
||||
}
|
||||
|
||||
func (e *elseNode) String() string {
|
||||
return "{{else}}"
|
||||
return e.tr.leftDelim + "else" + e.tr.rightDelim
|
||||
}
|
||||
|
||||
func (e *elseNode) writeTo(sb *strings.Builder) {
|
||||
@@ -869,17 +869,21 @@ func (b *BranchNode) writeTo(sb *strings.Builder) {
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
sb.WriteString("{{")
|
||||
sb.WriteString(b.tr.leftDelim)
|
||||
sb.WriteString(name)
|
||||
sb.WriteByte(' ')
|
||||
b.Pipe.writeTo(sb)
|
||||
sb.WriteString("}}")
|
||||
sb.WriteString(b.tr.rightDelim)
|
||||
b.List.writeTo(sb)
|
||||
if b.ElseList != nil {
|
||||
sb.WriteString("{{else}}")
|
||||
sb.WriteString(b.tr.leftDelim)
|
||||
sb.WriteString("else")
|
||||
sb.WriteString(b.tr.rightDelim)
|
||||
b.ElseList.writeTo(sb)
|
||||
}
|
||||
sb.WriteString("{{end}}")
|
||||
sb.WriteString(b.tr.leftDelim)
|
||||
sb.WriteString("end")
|
||||
sb.WriteString(b.tr.rightDelim)
|
||||
}
|
||||
|
||||
func (b *BranchNode) tree() *Tree {
|
||||
@@ -925,9 +929,9 @@ func (t *Tree) newBreak(pos Pos, line int) *BreakNode {
|
||||
}
|
||||
|
||||
func (b *BreakNode) Copy() Node { return b.tr.newBreak(b.Pos, b.Line) }
|
||||
func (b *BreakNode) String() string { return "{{break}}" }
|
||||
func (b *BreakNode) String() string { return b.tr.leftDelim + "break" + b.tr.rightDelim }
|
||||
func (b *BreakNode) tree() *Tree { return b.tr }
|
||||
func (b *BreakNode) writeTo(sb *strings.Builder) { sb.WriteString("{{break}}") }
|
||||
func (b *BreakNode) writeTo(sb *strings.Builder) { sb.WriteString(b.String()) }
|
||||
|
||||
// ContinueNode represents a {{continue}} action.
|
||||
type ContinueNode struct {
|
||||
@@ -942,9 +946,9 @@ func (t *Tree) newContinue(pos Pos, line int) *ContinueNode {
|
||||
}
|
||||
|
||||
func (c *ContinueNode) Copy() Node { return c.tr.newContinue(c.Pos, c.Line) }
|
||||
func (c *ContinueNode) String() string { return "{{continue}}" }
|
||||
func (c *ContinueNode) String() string { return c.tr.leftDelim + "continue" + c.tr.rightDelim }
|
||||
func (c *ContinueNode) tree() *Tree { return c.tr }
|
||||
func (c *ContinueNode) writeTo(sb *strings.Builder) { sb.WriteString("{{continue}}") }
|
||||
func (c *ContinueNode) writeTo(sb *strings.Builder) { sb.WriteString(c.String()) }
|
||||
|
||||
// RangeNode represents a {{range}} action and its commands.
|
||||
type RangeNode struct {
|
||||
@@ -993,13 +997,14 @@ func (t *TemplateNode) String() string {
|
||||
}
|
||||
|
||||
func (t *TemplateNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString("{{template ")
|
||||
sb.WriteString(t.tr.leftDelim)
|
||||
sb.WriteString("template ")
|
||||
sb.WriteString(strconv.Quote(t.Name))
|
||||
if t.Pipe != nil {
|
||||
sb.WriteByte(' ')
|
||||
t.Pipe.writeTo(sb)
|
||||
}
|
||||
sb.WriteString("}}")
|
||||
sb.WriteString(t.tr.rightDelim)
|
||||
}
|
||||
|
||||
func (t *TemplateNode) tree() *Tree {
|
||||
|
||||
@@ -33,6 +33,9 @@ type Tree struct {
|
||||
actionLine int // line of left delim starting action
|
||||
rangeDepth int
|
||||
stackDepth int // depth of nested parenthesized expressions
|
||||
|
||||
leftDelim string
|
||||
rightDelim string
|
||||
}
|
||||
|
||||
// A Mode value is a set of flags (or 0). Modes control parser behavior.
|
||||
@@ -60,10 +63,12 @@ func (t *Tree) Copy() *Tree {
|
||||
return nil
|
||||
}
|
||||
return &Tree{
|
||||
Name: t.Name,
|
||||
ParseName: t.ParseName,
|
||||
Root: t.Root.CopyList(),
|
||||
text: t.text,
|
||||
Name: t.Name,
|
||||
ParseName: t.ParseName,
|
||||
Root: t.Root.CopyList(),
|
||||
text: t.text,
|
||||
leftDelim: t.leftDelim,
|
||||
rightDelim: t.rightDelim,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +263,15 @@ func (t *Tree) stopParse() {
|
||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error) {
|
||||
defer t.recover(&err)
|
||||
t.ParseName = t.Name
|
||||
lexer := lex(t.Name, text, leftDelim, rightDelim)
|
||||
t.leftDelim = leftDelim
|
||||
if t.leftDelim == "" {
|
||||
t.leftDelim = defaultLeftDelim
|
||||
}
|
||||
t.rightDelim = rightDelim
|
||||
if t.rightDelim == "" {
|
||||
t.rightDelim = defaultRightDelim
|
||||
}
|
||||
lexer := lex(t.Name, text, t.leftDelim, t.rightDelim)
|
||||
t.startParse(funcs, lexer, treeSet)
|
||||
t.text = text
|
||||
t.parse()
|
||||
@@ -318,6 +331,8 @@ func (t *Tree) parse() {
|
||||
newT := New("definition") // name will be updated once we know it.
|
||||
newT.text = t.text
|
||||
newT.Mode = t.Mode
|
||||
newT.leftDelim = t.leftDelim
|
||||
newT.rightDelim = t.rightDelim
|
||||
newT.ParseName = t.ParseName
|
||||
newT.startParse(t.funcs, t.lex, t.treeSet)
|
||||
newT.parseDefinition()
|
||||
@@ -546,7 +561,7 @@ func (t *Tree) parseControl(context string) (pos Pos, line int, pipe *PipeNode,
|
||||
t.rangeDepth--
|
||||
}
|
||||
switch next.Type() {
|
||||
case nodeEnd: //done
|
||||
case nodeEnd: // done
|
||||
case nodeElse:
|
||||
// Special case for "else if" and "else with".
|
||||
// If the "else" is followed immediately by an "if" or "with",
|
||||
@@ -650,6 +665,8 @@ func (t *Tree) blockControl() Node {
|
||||
block := New(name) // name will be updated once we know it.
|
||||
block.text = t.text
|
||||
block.Mode = t.Mode
|
||||
block.leftDelim = t.leftDelim
|
||||
block.rightDelim = t.rightDelim
|
||||
block.ParseName = t.ParseName
|
||||
block.startParse(t.funcs, t.lex, t.treeSet)
|
||||
var end Node
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
@@ -415,6 +412,36 @@ func TestParseWithComments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelimsStringRoundtrip(t *testing.T) {
|
||||
// Each input is already in canonical String form, so parsing it with the
|
||||
// custom delimiters and printing the resulting tree must reproduce the
|
||||
// input exactly. This exercises the String/writeTo methods of every node
|
||||
// type that emits delimiters.
|
||||
const (
|
||||
left = "[["
|
||||
right = "]]"
|
||||
)
|
||||
for _, input := range []string{
|
||||
`[[.X]]`, // ActionNode
|
||||
`[[/* a comment */]]`, // CommentNode
|
||||
`[[if .X]]y[[else]]z[[end]]`, // BranchNode (if), with else and end
|
||||
`[[range .X]][[break]][[continue]][[end]]`, // RangeNode, BreakNode, ContinueNode
|
||||
`[[with .X]]y[[end]]`, // BranchNode (with)
|
||||
`[[template "name" .]]`, // TemplateNode
|
||||
} {
|
||||
tr := New("test")
|
||||
tr.Mode = ParseComments
|
||||
tmpl, err := tr.Parse(input, left, right, make(map[string]*Tree))
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected parse error: %v", input, err)
|
||||
continue
|
||||
}
|
||||
if got := tmpl.Root.String(); got != input {
|
||||
t.Errorf("got\n\t%q\nexpected\n\t%q", got, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeywordsAndFuncs(t *testing.T) {
|
||||
// Check collisions between functions and new keywords like 'break'. When a
|
||||
// break function is provided, the parser should treat 'break' as a function,
|
||||
|
||||
@@ -167,11 +167,13 @@ func (t *Template) Delims(left, right string) *Template {
|
||||
}
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// It must be called before the template is parsed.
|
||||
// Any function used in the template must be added before the template is
|
||||
// parsed. Funcs may be called more than once, including after parsing (for
|
||||
// example, after [Template.Clone]), to replace a function of the same name;
|
||||
// the replacement is used when the template is executed.
|
||||
// It panics if a value in the map is not a function with appropriate return
|
||||
// type or if the name cannot be used syntactically as a function in a template.
|
||||
// It is legal to overwrite elements of the map. The return value is the template,
|
||||
// so calls can be chained.
|
||||
// The return value is the template, so calls can be chained.
|
||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||
t.init()
|
||||
t.muFuncs.Lock()
|
||||
|
||||
Reference in New Issue
Block a user