mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
@@ -102,8 +102,8 @@ data, defined in detail in the corresponding sections that follow.
|
||||
If the value of the pipeline has length zero, nothing is output;
|
||||
otherwise, dot is set to the successive elements of the array,
|
||||
slice, or map and T1 is executed. If the value is a map and the
|
||||
keys are of basic type with a defined order ("comparable"), the
|
||||
elements will be visited in sorted key order.
|
||||
keys are of basic type with a defined order, the elements will be
|
||||
visited in sorted key order.
|
||||
|
||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
@@ -385,14 +385,12 @@ returning in effect
|
||||
(Unlike with || in Go, however, eq is a function call and all the
|
||||
arguments will be evaluated.)
|
||||
|
||||
The comparison functions work on basic types only (or named basic
|
||||
types, such as "type Celsius float32"). They implement the Go rules
|
||||
for comparison of values, except that size and exact type are
|
||||
ignored, so any integer value, signed or unsigned, may be compared
|
||||
with any other integer value. (The arithmetic value is compared,
|
||||
not the bit pattern, so all negative integers are less than all
|
||||
unsigned integers.) However, as usual, one may not compare an int
|
||||
with a float32 and so on.
|
||||
The comparison functions work on any values whose type Go defines as
|
||||
comparable. For basic types such as integers, the rules are relaxed:
|
||||
size and exact type are ignored, so any integer value, signed or unsigned,
|
||||
may be compared with any other integer value. (The arithmetic value is compared,
|
||||
not the bit pattern, so all negative integers are less than all unsigned integers.)
|
||||
However, as usual, one may not compare an int with a float32 and so on.
|
||||
|
||||
Associated templates
|
||||
|
||||
|
@@ -5,7 +5,6 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
|
||||
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
|
||||
@@ -230,21 +229,19 @@ func (t *Template) DefinedTemplates() string {
|
||||
if t.common == nil {
|
||||
return ""
|
||||
}
|
||||
var b bytes.Buffer
|
||||
var b strings.Builder
|
||||
for name, tmpl := range t.tmpl {
|
||||
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
if b.Len() == 0 {
|
||||
b.WriteString("; defined templates are: ")
|
||||
} else {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q", name)
|
||||
}
|
||||
var s string
|
||||
if b.Len() > 0 {
|
||||
s = "; defined templates are: " + b.String()
|
||||
}
|
||||
return s
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Walk functions step through the major pieces of the template structure,
|
||||
@@ -464,7 +461,8 @@ func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final ref
|
||||
// Must be a function.
|
||||
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||
case *parse.PipeNode:
|
||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final must be absent.
|
||||
s.notAFunction(cmd.Args, final)
|
||||
return s.evalPipeline(dot, n)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||
@@ -499,20 +497,29 @@ func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||
switch {
|
||||
case constant.IsComplex:
|
||||
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||
case constant.IsFloat && !isHexInt(constant.Text) && strings.ContainsAny(constant.Text, ".eEpP"):
|
||||
|
||||
case constant.IsFloat &&
|
||||
!isHexInt(constant.Text) && !isRuneInt(constant.Text) &&
|
||||
strings.ContainsAny(constant.Text, ".eEpP"):
|
||||
return reflect.ValueOf(constant.Float64)
|
||||
|
||||
case constant.IsInt:
|
||||
n := int(constant.Int64)
|
||||
if int64(n) != constant.Int64 {
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
|
||||
case constant.IsUint:
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return zero
|
||||
}
|
||||
|
||||
func isRuneInt(s string) bool {
|
||||
return len(s) > 0 && s[0] == '\''
|
||||
}
|
||||
|
||||
func isHexInt(s string) bool {
|
||||
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && !strings.ContainsAny(s, "pP")
|
||||
}
|
||||
|
@@ -354,6 +354,12 @@ var execTests = []execTest{
|
||||
{"field on interface", "{{.foo}}", "<no value>", nil, true},
|
||||
{"field on parenthesized interface", "{{(.).foo}}", "<no value>", nil, true},
|
||||
|
||||
// Issue 31810: Parenthesized first element of pipeline with arguments.
|
||||
// See also TestIssue31810.
|
||||
{"unparenthesized non-function", "{{1 2}}", "", nil, false},
|
||||
{"parenthesized non-function", "{{(1) 2}}", "", nil, false},
|
||||
{"parenthesized non-function with no args", "{{(1)}}", "1", nil, true}, // This is fine.
|
||||
|
||||
// Method calls.
|
||||
{".Method0", "-{{.Method0}}-", "-M0-", tVal, true},
|
||||
{".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true},
|
||||
@@ -498,6 +504,7 @@ var execTests = []execTest{
|
||||
{"map MUI64S", "{{index .MUI64S 3}}", "ui643", tVal, true},
|
||||
{"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true},
|
||||
{"map MUI8S", "{{index .MUI8S 2}}", "u82", tVal, true},
|
||||
{"index of an interface field", "{{index .Empty3 0}}", "7", tVal, true},
|
||||
|
||||
// Slicing.
|
||||
{"slice[:]", "{{slice .SI}}", "[3 4 5]", tVal, true},
|
||||
@@ -523,12 +530,14 @@ var execTests = []execTest{
|
||||
{"string[1:2]", "{{slice .S 1 2}}", "y", tVal, true},
|
||||
{"out of range", "{{slice .S 1 5}}", "", tVal, false},
|
||||
{"3-index slice of string", "{{slice .S 1 2 2}}", "", tVal, false},
|
||||
{"slice of an interface field", "{{slice .Empty3 0 1}}", "[7]", tVal, true},
|
||||
|
||||
// Len.
|
||||
{"slice", "{{len .SI}}", "3", tVal, true},
|
||||
{"map", "{{len .MSI }}", "3", tVal, true},
|
||||
{"len of int", "{{len 3}}", "", tVal, false},
|
||||
{"len of nothing", "{{len .Empty0}}", "", tVal, false},
|
||||
{"len of an interface field", "{{len .Empty3}}", "2", tVal, true},
|
||||
|
||||
// With.
|
||||
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
|
||||
@@ -665,6 +674,12 @@ var execTests = []execTest{
|
||||
{"bug17c", "{{len .NonEmptyInterfacePtS}}", "2", tVal, true},
|
||||
{"bug17d", "{{index .NonEmptyInterfacePtS 0}}", "a", tVal, true},
|
||||
{"bug17e", "{{range .NonEmptyInterfacePtS}}-{{.}}-{{end}}", "-a--b-", tVal, true},
|
||||
|
||||
// More variadic function corner cases. Some runes would get evaluated
|
||||
// as constant floats instead of ints. Issue 34483.
|
||||
{"bug18a", "{{eq . '.'}}", "true", '.', true},
|
||||
{"bug18b", "{{eq . 'e'}}", "true", 'e', true},
|
||||
{"bug18c", "{{eq . 'P'}}", "true", 'P', true},
|
||||
}
|
||||
|
||||
func zeroArgs() string {
|
||||
@@ -898,7 +913,9 @@ func TestJSEscaping(t *testing.T) {
|
||||
{`Go "jump" \`, `Go \"jump\" \\`},
|
||||
{`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`},
|
||||
{"unprintable \uFDFF", `unprintable \uFDFF`},
|
||||
{`<html>`, `\x3Chtml\x3E`},
|
||||
{`<html>`, `\u003Chtml\u003E`},
|
||||
{`no = in attributes`, `no \u003D in attributes`},
|
||||
{`' does not become HTML entity`, `\u0026#x27; does not become HTML entity`},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s := JSEscapeString(tc.in)
|
||||
@@ -1158,19 +1175,41 @@ var cmpTests = []cmpTest{
|
||||
{"ge .Uthree .NegOne", "true", true},
|
||||
{"eq (index `x` 0) 'x'", "true", true}, // The example that triggered this rule.
|
||||
{"eq (index `x` 0) 'y'", "false", true},
|
||||
{"eq .V1 .V2", "true", true},
|
||||
{"eq .Ptr .Ptr", "true", true},
|
||||
{"eq .Ptr .NilPtr", "false", true},
|
||||
{"eq .NilPtr .NilPtr", "true", true},
|
||||
{"eq .Iface1 .Iface1", "true", true},
|
||||
{"eq .Iface1 .Iface2", "false", true},
|
||||
{"eq .Iface2 .Iface2", "true", true},
|
||||
// Errors
|
||||
{"eq `xy` 1", "", false}, // Different types.
|
||||
{"eq 2 2.0", "", false}, // Different types.
|
||||
{"lt true true", "", false}, // Unordered types.
|
||||
{"lt 1+0i 1+0i", "", false}, // Unordered types.
|
||||
{"eq `xy` 1", "", false}, // Different types.
|
||||
{"eq 2 2.0", "", false}, // Different types.
|
||||
{"lt true true", "", false}, // Unordered types.
|
||||
{"lt 1+0i 1+0i", "", false}, // Unordered types.
|
||||
{"eq .Ptr 1", "", false}, // Incompatible types.
|
||||
{"eq .Ptr .NegOne", "", false}, // Incompatible types.
|
||||
{"eq .Map .Map", "", false}, // Uncomparable types.
|
||||
{"eq .Map .V1", "", false}, // Uncomparable types.
|
||||
}
|
||||
|
||||
func TestComparison(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
var cmpStruct = struct {
|
||||
Uthree, Ufour uint
|
||||
NegOne, Three int
|
||||
}{3, 4, -1, 3}
|
||||
Uthree, Ufour uint
|
||||
NegOne, Three int
|
||||
Ptr, NilPtr *int
|
||||
Map map[int]int
|
||||
V1, V2 V
|
||||
Iface1, Iface2 fmt.Stringer
|
||||
}{
|
||||
Uthree: 3,
|
||||
Ufour: 4,
|
||||
NegOne: -1,
|
||||
Three: 3,
|
||||
Ptr: new(int),
|
||||
Iface1: b,
|
||||
}
|
||||
for _, test := range cmpTests {
|
||||
text := fmt.Sprintf("{{if %s}}true{{else}}false{{end}}", test.expr)
|
||||
tmpl, err := New("empty").Parse(text)
|
||||
@@ -1622,3 +1661,41 @@ func TestExecutePanicDuringCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 31810. Check that a parenthesized first argument behaves properly.
|
||||
func TestIssue31810(t *testing.T) {
|
||||
// A simple value with no arguments is fine.
|
||||
var b bytes.Buffer
|
||||
const text = "{{ (.) }}"
|
||||
tmpl, err := New("").Parse(text)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = tmpl.Execute(&b, "result")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if b.String() != "result" {
|
||||
t.Errorf("%s got %q, expected %q", text, b.String(), "result")
|
||||
}
|
||||
|
||||
// Even a plain function fails - need to use call.
|
||||
f := func() string { return "result" }
|
||||
b.Reset()
|
||||
err = tmpl.Execute(&b, f)
|
||||
if err == nil {
|
||||
t.Error("expected error with no call, got none")
|
||||
}
|
||||
|
||||
// Works if the function is explicitly called.
|
||||
const textCall = "{{ (call .) }}"
|
||||
tmpl, err = New("").Parse(textCall)
|
||||
b.Reset()
|
||||
err = tmpl.Execute(&b, f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if b.String() != "result" {
|
||||
t.Errorf("%s got %q, expected %q", textCall, b.String(), "result")
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
@@ -29,31 +30,49 @@ import (
|
||||
// type can return interface{} or reflect.Value.
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"slice": slice,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
// builtins returns the FuncMap.
|
||||
// It is not a global variable so the linker can dead code eliminate
|
||||
// more when this isn't called. See golang.org/issue/36021.
|
||||
// TODO: revert this back to a global map once golang.org/issue/2559 is fixed.
|
||||
func builtins() FuncMap {
|
||||
return FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"slice": slice,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
|
||||
// Comparisons
|
||||
"eq": eq, // ==
|
||||
"ge": ge, // >=
|
||||
"gt": gt, // >
|
||||
"le": le, // <=
|
||||
"lt": lt, // <
|
||||
"ne": ne, // !=
|
||||
// Comparisons
|
||||
"eq": eq, // ==
|
||||
"ge": ge, // >=
|
||||
"gt": gt, // >
|
||||
"le": le, // <=
|
||||
"lt": lt, // <
|
||||
"ne": ne, // !=
|
||||
}
|
||||
}
|
||||
|
||||
var builtinFuncs = createValueFuncs(builtins)
|
||||
var builtinFuncsOnce struct {
|
||||
sync.Once
|
||||
v map[string]reflect.Value
|
||||
}
|
||||
|
||||
// builtinFuncsOnce lazily computes & caches the builtinFuncs map.
|
||||
// TODO: revert this back to a global map once golang.org/issue/2559 is fixed.
|
||||
func builtinFuncs() map[string]reflect.Value {
|
||||
builtinFuncsOnce.Do(func() {
|
||||
builtinFuncsOnce.v = createValueFuncs(builtins())
|
||||
})
|
||||
return builtinFuncsOnce.v
|
||||
}
|
||||
|
||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||
@@ -125,7 +144,7 @@ func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||
if fn := builtinFuncs()[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
return reflect.Value{}, false
|
||||
@@ -185,41 +204,41 @@ func indexArg(index reflect.Value, cap int) (int, error) {
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
|
||||
v := indirectInterface(item)
|
||||
if !v.IsValid() {
|
||||
item = indirectInterface(item)
|
||||
if !item.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("index of untyped nil")
|
||||
}
|
||||
for _, i := range indexes {
|
||||
index := indirectInterface(i)
|
||||
for _, index := range indexes {
|
||||
index = indirectInterface(index)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
if item, isNil = indirect(item); isNil {
|
||||
return reflect.Value{}, fmt.Errorf("index of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
switch item.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
x, err := indexArg(index, v.Len())
|
||||
x, err := indexArg(index, item.Len())
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
v = v.Index(x)
|
||||
item = item.Index(x)
|
||||
case reflect.Map:
|
||||
index, err := prepareArg(index, v.Type().Key())
|
||||
index, err := prepareArg(index, item.Type().Key())
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
if x := v.MapIndex(index); x.IsValid() {
|
||||
v = x
|
||||
if x := item.MapIndex(index); x.IsValid() {
|
||||
item = x
|
||||
} else {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
item = reflect.Zero(item.Type().Elem())
|
||||
}
|
||||
case reflect.Invalid:
|
||||
// the loop holds invariant: v.IsValid()
|
||||
// the loop holds invariant: item.IsValid()
|
||||
panic("unreachable")
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("can't index item of type %s", v.Type())
|
||||
return reflect.Value{}, fmt.Errorf("can't index item of type %s", item.Type())
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// Slicing.
|
||||
@@ -229,29 +248,27 @@ func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error)
|
||||
// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first
|
||||
// argument must be a string, slice, or array.
|
||||
func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
|
||||
var (
|
||||
cap int
|
||||
v = indirectInterface(item)
|
||||
)
|
||||
if !v.IsValid() {
|
||||
item = indirectInterface(item)
|
||||
if !item.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("slice of untyped nil")
|
||||
}
|
||||
if len(indexes) > 3 {
|
||||
return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes))
|
||||
}
|
||||
switch v.Kind() {
|
||||
var cap int
|
||||
switch item.Kind() {
|
||||
case reflect.String:
|
||||
if len(indexes) == 3 {
|
||||
return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string")
|
||||
}
|
||||
cap = v.Len()
|
||||
cap = item.Len()
|
||||
case reflect.Array, reflect.Slice:
|
||||
cap = v.Cap()
|
||||
cap = item.Cap()
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("can't slice item of type %s", v.Type())
|
||||
return reflect.Value{}, fmt.Errorf("can't slice item of type %s", item.Type())
|
||||
}
|
||||
// set default values for cases item[:], item[i:].
|
||||
idx := [3]int{0, v.Len()}
|
||||
idx := [3]int{0, item.Len()}
|
||||
for i, index := range indexes {
|
||||
x, err := indexArg(index, cap)
|
||||
if err != nil {
|
||||
@@ -276,20 +293,16 @@ func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error)
|
||||
// Length
|
||||
|
||||
// length returns the length of the item, with an error if it has no defined length.
|
||||
func length(item interface{}) (int, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
if !v.IsValid() {
|
||||
return 0, fmt.Errorf("len of untyped nil")
|
||||
}
|
||||
v, isNil := indirect(v)
|
||||
func length(item reflect.Value) (int, error) {
|
||||
item, isNil := indirect(item)
|
||||
if isNil {
|
||||
return 0, fmt.Errorf("len of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
switch item.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len(), nil
|
||||
return item.Len(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||
return 0, fmt.Errorf("len of type %s", item.Type())
|
||||
}
|
||||
|
||||
// Function invocation
|
||||
@@ -297,11 +310,11 @@ func length(item interface{}) (int, error) {
|
||||
// call returns the result of evaluating the first argument as a function.
|
||||
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||
func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
||||
v := indirectInterface(fn)
|
||||
if !v.IsValid() {
|
||||
fn = indirectInterface(fn)
|
||||
if !fn.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("call of nil")
|
||||
}
|
||||
typ := v.Type()
|
||||
typ := fn.Type()
|
||||
if typ.Kind() != reflect.Func {
|
||||
return reflect.Value{}, fmt.Errorf("non-function of type %s", typ)
|
||||
}
|
||||
@@ -322,7 +335,7 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
||||
}
|
||||
argv := make([]reflect.Value, len(args))
|
||||
for i, arg := range args {
|
||||
value := indirectInterface(arg)
|
||||
arg = indirectInterface(arg)
|
||||
// Compute the expected type. Clumsy because of variadics.
|
||||
argType := dddType
|
||||
if !typ.IsVariadic() || i < numIn-1 {
|
||||
@@ -330,11 +343,11 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
||||
}
|
||||
|
||||
var err error
|
||||
if argv[i], err = prepareArg(value, argType); err != nil {
|
||||
if argv[i], err = prepareArg(arg, argType); err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
return safeCall(v, argv)
|
||||
return safeCall(fn, argv)
|
||||
}
|
||||
|
||||
// safeCall runs fun.Call(args), and returns the resulting value and error, if
|
||||
@@ -440,47 +453,53 @@ func basicKind(v reflect.Value) (kind, error) {
|
||||
|
||||
// eq evaluates the comparison a == b || a == c || ...
|
||||
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
||||
v1 := indirectInterface(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
arg1 = indirectInterface(arg1)
|
||||
if arg1 != zero {
|
||||
if t1 := arg1.Type(); !t1.Comparable() {
|
||||
return false, fmt.Errorf("uncomparable type %s: %v", t1, arg1)
|
||||
}
|
||||
}
|
||||
if len(arg2) == 0 {
|
||||
return false, errNoComparison
|
||||
}
|
||||
k1, _ := basicKind(arg1)
|
||||
for _, arg := range arg2 {
|
||||
v2 := indirectInterface(arg)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
arg = indirectInterface(arg)
|
||||
k2, _ := basicKind(arg)
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||
truth = arg1.Int() >= 0 && uint64(arg1.Int()) == arg.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||
truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind:
|
||||
truth = v1.Bool() == v2.Bool()
|
||||
truth = arg1.Bool() == arg.Bool()
|
||||
case complexKind:
|
||||
truth = v1.Complex() == v2.Complex()
|
||||
truth = arg1.Complex() == arg.Complex()
|
||||
case floatKind:
|
||||
truth = v1.Float() == v2.Float()
|
||||
truth = arg1.Float() == arg.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() == v2.Int()
|
||||
truth = arg1.Int() == arg.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() == v2.String()
|
||||
truth = arg1.String() == arg.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() == v2.Uint()
|
||||
truth = arg1.Uint() == arg.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
if arg == zero {
|
||||
truth = arg1 == arg
|
||||
} else {
|
||||
if t2 := arg.Type(); !t2.Comparable() {
|
||||
return false, fmt.Errorf("uncomparable type %s: %v", t2, arg)
|
||||
}
|
||||
truth = arg1.Interface() == arg.Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
if truth {
|
||||
@@ -499,13 +518,13 @@ func ne(arg1, arg2 reflect.Value) (bool, error) {
|
||||
|
||||
// lt evaluates the comparison a < b.
|
||||
func lt(arg1, arg2 reflect.Value) (bool, error) {
|
||||
v1 := indirectInterface(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
arg1 = indirectInterface(arg1)
|
||||
k1, err := basicKind(arg1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2 := indirectInterface(arg2)
|
||||
k2, err := basicKind(v2)
|
||||
arg2 = indirectInterface(arg2)
|
||||
k2, err := basicKind(arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -514,9 +533,9 @@ func lt(arg1, arg2 reflect.Value) (bool, error) {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||
truth = arg1.Int() < 0 || uint64(arg1.Int()) < arg2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||
truth = arg2.Int() >= 0 && arg1.Uint() < uint64(arg2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
@@ -525,13 +544,13 @@ func lt(arg1, arg2 reflect.Value) (bool, error) {
|
||||
case boolKind, complexKind:
|
||||
return false, errBadComparisonType
|
||||
case floatKind:
|
||||
truth = v1.Float() < v2.Float()
|
||||
truth = arg1.Float() < arg2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() < v2.Int()
|
||||
truth = arg1.Int() < arg2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() < v2.String()
|
||||
truth = arg1.String() < arg2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() < v2.Uint()
|
||||
truth = arg1.Uint() < arg2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
@@ -634,8 +653,10 @@ var (
|
||||
jsBackslash = []byte(`\\`)
|
||||
jsApos = []byte(`\'`)
|
||||
jsQuot = []byte(`\"`)
|
||||
jsLt = []byte(`\x3C`)
|
||||
jsGt = []byte(`\x3E`)
|
||||
jsLt = []byte(`\u003C`)
|
||||
jsGt = []byte(`\u003E`)
|
||||
jsAmp = []byte(`\u0026`)
|
||||
jsEq = []byte(`\u003D`)
|
||||
)
|
||||
|
||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||
@@ -664,6 +685,10 @@ func JSEscape(w io.Writer, b []byte) {
|
||||
w.Write(jsLt)
|
||||
case '>':
|
||||
w.Write(jsGt)
|
||||
case '&':
|
||||
w.Write(jsAmp)
|
||||
case '=':
|
||||
w.Write(jsEq)
|
||||
default:
|
||||
w.Write(jsLowUni)
|
||||
t, b := c>>4, c&0x0f
|
||||
@@ -698,7 +723,7 @@ func JSEscapeString(s string) string {
|
||||
|
||||
func jsIsSpecial(r rune) bool {
|
||||
switch r {
|
||||
case '\\', '\'', '"', '<', '>':
|
||||
case '\\', '\'', '"', '<', '>', '&', '=':
|
||||
return true
|
||||
}
|
||||
return r < ' ' || utf8.RuneSelf <= r
|
||||
|
@@ -30,7 +30,7 @@ package is auto generated.
|
||||
*/
|
||||
|
||||
// Export it so we can populate Hugo's func map with it, which makes it faster.
|
||||
var GoFuncs = builtinFuncs
|
||||
var GoFuncs = builtinFuncs()
|
||||
|
||||
// Preparer prepares the template before execution.
|
||||
type Preparer interface {
|
||||
|
@@ -244,7 +244,7 @@ func TestAddParseTree(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Add a new parse tree.
|
||||
tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins)
|
||||
tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@@ -411,7 +411,6 @@ func lexInsideAction(l *lexer) stateFn {
|
||||
}
|
||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||
l.emit(itemChar)
|
||||
return lexInsideAction
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -29,6 +28,8 @@ type Node interface {
|
||||
// tree returns the containing *Tree.
|
||||
// It is unexported so all implementations of Node are in this package.
|
||||
tree() *Tree
|
||||
// writeTo writes the String output to the builder.
|
||||
writeTo(*strings.Builder)
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
@@ -94,11 +95,15 @@ func (l *ListNode) tree() *Tree {
|
||||
}
|
||||
|
||||
func (l *ListNode) String() string {
|
||||
b := new(bytes.Buffer)
|
||||
var sb strings.Builder
|
||||
l.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (l *ListNode) writeTo(sb *strings.Builder) {
|
||||
for _, n := range l.Nodes {
|
||||
fmt.Fprint(b, n)
|
||||
n.writeTo(sb)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (l *ListNode) CopyList() *ListNode {
|
||||
@@ -132,6 +137,10 @@ func (t *TextNode) String() string {
|
||||
return fmt.Sprintf(textFormat, t.Text)
|
||||
}
|
||||
|
||||
func (t *TextNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(t.String())
|
||||
}
|
||||
|
||||
func (t *TextNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
@@ -160,23 +169,27 @@ func (p *PipeNode) append(command *CommandNode) {
|
||||
}
|
||||
|
||||
func (p *PipeNode) String() string {
|
||||
s := ""
|
||||
var sb strings.Builder
|
||||
p.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (p *PipeNode) writeTo(sb *strings.Builder) {
|
||||
if len(p.Decl) > 0 {
|
||||
for i, v := range p.Decl {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
s += v.String()
|
||||
v.writeTo(sb)
|
||||
}
|
||||
s += " := "
|
||||
sb.WriteString(" := ")
|
||||
}
|
||||
for i, c := range p.Cmds {
|
||||
if i > 0 {
|
||||
s += " | "
|
||||
sb.WriteString(" | ")
|
||||
}
|
||||
s += c.String()
|
||||
c.writeTo(sb)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PipeNode) tree() *Tree {
|
||||
@@ -187,9 +200,9 @@ func (p *PipeNode) CopyPipe() *PipeNode {
|
||||
if p == nil {
|
||||
return p
|
||||
}
|
||||
var vars []*VariableNode
|
||||
for _, d := range p.Decl {
|
||||
vars = append(vars, d.Copy().(*VariableNode))
|
||||
vars := make([]*VariableNode, len(p.Decl))
|
||||
for i, d := range p.Decl {
|
||||
vars[i] = d.Copy().(*VariableNode)
|
||||
}
|
||||
n := p.tr.newPipeline(p.Pos, p.Line, vars)
|
||||
n.IsAssign = p.IsAssign
|
||||
@@ -219,8 +232,15 @@ func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||
}
|
||||
|
||||
func (a *ActionNode) String() string {
|
||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||
var sb strings.Builder
|
||||
a.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (a *ActionNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString("{{")
|
||||
a.Pipe.writeTo(sb)
|
||||
sb.WriteString("}}")
|
||||
}
|
||||
|
||||
func (a *ActionNode) tree() *Tree {
|
||||
@@ -249,18 +269,24 @@ func (c *CommandNode) append(arg Node) {
|
||||
}
|
||||
|
||||
func (c *CommandNode) String() string {
|
||||
s := ""
|
||||
var sb strings.Builder
|
||||
c.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (c *CommandNode) writeTo(sb *strings.Builder) {
|
||||
for i, arg := range c.Args {
|
||||
if i > 0 {
|
||||
s += " "
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
if arg, ok := arg.(*PipeNode); ok {
|
||||
s += "(" + arg.String() + ")"
|
||||
sb.WriteByte('(')
|
||||
arg.writeTo(sb)
|
||||
sb.WriteByte(')')
|
||||
continue
|
||||
}
|
||||
s += arg.String()
|
||||
arg.writeTo(sb)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *CommandNode) tree() *Tree {
|
||||
@@ -311,6 +337,10 @@ func (i *IdentifierNode) String() string {
|
||||
return i.Ident
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(i.String())
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) tree() *Tree {
|
||||
return i.tr
|
||||
}
|
||||
@@ -333,14 +363,18 @@ func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||
}
|
||||
|
||||
func (v *VariableNode) String() string {
|
||||
s := ""
|
||||
var sb strings.Builder
|
||||
v.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (v *VariableNode) writeTo(sb *strings.Builder) {
|
||||
for i, id := range v.Ident {
|
||||
if i > 0 {
|
||||
s += "."
|
||||
sb.WriteByte('.')
|
||||
}
|
||||
s += id
|
||||
sb.WriteString(id)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *VariableNode) tree() *Tree {
|
||||
@@ -373,6 +407,10 @@ func (d *DotNode) String() string {
|
||||
return "."
|
||||
}
|
||||
|
||||
func (d *DotNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(d.String())
|
||||
}
|
||||
|
||||
func (d *DotNode) tree() *Tree {
|
||||
return d.tr
|
||||
}
|
||||
@@ -403,6 +441,10 @@ func (n *NilNode) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
func (n *NilNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(n.String())
|
||||
}
|
||||
|
||||
func (n *NilNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
@@ -426,11 +468,16 @@ func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||
}
|
||||
|
||||
func (f *FieldNode) String() string {
|
||||
s := ""
|
||||
var sb strings.Builder
|
||||
f.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (f *FieldNode) writeTo(sb *strings.Builder) {
|
||||
for _, id := range f.Ident {
|
||||
s += "." + id
|
||||
sb.WriteByte('.')
|
||||
sb.WriteString(id)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *FieldNode) tree() *Tree {
|
||||
@@ -469,14 +516,23 @@ func (c *ChainNode) Add(field string) {
|
||||
}
|
||||
|
||||
func (c *ChainNode) String() string {
|
||||
s := c.Node.String()
|
||||
var sb strings.Builder
|
||||
c.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (c *ChainNode) writeTo(sb *strings.Builder) {
|
||||
if _, ok := c.Node.(*PipeNode); ok {
|
||||
s = "(" + s + ")"
|
||||
sb.WriteByte('(')
|
||||
c.Node.writeTo(sb)
|
||||
sb.WriteByte(')')
|
||||
} else {
|
||||
c.Node.writeTo(sb)
|
||||
}
|
||||
for _, field := range c.Field {
|
||||
s += "." + field
|
||||
sb.WriteByte('.')
|
||||
sb.WriteString(field)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *ChainNode) tree() *Tree {
|
||||
@@ -506,6 +562,10 @@ func (b *BoolNode) String() string {
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (b *BoolNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(b.String())
|
||||
}
|
||||
|
||||
func (b *BoolNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
@@ -639,6 +699,10 @@ func (n *NumberNode) String() string {
|
||||
return n.Text
|
||||
}
|
||||
|
||||
func (n *NumberNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(n.String())
|
||||
}
|
||||
|
||||
func (n *NumberNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
@@ -666,6 +730,10 @@ func (s *StringNode) String() string {
|
||||
return s.Quoted
|
||||
}
|
||||
|
||||
func (s *StringNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(s.String())
|
||||
}
|
||||
|
||||
func (s *StringNode) tree() *Tree {
|
||||
return s.tr
|
||||
}
|
||||
@@ -690,6 +758,10 @@ func (e *endNode) String() string {
|
||||
return "{{end}}"
|
||||
}
|
||||
|
||||
func (e *endNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(e.String())
|
||||
}
|
||||
|
||||
func (e *endNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
@@ -718,6 +790,10 @@ func (e *elseNode) String() string {
|
||||
return "{{else}}"
|
||||
}
|
||||
|
||||
func (e *elseNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString(e.String())
|
||||
}
|
||||
|
||||
func (e *elseNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
@@ -738,6 +814,12 @@ type BranchNode struct {
|
||||
}
|
||||
|
||||
func (b *BranchNode) String() string {
|
||||
var sb strings.Builder
|
||||
b.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (b *BranchNode) writeTo(sb *strings.Builder) {
|
||||
name := ""
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
@@ -749,10 +831,17 @@ func (b *BranchNode) String() string {
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
sb.WriteString("{{")
|
||||
sb.WriteString(name)
|
||||
sb.WriteByte(' ')
|
||||
b.Pipe.writeTo(sb)
|
||||
sb.WriteString("}}")
|
||||
b.List.writeTo(sb)
|
||||
if b.ElseList != nil {
|
||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||
sb.WriteString("{{else}}")
|
||||
b.ElseList.writeTo(sb)
|
||||
}
|
||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||
sb.WriteString("{{end}}")
|
||||
}
|
||||
|
||||
func (b *BranchNode) tree() *Tree {
|
||||
@@ -826,10 +915,19 @@ func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *Temp
|
||||
}
|
||||
|
||||
func (t *TemplateNode) String() string {
|
||||
if t.Pipe == nil {
|
||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||
var sb strings.Builder
|
||||
t.writeTo(&sb)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (t *TemplateNode) writeTo(sb *strings.Builder) {
|
||||
sb.WriteString("{{template ")
|
||||
sb.WriteString(strconv.Quote(t.Name))
|
||||
if t.Pipe != nil {
|
||||
sb.WriteByte(' ')
|
||||
t.Pipe.writeTo(sb)
|
||||
}
|
||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||
sb.WriteString("}}")
|
||||
}
|
||||
|
||||
func (t *TemplateNode) tree() *Tree {
|
||||
|
@@ -108,13 +108,8 @@ func (t *Tree) nextNonSpace() (token item) {
|
||||
}
|
||||
|
||||
// peekNonSpace returns but does not consume the next non-space token.
|
||||
func (t *Tree) peekNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
func (t *Tree) peekNonSpace() item {
|
||||
token := t.nextNonSpace()
|
||||
t.backup()
|
||||
return token
|
||||
}
|
||||
|
@@ -306,7 +306,8 @@ var parseTests = []parseTest{
|
||||
}
|
||||
|
||||
var builtins = map[string]interface{}{
|
||||
"printf": fmt.Sprintf,
|
||||
"printf": fmt.Sprintf,
|
||||
"contains": strings.Contains,
|
||||
}
|
||||
|
||||
func testParse(doCopy bool, t *testing.T) {
|
||||
@@ -555,3 +556,52 @@ func BenchmarkParseLarge(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sinkv, sinkl string
|
||||
|
||||
func BenchmarkVariableString(b *testing.B) {
|
||||
v := &VariableNode{
|
||||
Ident: []string{"$", "A", "BB", "CCC", "THIS_IS_THE_VARIABLE_BEING_PROCESSED"},
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sinkv = v.String()
|
||||
}
|
||||
if sinkv == "" {
|
||||
b.Fatal("Benchmark was not run")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkListString(b *testing.B) {
|
||||
text := `
|
||||
{{(printf .Field1.Field2.Field3).Value}}
|
||||
{{$x := (printf .Field1.Field2.Field3).Value}}
|
||||
{{$y := (printf $x.Field1.Field2.Field3).Value}}
|
||||
{{$z := $y.Field1.Field2.Field3}}
|
||||
{{if contains $y $z}}
|
||||
{{printf "%q" $y}}
|
||||
{{else}}
|
||||
{{printf "%q" $x}}
|
||||
{{end}}
|
||||
{{with $z.Field1 | contains "boring"}}
|
||||
{{printf "%q" . | printf "%s"}}
|
||||
{{else}}
|
||||
{{printf "%d %d %d" 11 11 11}}
|
||||
{{printf "%d %d %s" 22 22 $x.Field1.Field2.Field3 | printf "%s"}}
|
||||
{{printf "%v" (contains $z.Field1.Field2 $y)}}
|
||||
{{end}}
|
||||
`
|
||||
tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sinkl = tree.Root.String()
|
||||
}
|
||||
if sinkl == "" {
|
||||
b.Fatal("Benchmark was not run")
|
||||
}
|
||||
}
|
||||
|
@@ -110,20 +110,21 @@ func (t *Template) Clone() (*Template, error) {
|
||||
|
||||
// copy returns a shallow copy of t, with common set to the argument.
|
||||
func (t *Template) copy(c *common) *Template {
|
||||
nt := New(t.name)
|
||||
nt.Tree = t.Tree
|
||||
nt.common = c
|
||||
nt.leftDelim = t.leftDelim
|
||||
nt.rightDelim = t.rightDelim
|
||||
return nt
|
||||
return &Template{
|
||||
name: t.name,
|
||||
Tree: t.Tree,
|
||||
common: c,
|
||||
leftDelim: t.leftDelim,
|
||||
rightDelim: t.rightDelim,
|
||||
}
|
||||
}
|
||||
|
||||
// AddParseTree adds parse tree for template with given name and associates it with t.
|
||||
// If the template does not already exist, it will create a new one.
|
||||
// If the template does exist, it will be replaced.
|
||||
// AddParseTree associates the argument parse tree with the template t, giving
|
||||
// it the specified name. If the template has not been defined, this tree becomes
|
||||
// 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.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||
t.init()
|
||||
// If the name is the name of this template, overwrite this template.
|
||||
nt := t
|
||||
if name != t.name {
|
||||
nt = t.New(name)
|
||||
@@ -197,7 +198,7 @@ func (t *Template) Lookup(name string) *Template {
|
||||
func (t *Template) Parse(text string) (*Template, error) {
|
||||
t.init()
|
||||
t.muFuncs.RLock()
|
||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins())
|
||||
t.muFuncs.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
Reference in New Issue
Block a user