mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
tpl: Sync go_templates for Go 1.18
Using Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774 Updates #9677
This commit is contained in:
@@ -62,6 +62,8 @@ const (
|
||||
// Keywords appear after all the rest.
|
||||
itemKeyword // used only to delimit the keywords
|
||||
itemBlock // block keyword
|
||||
itemBreak // break keyword
|
||||
itemContinue // continue keyword
|
||||
itemDot // the cursor, spelled '.'
|
||||
itemDefine // define keyword
|
||||
itemElse // else keyword
|
||||
@@ -76,6 +78,8 @@ const (
|
||||
var key = map[string]itemType{
|
||||
".": itemDot,
|
||||
"block": itemBlock,
|
||||
"break": itemBreak,
|
||||
"continue": itemContinue,
|
||||
"define": itemDefine,
|
||||
"else": itemElse,
|
||||
"end": itemEnd,
|
||||
@@ -119,6 +123,8 @@ type lexer struct {
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
line int // 1+number of newlines seen
|
||||
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.
|
||||
@@ -184,7 +190,7 @@ func (l *lexer) acceptRun(valid string) {
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// 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}
|
||||
return nil
|
||||
}
|
||||
@@ -461,7 +467,12 @@ Loop:
|
||||
}
|
||||
switch {
|
||||
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] == '.':
|
||||
l.emit(itemField)
|
||||
case word == "true", word == "false":
|
||||
|
@@ -2,6 +2,7 @@
|
||||
// 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
|
||||
@@ -37,6 +38,8 @@ var itemName = map[itemType]string{
|
||||
// keywords
|
||||
itemDot: ".",
|
||||
itemBlock: "block",
|
||||
itemBreak: "break",
|
||||
itemContinue: "continue",
|
||||
itemDefine: "define",
|
||||
itemElse: "else",
|
||||
itemIf: "if",
|
||||
|
@@ -71,6 +71,8 @@ const (
|
||||
NodeVariable // A $ variable.
|
||||
NodeWith // A with action.
|
||||
NodeComment // A comment.
|
||||
NodeBreak // A break action.
|
||||
NodeContinue // A continue action.
|
||||
)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// 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.
|
||||
type RangeNode struct {
|
||||
BranchNode
|
||||
|
@@ -24,14 +24,14 @@ type Tree struct {
|
||||
Mode Mode // parsing mode.
|
||||
text string // text parsed to create the template (or its parent)
|
||||
// Parsing only; cleared after parse.
|
||||
funcs []map[string]interface{}
|
||||
funcs []map[string]any
|
||||
lex *lexer
|
||||
token [3]item // three-token lookahead for parser.
|
||||
peekCount int
|
||||
vars []string // variables defined at the moment.
|
||||
treeSet map[string]*Tree
|
||||
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.
|
||||
@@ -39,6 +39,7 @@ type Mode uint
|
||||
|
||||
const (
|
||||
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.
|
||||
@@ -58,7 +59,7 @@ func (t *Tree) Copy() *Tree {
|
||||
// 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
|
||||
// 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)
|
||||
t := New(name)
|
||||
t.text = text
|
||||
@@ -127,7 +128,7 @@ func (t *Tree) peekNonSpace() item {
|
||||
// Parsing.
|
||||
|
||||
// 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{
|
||||
Name: name,
|
||||
funcs: funcs,
|
||||
@@ -157,7 +158,7 @@ func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||
}
|
||||
|
||||
// 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
|
||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
@@ -217,12 +218,14 @@ func (t *Tree) recover(errp *error) {
|
||||
}
|
||||
|
||||
// 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.lex = lex
|
||||
t.vars = []string{"$"}
|
||||
t.funcs = funcs
|
||||
t.treeSet = treeSet
|
||||
lex.breakOK = !t.hasFunction("break")
|
||||
lex.continueOK = !t.hasFunction("continue")
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
@@ -237,7 +240,7 @@ func (t *Tree) stopParse() {
|
||||
// the template for execution. If either action delimiter string is empty, the
|
||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||
// 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)
|
||||
t.ParseName = t.Name
|
||||
emitComment := t.Mode&ParseComments != 0
|
||||
@@ -385,6 +388,10 @@ func (t *Tree) action() (n Node) {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemBlock:
|
||||
return t.blockControl()
|
||||
case itemBreak:
|
||||
return t.breakControl(token.pos, token.line)
|
||||
case itemContinue:
|
||||
return t.continueControl(token.pos, token.line)
|
||||
case itemElse:
|
||||
return t.elseControl()
|
||||
case itemEnd:
|
||||
@@ -404,6 +411,32 @@ func (t *Tree) action() (n Node) {
|
||||
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:
|
||||
// declarations? command ('|' command)*
|
||||
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) {
|
||||
defer t.popVars(len(t.vars))
|
||||
pipe = t.pipeline(context, itemRightDelim)
|
||||
if context == "range" {
|
||||
t.rangeDepth++
|
||||
}
|
||||
var next Node
|
||||
list, next = t.itemList()
|
||||
if context == "range" {
|
||||
t.rangeDepth--
|
||||
}
|
||||
switch next.Type() {
|
||||
case nodeEnd: //done
|
||||
case nodeElse:
|
||||
@@ -522,7 +561,8 @@ func (t *Tree) ifControl() Node {
|
||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||
// Range keyword is past.
|
||||
func (t *Tree) rangeControl() Node {
|
||||
return t.newRange(t.parseControl(false, "range"))
|
||||
r := t.newRange(t.parseControl(false, "range"))
|
||||
return r
|
||||
}
|
||||
|
||||
// With:
|
||||
@@ -689,7 +729,8 @@ func (t *Tree) operand() Node {
|
||||
func (t *Tree) term() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
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)
|
||||
}
|
||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||
|
@@ -2,6 +2,7 @@
|
||||
// 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
|
||||
@@ -232,6 +233,10 @@ var parseTests = []parseTest{
|
||||
`{{range $x := .SI}}{{.}}{{end}}`},
|
||||
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
||||
`{{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,
|
||||
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
||||
{"template", "{{template `x`}}", noError,
|
||||
@@ -281,6 +286,10 @@ var parseTests = []parseTest{
|
||||
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
|
||||
{"adjacent args with .", "{{printf `x`.}}", 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.
|
||||
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
|
||||
{"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
|
||||
@@ -312,7 +321,7 @@ var parseTests = []parseTest{
|
||||
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
|
||||
}
|
||||
|
||||
var builtins = map[string]interface{}{
|
||||
var builtins = map[string]any{
|
||||
"printf": fmt.Sprintf,
|
||||
"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 {
|
||||
name string
|
||||
input string
|
||||
|
Reference in New Issue
Block a user