mirror of
https://github.com/golang/go
synced 2024-11-22 01:34:41 -07:00
exp/template: functions
Add the ability to attach functions to template and template sets. Make variadic functions and methods work. Still to come: static checking of function names during parse. R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/4643068
This commit is contained in:
parent
9cf37c3723
commit
b177c97803
@ -7,6 +7,7 @@ include ../../../Make.inc
|
|||||||
TARG=exp/template
|
TARG=exp/template
|
||||||
GOFILES=\
|
GOFILES=\
|
||||||
exec.go\
|
exec.go\
|
||||||
|
funcs.go\
|
||||||
lex.go\
|
lex.go\
|
||||||
parse.go\
|
parse.go\
|
||||||
set.go\
|
set.go\
|
||||||
|
@ -174,8 +174,11 @@ func (s *state) evalPipeline(data reflect.Value, pipe []*commandNode) reflect.Va
|
|||||||
|
|
||||||
func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
|
func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
|
||||||
firstWord := cmd.args[0]
|
firstWord := cmd.args[0]
|
||||||
if field, ok := firstWord.(*fieldNode); ok {
|
switch n := firstWord.(type) {
|
||||||
return s.evalFieldNode(data, field, cmd.args, final)
|
case *fieldNode:
|
||||||
|
return s.evalFieldNode(data, n, cmd.args, final)
|
||||||
|
case *identifierNode:
|
||||||
|
return s.evalFieldOrCall(data, n.ident, cmd.args, final)
|
||||||
}
|
}
|
||||||
if len(cmd.args) > 1 || final.IsValid() {
|
if len(cmd.args) > 1 || final.IsValid() {
|
||||||
// TODO: functions
|
// TODO: functions
|
||||||
@ -215,7 +218,7 @@ func (s *state) evalFieldNode(data reflect.Value, field *fieldNode, args []node,
|
|||||||
data = s.evalField(data, field.ident[i])
|
data = s.evalField(data, field.ident[i])
|
||||||
}
|
}
|
||||||
// Now it can be a field or method and if a method, gets arguments.
|
// Now it can be a field or method and if a method, gets arguments.
|
||||||
return s.evalMethodOrField(data, field.ident[n-1], args, final)
|
return s.evalFieldOrCall(data, field.ident[n-1], args, final)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
|
func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
|
||||||
@ -238,14 +241,18 @@ func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalMethodOrField(data reflect.Value, fieldName string, args []node, final reflect.Value) reflect.Value {
|
func (s *state) evalFieldOrCall(data reflect.Value, fieldName string, args []node, final reflect.Value) reflect.Value {
|
||||||
|
// Is it a function?
|
||||||
|
if function, ok := findFunction(fieldName, s.tmpl, s.set); ok {
|
||||||
|
return s.evalCall(data, function, fieldName, false, args, final)
|
||||||
|
}
|
||||||
ptr := data
|
ptr := data
|
||||||
for data.Kind() == reflect.Ptr {
|
for data.Kind() == reflect.Ptr {
|
||||||
ptr, data = data, reflect.Indirect(data)
|
ptr, data = data, reflect.Indirect(data)
|
||||||
}
|
}
|
||||||
// Is it a method? We use the pointer because it has value methods too.
|
// Is it a method? We use the pointer because it has value methods too.
|
||||||
if method, ok := ptr.Type().MethodByName(fieldName); ok {
|
if method, ok := ptr.Type().MethodByName(fieldName); ok {
|
||||||
return s.evalMethod(ptr, method, args, final)
|
return s.evalCall(ptr, method.Func, fieldName, true, args, final)
|
||||||
}
|
}
|
||||||
if len(args) > 1 || final.IsValid() {
|
if len(args) > 1 || final.IsValid() {
|
||||||
s.errorf("%s is not a method but has arguments", fieldName)
|
s.errorf("%s is not a method but has arguments", fieldName)
|
||||||
@ -263,31 +270,46 @@ var (
|
|||||||
osErrorType = reflect.TypeOf(new(os.Error)).Elem()
|
osErrorType = reflect.TypeOf(new(os.Error)).Elem()
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *state) evalMethod(v reflect.Value, method reflect.Method, args []node, final reflect.Value) reflect.Value {
|
func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args []node, final reflect.Value) reflect.Value {
|
||||||
typ := method.Type
|
typ := fun.Type()
|
||||||
fun := method.Func
|
if !isMethod && len(args) > 0 { // Args will be nil if it's a niladic call in an argument list
|
||||||
|
args = args[1:] // first arg is name of function; not used in call.
|
||||||
|
}
|
||||||
numIn := len(args)
|
numIn := len(args)
|
||||||
if final.IsValid() {
|
if final.IsValid() {
|
||||||
numIn++
|
numIn++
|
||||||
}
|
}
|
||||||
if !typ.IsVariadic() && numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
numFixed := len(args)
|
||||||
s.errorf("wrong number of args for %s: want %d got %d", method.Name, typ.NumIn(), len(args))
|
if typ.IsVariadic() {
|
||||||
|
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||||
|
if numIn < numFixed {
|
||||||
|
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||||
}
|
}
|
||||||
// We allow methods with 1 result or 2 results where the second is an os.Error.
|
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||||
switch {
|
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||||
case typ.NumOut() == 1:
|
}
|
||||||
case typ.NumOut() == 2 && typ.Out(1) == osErrorType:
|
if !goodFunc(typ) {
|
||||||
default:
|
s.errorf("can't handle multiple results from method/function %q", name)
|
||||||
s.errorf("can't handle multiple results from method %q", method.Name)
|
|
||||||
}
|
}
|
||||||
// Build the arg list.
|
// Build the arg list.
|
||||||
argv := make([]reflect.Value, numIn)
|
argv := make([]reflect.Value, numIn)
|
||||||
// First arg is the receiver.
|
// First arg is the receiver.
|
||||||
|
i := 0
|
||||||
|
if isMethod {
|
||||||
argv[0] = v
|
argv[0] = v
|
||||||
// Others must be evaluated.
|
i++
|
||||||
for i := 1; i < len(args); i++ {
|
}
|
||||||
|
// Others must be evaluated. Fixed args first.
|
||||||
|
for ; i < numFixed; i++ {
|
||||||
argv[i] = s.evalArg(v, typ.In(i), args[i])
|
argv[i] = s.evalArg(v, typ.In(i), args[i])
|
||||||
}
|
}
|
||||||
|
// And now the ... args.
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||||
|
for ; i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(v, argType, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
// Add final value if necessary.
|
// Add final value if necessary.
|
||||||
if final.IsValid() {
|
if final.IsValid() {
|
||||||
argv[len(args)] = final
|
argv[len(args)] = final
|
||||||
@ -310,23 +332,27 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
|
|||||||
}
|
}
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return s.evalBool(data, typ, n)
|
return s.evalBool(typ, n)
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return s.evalString(data, typ, n)
|
return s.evalString(typ, n)
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return s.evalInteger(data, typ, n)
|
return s.evalInteger(typ, n)
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
return s.evalUnsignedInteger(data, typ, n)
|
return s.evalUnsignedInteger(typ, n)
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return s.evalFloat(data, typ, n)
|
return s.evalFloat(typ, n)
|
||||||
case reflect.Complex64, reflect.Complex128:
|
case reflect.Complex64, reflect.Complex128:
|
||||||
return s.evalComplex(data, typ, n)
|
return s.evalComplex(typ, n)
|
||||||
|
case reflect.Interface:
|
||||||
|
if typ.NumMethod() == 0 {
|
||||||
|
return s.evalEmptyInterface(data, typ, n)
|
||||||
}
|
}
|
||||||
s.errorf("can't handle node %s for method arg of type %s", n, typ)
|
}
|
||||||
|
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalBool(v reflect.Value, typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalBool(typ reflect.Type, n node) reflect.Value {
|
||||||
if n, ok := n.(*boolNode); ok {
|
if n, ok := n.(*boolNode); ok {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetBool(n.true)
|
value.SetBool(n.true)
|
||||||
@ -336,7 +362,7 @@ func (s *state) evalBool(v reflect.Value, typ reflect.Type, n node) reflect.Valu
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalString(v reflect.Value, typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalString(typ reflect.Type, n node) reflect.Value {
|
||||||
if n, ok := n.(*stringNode); ok {
|
if n, ok := n.(*stringNode); ok {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetString(n.text)
|
value.SetString(n.text)
|
||||||
@ -346,7 +372,7 @@ func (s *state) evalString(v reflect.Value, typ reflect.Type, n node) reflect.Va
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalInteger(v reflect.Value, typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalInteger(typ reflect.Type, n node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isInt {
|
if n, ok := n.(*numberNode); ok && n.isInt {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetInt(n.int64)
|
value.SetInt(n.int64)
|
||||||
@ -356,7 +382,7 @@ func (s *state) evalInteger(v reflect.Value, typ reflect.Type, n node) reflect.V
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalUnsignedInteger(v reflect.Value, typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalUnsignedInteger(typ reflect.Type, n node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isUint {
|
if n, ok := n.(*numberNode); ok && n.isUint {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetUint(n.uint64)
|
value.SetUint(n.uint64)
|
||||||
@ -366,7 +392,7 @@ func (s *state) evalUnsignedInteger(v reflect.Value, typ reflect.Type, n node) r
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalFloat(typ reflect.Type, n node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isFloat {
|
if n, ok := n.(*numberNode); ok && n.isFloat {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetFloat(n.float64)
|
value.SetFloat(n.float64)
|
||||||
@ -376,7 +402,7 @@ func (s *state) evalFloat(v reflect.Value, typ reflect.Type, n node) reflect.Val
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) evalComplex(v reflect.Value, typ reflect.Type, n node) reflect.Value {
|
func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
|
||||||
if n, ok := n.(*numberNode); ok && n.isComplex {
|
if n, ok := n.(*numberNode); ok && n.isComplex {
|
||||||
value := reflect.New(typ).Elem()
|
value := reflect.New(typ).Elem()
|
||||||
value.SetComplex(n.complex128)
|
value.SetComplex(n.complex128)
|
||||||
@ -386,6 +412,34 @@ func (s *state) evalComplex(v reflect.Value, typ reflect.Type, n node) reflect.V
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *state) evalEmptyInterface(data reflect.Value, typ reflect.Type, n node) reflect.Value {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *boolNode:
|
||||||
|
return reflect.ValueOf(n.true)
|
||||||
|
case *fieldNode:
|
||||||
|
return s.evalFieldNode(data, n, nil, reflect.Value{})
|
||||||
|
case *identifierNode:
|
||||||
|
return s.evalFieldOrCall(data, n.ident, nil, reflect.Value{})
|
||||||
|
case *numberNode:
|
||||||
|
if n.isComplex {
|
||||||
|
return reflect.ValueOf(n.complex128)
|
||||||
|
}
|
||||||
|
if n.isInt {
|
||||||
|
return reflect.ValueOf(n.int64)
|
||||||
|
}
|
||||||
|
if n.isUint {
|
||||||
|
return reflect.ValueOf(n.uint64)
|
||||||
|
}
|
||||||
|
if n.isFloat {
|
||||||
|
return reflect.ValueOf(n.float64)
|
||||||
|
}
|
||||||
|
case *stringNode:
|
||||||
|
return reflect.ValueOf(n.text)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
// printValue writes the textual representation of the value to the output of
|
// printValue writes the textual representation of the value to the output of
|
||||||
// the template.
|
// the template.
|
||||||
func (s *state) printValue(n node, v reflect.Value) {
|
func (s *state) printValue(n node, v reflect.Value) {
|
||||||
|
@ -140,6 +140,16 @@ var execTests = []execTest{
|
|||||||
{"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
{"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
||||||
{"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
{"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
|
||||||
{"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
{"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
|
||||||
|
// Function calls.
|
||||||
|
{"printf", `{{printf "hello, printf"}}`, "hello, printf", tVal, true},
|
||||||
|
{"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
|
||||||
|
{"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
|
||||||
|
{"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true},
|
||||||
|
{"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true},
|
||||||
|
{"printf function", `{{printf "%#q" gopher}}`, "`gopher`", tVal, true},
|
||||||
|
{"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true},
|
||||||
|
{"printf method", `{{printf "%s" .Method0}}`, "resultOfMethod0", tVal, true},
|
||||||
|
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) resultOfMethod0", tVal, true},
|
||||||
// With.
|
// With.
|
||||||
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
|
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
|
||||||
{"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
|
{"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
|
||||||
@ -171,10 +181,15 @@ var execTests = []execTest{
|
|||||||
{"error method, no error", "{{.EPERM false}}", "false", tVal, true},
|
{"error method, no error", "{{.EPERM false}}", "false", tVal, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gopher() string {
|
||||||
|
return "gopher"
|
||||||
|
}
|
||||||
|
|
||||||
func testExecute(execTests []execTest, set *Set, t *testing.T) {
|
func testExecute(execTests []execTest, set *Set, t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
|
funcs := FuncMap{"gopher": gopher}
|
||||||
for _, test := range execTests {
|
for _, test := range execTests {
|
||||||
tmpl := New(test.name)
|
tmpl := New(test.name).Funcs(funcs)
|
||||||
err := tmpl.Parse(test.input)
|
err := tmpl.Parse(test.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s: parse error: %s", test.name, err)
|
t.Errorf("%s: parse error: %s", test.name, err)
|
||||||
|
63
src/pkg/exp/template/funcs.go
Normal file
63
src/pkg/exp/template/funcs.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||||
|
// Each function must have either a single return value, or two return values of
|
||||||
|
// which the second has type os.Error.
|
||||||
|
type FuncMap map[string]interface{}
|
||||||
|
|
||||||
|
var funcs = map[string]reflect.Value{
|
||||||
|
"printf": reflect.ValueOf(fmt.Sprintf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||||
|
func addFuncs(values map[string]reflect.Value, funcMap FuncMap) {
|
||||||
|
for name, fn := range funcMap {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
if v.Kind() != reflect.Func {
|
||||||
|
panic("value for " + name + " not a function")
|
||||||
|
}
|
||||||
|
if !goodFunc(v.Type()) {
|
||||||
|
panic(fmt.Errorf("can't handle multiple results from method/function %q", name))
|
||||||
|
}
|
||||||
|
values[name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// goodFunc checks that the function or method has the right result signature.
|
||||||
|
func goodFunc(typ reflect.Type) bool {
|
||||||
|
// We allow functions with 1 result or 2 results where the second is an os.Error.
|
||||||
|
switch {
|
||||||
|
case typ.NumOut() == 1:
|
||||||
|
return true
|
||||||
|
case typ.NumOut() == 2 && typ.Out(1) == osErrorType:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// findFunction looks for a function in the template, set, and global map.
|
||||||
|
func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) {
|
||||||
|
if tmpl != nil {
|
||||||
|
if fn := tmpl.funcs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if set != nil {
|
||||||
|
if fn := set.funcs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fn := funcs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
return reflect.Value{}, false
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
type Template struct {
|
type Template struct {
|
||||||
name string
|
name string
|
||||||
root *listNode
|
root *listNode
|
||||||
|
funcs map[string]reflect.Value
|
||||||
// Parsing.
|
// Parsing.
|
||||||
lex *lexer
|
lex *lexer
|
||||||
tokens <-chan item
|
tokens <-chan item
|
||||||
@ -451,11 +453,22 @@ func (w *withNode) String() string {
|
|||||||
func New(name string) *Template {
|
func New(name string) *Template {
|
||||||
return &Template{
|
return &Template{
|
||||||
name: name,
|
name: name,
|
||||||
|
funcs: make(map[string]reflect.Value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Funcs adds to the template's function map the elements of the
|
||||||
|
// argument map. It panics if a value in the map is not a function
|
||||||
|
// with appropriate return type.
|
||||||
|
// The return value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||||
|
addFuncs(t.funcs, funcMap)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
// errorf formats the error and terminates processing.
|
// errorf formats the error and terminates processing.
|
||||||
func (t *Template) errorf(format string, args ...interface{}) {
|
func (t *Template) errorf(format string, args ...interface{}) {
|
||||||
|
t.root = nil
|
||||||
format = fmt.Sprintf("template: %s:%d: %s", t.name, t.lex.lineNumber(), format)
|
format = fmt.Sprintf("template: %s:%d: %s", t.name, t.lex.lineNumber(), format)
|
||||||
panic(fmt.Errorf(format, args...))
|
panic(fmt.Errorf(format, args...))
|
||||||
}
|
}
|
||||||
@ -488,7 +501,6 @@ func (t *Template) recover(errp *os.Error) {
|
|||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
t.stopParse()
|
t.stopParse()
|
||||||
t.root = nil
|
|
||||||
*errp = e.(os.Error)
|
*errp = e.(os.Error)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -6,6 +6,7 @@ package template
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@ -14,15 +15,26 @@ import (
|
|||||||
// A template may be a member of multiple sets.
|
// A template may be a member of multiple sets.
|
||||||
type Set struct {
|
type Set struct {
|
||||||
tmpl map[string]*Template
|
tmpl map[string]*Template
|
||||||
|
funcs map[string]reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSet allocates a new, empty template set.
|
// NewSet allocates a new, empty template set.
|
||||||
func NewSet() *Set {
|
func NewSet() *Set {
|
||||||
return &Set{
|
return &Set{
|
||||||
tmpl: make(map[string]*Template),
|
tmpl: make(map[string]*Template),
|
||||||
|
funcs: make(map[string]reflect.Value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Funcs adds to the set's function map the elements of the
|
||||||
|
// argument map. It panics if a value in the map is not a function
|
||||||
|
// with appropriate return type.
|
||||||
|
// The return value is the set, so calls can be chained.
|
||||||
|
func (s *Set) Funcs(funcMap FuncMap) *Set {
|
||||||
|
addFuncs(s.funcs, funcMap)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// recover is the handler that turns panics into returns from the top
|
// recover is the handler that turns panics into returns from the top
|
||||||
// level of Parse.
|
// level of Parse.
|
||||||
func (s *Set) recover(errp *os.Error) {
|
func (s *Set) recover(errp *os.Error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user