aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/text
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@google.com>2016-02-03 21:58:02 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2016-02-03 21:58:02 +0000
commitf98dd1a338867a408f7c72d73fbad7fe7fc93e3a (patch)
tree2f8da9862a9c1fe0df138917f997b03439c02773 /libgo/go/text
parentb081ed4efc144da0c45a6484aebfd10e0eb9fda3 (diff)
downloadgcc-f98dd1a338867a408f7c72d73fbad7fe7fc93e3a.zip
gcc-f98dd1a338867a408f7c72d73fbad7fe7fc93e3a.tar.gz
gcc-f98dd1a338867a408f7c72d73fbad7fe7fc93e3a.tar.bz2
libgo: Update to go1.6rc1.
Reviewed-on: https://go-review.googlesource.com/19200 From-SVN: r233110
Diffstat (limited to 'libgo/go/text')
-rw-r--r--libgo/go/text/scanner/scanner.go2
-rw-r--r--libgo/go/text/template/doc.go39
-rw-r--r--libgo/go/text/template/exec.go69
-rw-r--r--libgo/go/text/template/exec_test.go211
-rw-r--r--libgo/go/text/template/funcs.go79
-rw-r--r--libgo/go/text/template/multi_test.go22
-rw-r--r--libgo/go/text/template/parse/lex.go98
-rw-r--r--libgo/go/text/template/parse/lex_test.go31
-rw-r--r--libgo/go/text/template/parse/parse.go83
-rw-r--r--libgo/go/text/template/parse/parse_test.go34
-rw-r--r--libgo/go/text/template/template.go37
11 files changed, 553 insertions, 152 deletions
diff --git a/libgo/go/text/scanner/scanner.go b/libgo/go/text/scanner/scanner.go
index 3ab01ed..0155800 100644
--- a/libgo/go/text/scanner/scanner.go
+++ b/libgo/go/text/scanner/scanner.go
@@ -73,7 +73,7 @@ const (
GoTokens = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments
)
-// The result of Scan is one of the following tokens or a Unicode character.
+// The result of Scan is one of these tokens or a Unicode character.
const (
EOF = -(iota + 1)
Ident
diff --git a/libgo/go/text/template/doc.go b/libgo/go/text/template/doc.go
index 0ce63f6..df8c95f 100644
--- a/libgo/go/text/template/doc.go
+++ b/libgo/go/text/template/doc.go
@@ -36,10 +36,35 @@ Here is a trivial example that prints "17 items are made of wool".
More intricate examples appear below.
+Text and spaces
+
+By default, all text between actions is copied verbatim when the template is
+executed. For example, the string " items are made of " in the example above appears
+on standard output when the program is run.
+
+However, to aid in formatting template source code, if an action's left delimiter
+(by default "{{") is followed immediately by a minus sign and ASCII space character
+("{{- "), all trailing white space is trimmed from the immediately preceding text.
+Similarly, if the right delimiter ("}}") is preceded by a space and minus sign
+(" -}}"), all leading white space is trimmed from the immediately following text.
+In these trim markers, the ASCII space must be present; "{{-3}}" parses as an
+action containing the number -3.
+
+For instance, when executing the template whose source is
+
+ "{{23 -}} < {{- 45}}"
+
+the generated output would be
+
+ "23<45"
+
+For this trimming, the definition of white space characters is the same as in Go:
+space, horizontal tab, carriage return, and newline.
+
Actions
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
-data, defined in detail below.
+data, defined in detail in the corresponding sections that follow.
*/
// {{/* a comment */}}
@@ -90,6 +115,14 @@ data, defined in detail below.
The template with the specified name is executed with dot set
to the value of the pipeline.
+ {{block "name" pipeline}} T1 {{end}}
+ A block is shorthand for defining a template
+ {{define "name"}} T1 {{end}}
+ and then executing it in place
+ {{template "name" .}}
+ The typical use is to define a set of root templates that are
+ then customized by redefining the block templates within.
+
{{with pipeline}} T1 {{end}}
If the value of the pipeline is empty, no output is generated;
otherwise, dot is set to the value of the pipeline and T1 is
@@ -167,6 +200,8 @@ field of a struct, the function is not invoked automatically, but it
can be used as a truth value for an if action and the like. To invoke
it, use the call function, defined below.
+Pipelines
+
A pipeline is a possibly chained sequence of "commands". A command is a simple
value (argument) or a function or method call, possibly with multiple arguments:
@@ -184,8 +219,6 @@ value (argument) or a function or method call, possibly with multiple arguments:
function(Argument1, etc.)
Functions and function names are described below.
-Pipelines
-
A pipeline may be "chained" by separating a sequence of commands with pipeline
characters '|'. In a chained pipeline, the result of the each command is
passed as the last argument of the following command. The output of the final
diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go
index daba788..efe1817 100644
--- a/libgo/go/text/template/exec.go
+++ b/libgo/go/text/template/exec.go
@@ -78,7 +78,23 @@ func doublePercent(str string) string {
return str
}
-// errorf formats the error and terminates processing.
+// TODO: It would be nice if ExecError was more broken down, but
+// the way ErrorContext embeds the template name makes the
+// processing too clumsy.
+
+// ExecError is the custom error type returned when Execute has an
+// error evaluating its template. (If a write error occurs, the actual
+// error is returned; it will not be of type ExecError.)
+type ExecError struct {
+ Name string // Name of template.
+ Err error // Pre-formatted error.
+}
+
+func (e ExecError) Error() string {
+ return e.Err.Error()
+}
+
+// errorf records an ExecError and terminates processing.
func (s *state) errorf(format string, args ...interface{}) {
name := doublePercent(s.tmpl.Name())
if s.node == nil {
@@ -87,7 +103,24 @@ func (s *state) errorf(format string, args ...interface{}) {
location, context := s.tmpl.ErrorContext(s.node)
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
}
- panic(fmt.Errorf(format, args...))
+ panic(ExecError{
+ Name: s.tmpl.Name(),
+ Err: fmt.Errorf(format, args...),
+ })
+}
+
+// writeError is the wrapper type used internally when Execute has an
+// error writing to its output. We strip the wrapper in errRecover.
+// Note that this is not an implementation of error, so it cannot escape
+// from the package as an error value.
+type writeError struct {
+ Err error // Original error.
+}
+
+func (s *state) writeError(err error) {
+ panic(writeError{
+ Err: err,
+ })
}
// errRecover is the handler that turns panics into returns from the top
@@ -98,8 +131,10 @@ func errRecover(errp *error) {
switch err := e.(type) {
case runtime.Error:
panic(e)
- case error:
- *errp = err
+ case writeError:
+ *errp = err.Err // Strip the wrapper.
+ case ExecError:
+ *errp = err // Keep the wrapper.
default:
panic(e)
}
@@ -145,7 +180,7 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
}
// DefinedTemplates returns a string listing the defined templates,
-// prefixed by the string "defined templates are: ". If there are none,
+// prefixed by the string "; defined templates are: ". If there are none,
// it returns the empty string. For generating an error message here
// and in html/template.
func (t *Template) DefinedTemplates() string {
@@ -193,7 +228,7 @@ func (s *state) walk(dot reflect.Value, node parse.Node) {
s.walkTemplate(dot, node)
case *parse.TextNode:
if _, err := s.wr.Write(node.Text); err != nil {
- s.errorf("%s", err)
+ s.writeError(err)
}
case *parse.WithNode:
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
@@ -222,8 +257,13 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.
}
}
-// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
-// and whether the value has a meaningful truth value.
+// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
+// and whether the value has a meaningful truth value. This is the definition of
+// truth used by if and other such actions.
+func IsTrue(val interface{}) (truth, ok bool) {
+ return isTrue(reflect.ValueOf(val))
+}
+
func isTrue(val reflect.Value) (truth, ok bool) {
if !val.IsValid() {
// Something like var x interface{}, never set. It's a form of nil.
@@ -483,7 +523,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
return zero
}
typ := receiver.Type()
- receiver, _ = indirect(receiver)
+ receiver, isNil := indirect(receiver)
// Unless it's an interface, need to get to a value of type *T to guarantee
// we see all methods of T and *T.
ptr := receiver
@@ -495,7 +535,6 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
}
hasArgs := len(args) > 1 || final.IsValid()
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
- receiver, isNil := indirect(receiver)
if isNil {
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
}
@@ -789,16 +828,11 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu
}
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
-// We indirect through pointers and empty interfaces (only) because
-// non-empty interfaces have methods we might need.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
- if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
- break
- }
}
return v, false
}
@@ -811,7 +845,10 @@ func (s *state) printValue(n parse.Node, v reflect.Value) {
if !ok {
s.errorf("can't print %s of type %s", n, v.Type())
}
- fmt.Fprint(s.wr, iface)
+ _, err := fmt.Fprint(s.wr, iface)
+ if err != nil {
+ s.writeError(err)
+ }
}
// printableValue returns the, possibly indirected, interface value inside v that
diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go
index ba0e434..e507e91 100644
--- a/libgo/go/text/template/exec_test.go
+++ b/libgo/go/text/template/exec_test.go
@@ -9,6 +9,7 @@ import (
"errors"
"flag"
"fmt"
+ "io/ioutil"
"reflect"
"strings"
"testing"
@@ -50,8 +51,9 @@ type T struct {
Empty2 interface{}
Empty3 interface{}
Empty4 interface{}
- // Non-empty interface.
- NonEmptyInterface I
+ // Non-empty interfaces.
+ NonEmptyInterface I
+ NonEmptyInterfacePtS *I
// Stringer.
Str fmt.Stringer
Err error
@@ -72,6 +74,12 @@ type T struct {
unexported int
}
+type S []string
+
+func (S) Method0() string {
+ return "M0"
+}
+
type U struct {
V string
}
@@ -98,6 +106,8 @@ func (w *W) Error() string {
return fmt.Sprintf("[%d]", w.k)
}
+var siVal = I(S{"a", "b"})
+
var tVal = &T{
True: true,
I: 17,
@@ -118,22 +128,23 @@ var tVal = &T{
{"one": 1, "two": 2},
{"eleven": 11, "twelve": 12},
},
- Empty1: 3,
- Empty2: "empty2",
- Empty3: []int{7, 8},
- Empty4: &U{"UinEmpty"},
- NonEmptyInterface: new(T),
- Str: bytes.NewBuffer([]byte("foozle")),
- Err: errors.New("erroozle"),
- PI: newInt(23),
- PS: newString("a string"),
- PSI: newIntSlice(21, 22, 23),
- BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
- VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
- VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
- NilOKFunc: func(s *int) bool { return s == nil },
- ErrFunc: func() (string, error) { return "bla", nil },
- Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
+ Empty1: 3,
+ Empty2: "empty2",
+ Empty3: []int{7, 8},
+ Empty4: &U{"UinEmpty"},
+ NonEmptyInterface: &T{X: "x"},
+ NonEmptyInterfacePtS: &siVal,
+ Str: bytes.NewBuffer([]byte("foozle")),
+ Err: errors.New("erroozle"),
+ PI: newInt(23),
+ PS: newString("a string"),
+ PSI: newIntSlice(21, 22, 23),
+ BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
+ VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
+ VariadicFuncInt: func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
+ NilOKFunc: func(s *int) bool { return s == nil },
+ ErrFunc: func() (string, error) { return "bla", nil },
+ Tmpl: Must(New("x").Parse("test template")), // "x" is the value of .X
}
// A non-empty interface.
@@ -336,6 +347,7 @@ var execTests = []execTest{
{"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
{"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true},
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
+ {"call nil", "{{call nil}}", "", tVal, false},
// Erroneous function calls (check args).
{".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false},
@@ -424,12 +436,15 @@ var execTests = []execTest{
{"slice[1]", "{{index .SI 1}}", "4", tVal, true},
{"slice[HUGE]", "{{index .SI 10}}", "", tVal, false},
{"slice[WRONG]", "{{index .SI `hello`}}", "", tVal, false},
+ {"slice[nil]", "{{index .SI nil}}", "", tVal, false},
{"map[one]", "{{index .MSI `one`}}", "1", tVal, true},
{"map[two]", "{{index .MSI `two`}}", "2", tVal, true},
{"map[NO]", "{{index .MSI `XXX`}}", "0", tVal, true},
- {"map[nil]", "{{index .MSI nil}}", "0", tVal, true},
+ {"map[nil]", "{{index .MSI nil}}", "", tVal, false},
+ {"map[``]", "{{index .MSI ``}}", "0", tVal, true},
{"map[WRONG]", "{{index .MSI 10}}", "", tVal, false},
{"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true},
+ {"nil[1]", "{{index nil 1}}", "", tVal, false},
// Len.
{"slice", "{{len .SI}}", "3", tVal, true},
@@ -545,6 +560,11 @@ var execTests = []execTest{
{"bug16i", "{{\"aaa\"|oneArg}}", "oneArg=aaa", tVal, true},
{"bug16j", "{{1+2i|printf \"%v\"}}", "(1+2i)", tVal, true},
{"bug16k", "{{\"aaa\"|printf }}", "aaa", tVal, true},
+ {"bug17a", "{{.NonEmptyInterface.X}}", "x", tVal, true},
+ {"bug17b", "-{{.NonEmptyInterface.Method1 1234}}-", "-1234-", tVal, true},
+ {"bug17c", "{{len .NonEmptyInterfacePtS}}", "2", tVal, true},
+ {"bug17d", "{{index .NonEmptyInterfacePtS 0}}", "a", tVal, true},
+ {"bug17e", "{{range .NonEmptyInterfacePtS}}-{{.}}-{{end}}", "-a--b-", tVal, true},
}
func zeroArgs() string {
@@ -796,18 +816,19 @@ type Tree struct {
}
// Use different delimiters to test Set.Delims.
+// Also test the trimming of leading and trailing spaces.
const treeTemplate = `
- (define "tree")
+ (- define "tree" -)
[
- (.Val)
- (with .Left)
- (template "tree" .)
- (end)
- (with .Right)
- (template "tree" .)
- (end)
+ (- .Val -)
+ (- with .Left -)
+ (template "tree" . -)
+ (- end -)
+ (- with .Right -)
+ (- template "tree" . -)
+ (- end -)
]
- (end)
+ (- end -)
`
func TestTree(t *testing.T) {
@@ -852,19 +873,13 @@ func TestTree(t *testing.T) {
t.Fatal("parse error:", err)
}
var b bytes.Buffer
- stripSpace := func(r rune) rune {
- if r == '\t' || r == '\n' {
- return -1
- }
- return r
- }
const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]"
// First by looking up the template.
err = tmpl.Lookup("tree").Execute(&b, tree)
if err != nil {
t.Fatal("exec error:", err)
}
- result := strings.Map(stripSpace, b.String())
+ result := b.String()
if result != expect {
t.Errorf("expected %q got %q", expect, result)
}
@@ -874,7 +889,7 @@ func TestTree(t *testing.T) {
if err != nil {
t.Fatal("exec error:", err)
}
- result = strings.Map(stripSpace, b.String())
+ result = b.String()
if result != expect {
t.Errorf("expected %q got %q", expect, result)
}
@@ -1141,3 +1156,127 @@ func TestUnterminatedStringError(t *testing.T) {
t.Fatalf("unexpected error: %s", str)
}
}
+
+const alwaysErrorText = "always be failing"
+
+var alwaysError = errors.New(alwaysErrorText)
+
+type ErrorWriter int
+
+func (e ErrorWriter) Write(p []byte) (int, error) {
+ return 0, alwaysError
+}
+
+func TestExecuteGivesExecError(t *testing.T) {
+ // First, a non-execution error shouldn't be an ExecError.
+ tmpl, err := New("X").Parse("hello")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = tmpl.Execute(ErrorWriter(0), 0)
+ if err == nil {
+ t.Fatal("expected error; got none")
+ }
+ if err.Error() != alwaysErrorText {
+ t.Errorf("expected %q error; got %q", alwaysErrorText, err)
+ }
+ // This one should be an ExecError.
+ tmpl, err = New("X").Parse("hello, {{.X.Y}}")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = tmpl.Execute(ioutil.Discard, 0)
+ if err == nil {
+ t.Fatal("expected error; got none")
+ }
+ eerr, ok := err.(ExecError)
+ if !ok {
+ t.Fatalf("did not expect ExecError %s", eerr)
+ }
+ expect := "field X in type int"
+ if !strings.Contains(err.Error(), expect) {
+ t.Errorf("expected %q; got %q", expect, err)
+ }
+}
+
+func funcNameTestFunc() int {
+ return 0
+}
+
+func TestGoodFuncNames(t *testing.T) {
+ names := []string{
+ "_",
+ "a",
+ "a1",
+ "a1",
+ "Ӵ",
+ }
+ for _, name := range names {
+ tmpl := New("X").Funcs(
+ FuncMap{
+ name: funcNameTestFunc,
+ },
+ )
+ if tmpl == nil {
+ t.Fatalf("nil result for %q", name)
+ }
+ }
+}
+
+func TestBadFuncNames(t *testing.T) {
+ names := []string{
+ "",
+ "2",
+ "a-b",
+ }
+ for _, name := range names {
+ testBadFuncName(name, t)
+ }
+}
+
+func testBadFuncName(name string, t *testing.T) {
+ defer func() {
+ recover()
+ }()
+ New("X").Funcs(
+ FuncMap{
+ name: funcNameTestFunc,
+ },
+ )
+ // If we get here, the name did not cause a panic, which is how Funcs
+ // reports an error.
+ t.Errorf("%q succeeded incorrectly as function name", name)
+}
+
+func TestBlock(t *testing.T) {
+ const (
+ input = `a({{block "inner" .}}bar({{.}})baz{{end}})b`
+ want = `a(bar(hello)baz)b`
+ overlay = `{{define "inner"}}foo({{.}})bar{{end}}`
+ want2 = `a(foo(goodbye)bar)b`
+ )
+ tmpl, err := New("outer").Parse(input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tmpl2, err := Must(tmpl.Clone()).Parse(overlay)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var buf bytes.Buffer
+ if err := tmpl.Execute(&buf, "hello"); err != nil {
+ t.Fatal(err)
+ }
+ if got := buf.String(); got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+
+ buf.Reset()
+ if err := tmpl2.Execute(&buf, "goodbye"); err != nil {
+ t.Fatal(err)
+ }
+ if got := buf.String(); got != want2 {
+ t.Errorf("got %q, want %q", got, want2)
+ }
+}
diff --git a/libgo/go/text/template/funcs.go b/libgo/go/text/template/funcs.go
index ccd0dfc..49e9e74 100644
--- a/libgo/go/text/template/funcs.go
+++ b/libgo/go/text/template/funcs.go
@@ -58,6 +58,9 @@ func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
for name, fn := range in {
+ if !goodName(name) {
+ panic(fmt.Errorf("function name %s is not a valid identifier", name))
+ }
v := reflect.ValueOf(fn)
if v.Kind() != reflect.Func {
panic("value for " + name + " not a function")
@@ -77,7 +80,7 @@ func addFuncs(out, in FuncMap) {
}
}
-// goodFunc checks that the function or method has the right result signature.
+// goodFunc reports whether 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 error.
switch {
@@ -89,6 +92,23 @@ func goodFunc(typ reflect.Type) bool {
return false
}
+// goodName reports whether the function name is a valid identifier.
+func goodName(name string) bool {
+ if name == "" {
+ return false
+ }
+ for i, r := range name {
+ switch {
+ case r == '_':
+ case i == 0 && !unicode.IsLetter(r):
+ return false
+ case !unicode.IsLetter(r) && !unicode.IsDigit(r):
+ return false
+ }
+ }
+ return true
+}
+
// findFunction looks for a function in the template, and global map.
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
if tmpl != nil && tmpl.common != nil {
@@ -104,6 +124,21 @@ func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
return reflect.Value{}, false
}
+// prepareArg checks if value can be used as an argument of type argType, and
+// converts an invalid value to appropriate zero if possible.
+func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
+ if !value.IsValid() {
+ if !canBeNil(argType) {
+ return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
+ }
+ value = reflect.Zero(argType)
+ }
+ if !value.Type().AssignableTo(argType) {
+ return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
+ }
+ return value, nil
+}
+
// Indexing.
// index returns the result of indexing its first argument by the following
@@ -111,6 +146,9 @@ func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
// indexed item must be a map, slice, or array.
func index(item interface{}, indices ...interface{}) (interface{}, error) {
v := reflect.ValueOf(item)
+ if !v.IsValid() {
+ return nil, fmt.Errorf("index of untyped nil")
+ }
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
@@ -125,6 +163,8 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
+ case reflect.Invalid:
+ return nil, fmt.Errorf("cannot index slice/array with nil")
default:
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
}
@@ -133,17 +173,18 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
}
v = v.Index(int(x))
case reflect.Map:
- if !index.IsValid() {
- index = reflect.Zero(v.Type().Key())
- }
- if !index.Type().AssignableTo(v.Type().Key()) {
- return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
+ index, err := prepareArg(index, v.Type().Key())
+ if err != nil {
+ return nil, err
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
v = reflect.Zero(v.Type().Elem())
}
+ case reflect.Invalid:
+ // the loop holds invariant: v.IsValid()
+ panic("unreachable")
default:
return nil, fmt.Errorf("can't index item of type %s", v.Type())
}
@@ -155,7 +196,11 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
// length returns the length of the item, with an error if it has no defined length.
func length(item interface{}) (int, error) {
- v, isNil := indirect(reflect.ValueOf(item))
+ v := reflect.ValueOf(item)
+ if !v.IsValid() {
+ return 0, fmt.Errorf("len of untyped nil")
+ }
+ v, isNil := indirect(v)
if isNil {
return 0, fmt.Errorf("len of nil pointer")
}
@@ -172,6 +217,9 @@ func length(item interface{}) (int, error) {
// The function must return 1 result, or 2 results, the second of which is an error.
func call(fn interface{}, args ...interface{}) (interface{}, error) {
v := reflect.ValueOf(fn)
+ if !v.IsValid() {
+ return nil, fmt.Errorf("call of nil")
+ }
typ := v.Type()
if typ.Kind() != reflect.Func {
return nil, fmt.Errorf("non-function of type %s", typ)
@@ -201,13 +249,11 @@ func call(fn interface{}, args ...interface{}) (interface{}, error) {
} else {
argType = dddType
}
- if !value.IsValid() && canBeNil(argType) {
- value = reflect.Zero(argType)
- }
- if !value.Type().AssignableTo(argType) {
- return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
+
+ var err error
+ if argv[i], err = prepareArg(value, argType); err != nil {
+ return nil, fmt.Errorf("arg %d: %s", i, err)
}
- argv[i] = value
}
result := v.Call(argv)
if len(result) == 2 && !result[1].IsNil() {
@@ -219,7 +265,7 @@ func call(fn interface{}, args ...interface{}) (interface{}, error) {
// Boolean logic.
func truth(a interface{}) bool {
- t, _ := isTrue(reflect.ValueOf(a))
+ t, _ := IsTrue(a)
return t
}
@@ -254,9 +300,8 @@ func or(arg0 interface{}, args ...interface{}) interface{} {
}
// not returns the Boolean negation of its argument.
-func not(arg interface{}) (truth bool) {
- truth, _ = isTrue(reflect.ValueOf(arg))
- return !truth
+func not(arg interface{}) bool {
+ return !truth(arg)
}
// Comparison.
diff --git a/libgo/go/text/template/multi_test.go b/libgo/go/text/template/multi_test.go
index ea01875..a8342f5 100644
--- a/libgo/go/text/template/multi_test.go
+++ b/libgo/go/text/template/multi_test.go
@@ -9,7 +9,6 @@ package template
import (
"bytes"
"fmt"
- "strings"
"testing"
"text/template/parse"
)
@@ -277,17 +276,11 @@ func TestRedefinition(t *testing.T) {
if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil {
t.Fatalf("parse 1: %v", err)
}
- if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err == nil {
- t.Fatal("expected error")
+ if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err != nil {
+ t.Fatalf("got error %v, expected nil", err)
}
- if !strings.Contains(err.Error(), "redefinition") {
- t.Fatalf("expected redefinition error; got %v", err)
- }
- if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err == nil {
- t.Fatal("expected error")
- }
- if !strings.Contains(err.Error(), "redefinition") {
- t.Fatalf("expected redefinition error; got %v", err)
+ if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err != nil {
+ t.Fatalf("got error %v, expected nil", err)
}
}
@@ -345,7 +338,6 @@ func TestNew(t *testing.T) {
func TestParse(t *testing.T) {
// In multiple calls to Parse with the same receiver template, only one call
// can contain text other than space, comments, and template definitions
- var err error
t1 := New("test")
if _, err := t1.Parse(`{{define "test"}}{{end}}`); err != nil {
t.Fatalf("parsing test: %s", err)
@@ -356,10 +348,4 @@ func TestParse(t *testing.T) {
if _, err := t1.Parse(`{{define "test"}}foo{{end}}`); err != nil {
t.Fatalf("parsing test: %s", err)
}
- if _, err = t1.Parse(`{{define "test"}}foo{{end}}`); err == nil {
- t.Fatal("no error from redefining a template")
- }
- if !strings.Contains(err.Error(), "redefinition") {
- t.Fatalf("expected redefinition error; got %v", err)
- }
}
diff --git a/libgo/go/text/template/parse/lex.go b/libgo/go/text/template/parse/lex.go
index 8f9fe1d..ea93e05 100644
--- a/libgo/go/text/template/parse/lex.go
+++ b/libgo/go/text/template/parse/lex.go
@@ -58,6 +58,7 @@ const (
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
// Keywords appear after all the rest.
itemKeyword // used only to delimit the keywords
+ itemBlock // block keyword
itemDot // the cursor, spelled '.'
itemDefine // define keyword
itemElse // else keyword
@@ -71,6 +72,7 @@ const (
var key = map[string]itemType{
".": itemDot,
+ "block": itemBlock,
"define": itemDefine,
"else": itemElse,
"end": itemEnd,
@@ -83,6 +85,21 @@ var key = map[string]itemType{
const eof = -1
+// Trimming spaces.
+// If the action begins "{{- " rather than "{{", then all space/tab/newlines
+// preceding the action are trimmed; conversely if it ends " -}}" the
+// leading spaces are trimmed. This is done entirely in the lexer; the
+// parser never sees it happen. We require an ASCII space to be
+// present to avoid ambiguity with things like "{{-3}}". It reads
+// better with the space present anyway. For simplicity, only ASCII
+// space does the job.
+const (
+ spaceChars = " \t\r\n" // These are the space characters defined by Go itself.
+ leftTrimMarker = "- " // Attached to left delimiter, trims trailing spaces from preceding text.
+ rightTrimMarker = " -" // Attached to right delimiter, trims leading spaces from following text.
+ trimMarkerLen = Pos(len(leftTrimMarker))
+)
+
// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn
@@ -220,10 +237,18 @@ const (
// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
for {
- if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
+ delim, trimSpace := l.atLeftDelim()
+ if delim {
+ trimLength := Pos(0)
+ if trimSpace {
+ trimLength = rightTrimLength(l.input[l.start:l.pos])
+ }
+ l.pos -= trimLength
if l.pos > l.start {
l.emit(itemText)
}
+ l.pos += trimLength
+ l.ignore()
return lexLeftDelim
}
if l.next() == eof {
@@ -238,13 +263,56 @@ func lexText(l *lexer) stateFn {
return nil
}
-// lexLeftDelim scans the left delimiter, which is known to be present.
+// atLeftDelim reports whether the lexer is at a left delimiter, possibly followed by a trim marker.
+func (l *lexer) atLeftDelim() (delim, trimSpaces bool) {
+ if !strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
+ return false, false
+ }
+ // The left delim might have the marker afterwards.
+ trimSpaces = strings.HasPrefix(l.input[l.pos+Pos(len(l.leftDelim)):], leftTrimMarker)
+ return true, trimSpaces
+}
+
+// rightTrimLength returns the length of the spaces at the end of the string.
+func rightTrimLength(s string) Pos {
+ return Pos(len(s) - len(strings.TrimRight(s, spaceChars)))
+}
+
+// atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker.
+func (l *lexer) atRightDelim() (delim, trimSpaces bool) {
+ if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+ return true, false
+ }
+ // The right delim might have the marker before.
+ if strings.HasPrefix(l.input[l.pos:], rightTrimMarker) {
+ if strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) {
+ return true, true
+ }
+ }
+ return false, false
+}
+
+// leftTrimLength returns the length of the spaces at the beginning of the string.
+func leftTrimLength(s string) Pos {
+ return Pos(len(s) - len(strings.TrimLeft(s, spaceChars)))
+}
+
+// lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker.
func lexLeftDelim(l *lexer) stateFn {
l.pos += Pos(len(l.leftDelim))
- if strings.HasPrefix(l.input[l.pos:], leftComment) {
+ trimSpace := strings.HasPrefix(l.input[l.pos:], leftTrimMarker)
+ afterMarker := Pos(0)
+ if trimSpace {
+ afterMarker = trimMarkerLen
+ }
+ if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) {
+ l.pos += afterMarker
+ l.ignore()
return lexComment
}
l.emit(itemLeftDelim)
+ l.pos += afterMarker
+ l.ignore()
l.parenDepth = 0
return lexInsideAction
}
@@ -257,19 +325,34 @@ func lexComment(l *lexer) stateFn {
return l.errorf("unclosed comment")
}
l.pos += Pos(i + len(rightComment))
- if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+ delim, trimSpace := l.atRightDelim()
+ if !delim {
return l.errorf("comment ends before closing delimiter")
-
+ }
+ if trimSpace {
+ l.pos += trimMarkerLen
}
l.pos += Pos(len(l.rightDelim))
+ if trimSpace {
+ l.pos += leftTrimLength(l.input[l.pos:])
+ }
l.ignore()
return lexText
}
-// lexRightDelim scans the right delimiter, which is known to be present.
+// lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
func lexRightDelim(l *lexer) stateFn {
+ trimSpace := strings.HasPrefix(l.input[l.pos:], rightTrimMarker)
+ if trimSpace {
+ l.pos += trimMarkerLen
+ l.ignore()
+ }
l.pos += Pos(len(l.rightDelim))
l.emit(itemRightDelim)
+ if trimSpace {
+ l.pos += leftTrimLength(l.input[l.pos:])
+ l.ignore()
+ }
return lexText
}
@@ -278,7 +361,8 @@ func lexInsideAction(l *lexer) stateFn {
// Either number, quoted string, or identifier.
// Spaces separate arguments; runs of spaces turn into itemSpace.
// Pipe symbols separate and are emitted.
- if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
+ delim, _ := l.atRightDelim()
+ if delim {
if l.parenDepth == 0 {
return lexRightDelim
}
diff --git a/libgo/go/text/template/parse/lex_test.go b/libgo/go/text/template/parse/lex_test.go
index be551d8..e35ebf1 100644
--- a/libgo/go/text/template/parse/lex_test.go
+++ b/libgo/go/text/template/parse/lex_test.go
@@ -33,6 +33,7 @@ var itemName = map[itemType]string{
// keywords
itemDot: ".",
+ itemBlock: "block",
itemDefine: "define",
itemElse: "else",
itemIf: "if",
@@ -58,6 +59,8 @@ type lexTest struct {
}
var (
+ tDot = item{itemDot, 0, "."}
+ tBlock = item{itemBlock, 0, "block"}
tEOF = item{itemEOF, 0, ""}
tFor = item{itemIdentifier, 0, "for"}
tLeft = item{itemLeftDelim, 0, "{{"}
@@ -104,6 +107,9 @@ var lexTests = []lexTest{
}},
{"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
{"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}},
+ {"block", `{{block "foo" .}}`, []item{
+ tLeft, tBlock, tSpace, {itemString, 0, `"foo"`}, tSpace, tDot, tRight, tEOF,
+ }},
{"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
{"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}},
@@ -155,7 +161,7 @@ var lexTests = []lexTest{
}},
{"dot", "{{.}}", []item{
tLeft,
- {itemDot, 0, "."},
+ tDot,
tRight,
tEOF,
}},
@@ -169,7 +175,7 @@ var lexTests = []lexTest{
tLeft,
{itemField, 0, ".x"},
tSpace,
- {itemDot, 0, "."},
+ tDot,
tSpace,
{itemNumber, 0, ".2"},
tSpace,
@@ -278,6 +284,19 @@ var lexTests = []lexTest{
tRight,
tEOF,
}},
+ {"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{
+ {itemText, 0, "hello-"},
+ tLeft,
+ {itemNumber, 0, "3"},
+ tRight,
+ {itemText, 0, "-world"},
+ tEOF,
+ }},
+ {"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{
+ {itemText, 0, "hello-"},
+ {itemText, 0, "-world"},
+ tEOF,
+ }},
// errors
{"badchar", "#{{\x01}}", []item{
{itemText, 0, "#"},
@@ -339,7 +358,7 @@ var lexTests = []lexTest{
{itemText, 0, "hello-"},
{itemError, 0, `unclosed comment`},
}},
- {"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{
+ {"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{
{itemText, 0, "hello-"},
{itemError, 0, `comment ends before closing delimiter`},
}},
@@ -488,9 +507,9 @@ func TestShutdown(t *testing.T) {
func (t *Tree) parseLexer(lex *lexer, text string) (tree *Tree, err error) {
defer t.recover(&err)
t.ParseName = t.Name
- t.startParse(nil, lex)
- t.parse(nil)
- t.add(nil)
+ t.startParse(nil, lex, map[string]*Tree{})
+ t.parse()
+ t.add()
t.stopParse()
return t, nil
}
diff --git a/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go
index 88aacd1..dc56cf7 100644
--- a/libgo/go/text/template/parse/parse.go
+++ b/libgo/go/text/template/parse/parse.go
@@ -28,6 +28,7 @@ type Tree struct {
token [3]item // three-token lookahead for parser.
peekCount int
vars []string // variables defined at the moment.
+ treeSet map[string]*Tree
}
// Copy returns a copy of the Tree. Any parsing state is discarded.
@@ -205,11 +206,12 @@ func (t *Tree) recover(errp *error) {
}
// startParse initializes the parser, using the lexer.
-func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
+func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) {
t.Root = nil
t.lex = lex
t.vars = []string{"$"}
t.funcs = funcs
+ t.treeSet = treeSet
}
// stopParse terminates parsing.
@@ -217,6 +219,7 @@ func (t *Tree) stopParse() {
t.lex = nil
t.vars = nil
t.funcs = nil
+ t.treeSet = nil
}
// Parse parses the template definition string to construct a representation of
@@ -226,19 +229,19 @@ func (t *Tree) stopParse() {
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
defer t.recover(&err)
t.ParseName = t.Name
- t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
+ t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim), treeSet)
t.text = text
- t.parse(treeSet)
- t.add(treeSet)
+ t.parse()
+ t.add()
t.stopParse()
return t, nil
}
-// add adds tree to the treeSet.
-func (t *Tree) add(treeSet map[string]*Tree) {
- tree := treeSet[t.Name]
+// add adds tree to t.treeSet.
+func (t *Tree) add() {
+ tree := t.treeSet[t.Name]
if tree == nil || IsEmptyTree(tree.Root) {
- treeSet[t.Name] = t
+ t.treeSet[t.Name] = t
return
}
if !IsEmptyTree(t.Root) {
@@ -274,7 +277,7 @@ func IsEmptyTree(n Node) bool {
// parse is the top-level parser for a template, essentially the same
// as itemList except it also parses {{define}} actions.
// It runs to EOF.
-func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
+func (t *Tree) parse() (next Node) {
t.Root = t.newList(t.peek().pos)
for t.peek().typ != itemEOF {
if t.peek().typ == itemLeftDelim {
@@ -283,8 +286,8 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
newT := New("definition") // name will be updated once we know it.
newT.text = t.text
newT.ParseName = t.ParseName
- newT.startParse(t.funcs, t.lex)
- newT.parseDefinition(treeSet)
+ newT.startParse(t.funcs, t.lex, t.treeSet)
+ newT.parseDefinition()
continue
}
t.backup2(delim)
@@ -300,9 +303,9 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
}
// parseDefinition parses a {{define}} ... {{end}} template definition and
-// installs the definition in the treeSet map. The "define" keyword has already
+// installs the definition in t.treeSet. The "define" keyword has already
// been scanned.
-func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
+func (t *Tree) parseDefinition() {
const context = "define clause"
name := t.expectOneOf(itemString, itemRawString, context)
var err error
@@ -316,7 +319,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
if end.Type() != nodeEnd {
t.errorf("unexpected %s in %s", end, context)
}
- t.add(treeSet)
+ t.add()
t.stopParse()
}
@@ -358,6 +361,8 @@ func (t *Tree) textOrAction() Node {
// First word could be a keyword such as range.
func (t *Tree) action() (n Node) {
switch token := t.nextNonSpace(); token.typ {
+ case itemBlock:
+ return t.blockControl()
case itemElse:
return t.elseControl()
case itemEnd:
@@ -522,13 +527,51 @@ func (t *Tree) elseControl() Node {
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
}
+// Block:
+// {{block stringValue pipeline}}
+// Block keyword is past.
+// The name must be something that can evaluate to a string.
+// The pipeline is mandatory.
+func (t *Tree) blockControl() Node {
+ const context = "block clause"
+
+ token := t.nextNonSpace()
+ name := t.parseTemplateName(token, context)
+ pipe := t.pipeline(context)
+
+ block := New(name) // name will be updated once we know it.
+ block.text = t.text
+ block.ParseName = t.ParseName
+ block.startParse(t.funcs, t.lex, t.treeSet)
+ var end Node
+ block.Root, end = block.itemList()
+ if end.Type() != nodeEnd {
+ t.errorf("unexpected %s in %s", end, context)
+ }
+ block.add()
+ block.stopParse()
+
+ return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
+}
+
// Template:
// {{template stringValue pipeline}}
// Template keyword is past. The name must be something that can evaluate
// to a string.
func (t *Tree) templateControl() Node {
- var name string
+ const context = "template clause"
token := t.nextNonSpace()
+ name := t.parseTemplateName(token, context)
+ var pipe *PipeNode
+ if t.nextNonSpace().typ != itemRightDelim {
+ t.backup()
+ // Do not pop variables; they persist until "end".
+ pipe = t.pipeline(context)
+ }
+ return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
+}
+
+func (t *Tree) parseTemplateName(token item, context string) (name string) {
switch token.typ {
case itemString, itemRawString:
s, err := strconv.Unquote(token.val)
@@ -537,15 +580,9 @@ func (t *Tree) templateControl() Node {
}
name = s
default:
- t.unexpected(token, "template invocation")
- }
- var pipe *PipeNode
- if t.nextNonSpace().typ != itemRightDelim {
- t.backup()
- // Do not pop variables; they persist until "end".
- pipe = t.pipeline("template")
+ t.unexpected(token, context)
}
- return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
+ return
}
// command:
diff --git a/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go
index 200d50c..b4512d3 100644
--- a/libgo/go/text/template/parse/parse_test.go
+++ b/libgo/go/text/template/parse/parse_test.go
@@ -228,6 +228,15 @@ var parseTests = []parseTest{
`{{with .X}}"hello"{{end}}`},
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
`{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
+ // Trimming spaces.
+ {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
+ {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
+ {"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`},
+ {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
+ {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
+ {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
+ {"block definition", `{{block "foo" .}}hello{{end}}`, noError,
+ `{{template "foo" .}}`},
// Errors.
{"unclosed action", "hello{{range", hasError, ""},
{"unmatched end", "{{end}}", hasError, ""},
@@ -277,6 +286,8 @@ var parseTests = []parseTest{
{"wrong pipeline boolean", "{{.|true}}", hasError, ""},
{"wrong pipeline nil", "{{'c'|nil}}", hasError, ""},
{"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
+ // Missing pipeline in block
+ {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
}
var builtins = map[string]interface{}{
@@ -450,3 +461,26 @@ func TestErrors(t *testing.T) {
}
}
}
+
+func TestBlock(t *testing.T) {
+ const (
+ input = `a{{block "inner" .}}bar{{.}}baz{{end}}b`
+ outer = `a{{template "inner" .}}b`
+ inner = `bar{{.}}baz`
+ )
+ treeSet := make(map[string]*Tree)
+ tmpl, err := New("outer").Parse(input, "", "", treeSet, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if g, w := tmpl.Root.String(), outer; g != w {
+ t.Errorf("outer template = %q, want %q", g, w)
+ }
+ inTmpl := treeSet["inner"]
+ if inTmpl == nil {
+ t.Fatal("block did not define template")
+ }
+ if g, w := inTmpl.Root.String(), inner; g != w {
+ t.Errorf("inner template = %q, want %q", g, w)
+ }
+}
diff --git a/libgo/go/text/template/template.go b/libgo/go/text/template/template.go
index 3e80982..7a7f42a 100644
--- a/libgo/go/text/template/template.go
+++ b/libgo/go/text/template/template.go
@@ -5,7 +5,6 @@
package template
import (
- "fmt"
"reflect"
"sync"
"text/template/parse"
@@ -117,11 +116,10 @@ func (t *Template) copy(c *common) *Template {
// 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.
-// It is an error to reuse a name except to overwrite an empty template.
+// If the template does exist, it will be replaced.
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.
- // The associate method checks it's not a redefinition.
nt := t
if name != t.name {
nt = t.New(name)
@@ -162,8 +160,9 @@ func (t *Template) Delims(left, right string) *Template {
// Funcs adds the elements of the argument map to the template's function map.
// 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 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.
func (t *Template) Funcs(funcMap FuncMap) *Template {
t.init()
t.muFuncs.Lock()
@@ -184,11 +183,7 @@ func (t *Template) Lookup(name string) *Template {
// Parse defines the template by parsing the text. Nested template definitions will be
// associated with the top-level template t. Parse may be called multiple times
-// to parse definitions of templates to associate with t. It is an error if a
-// resulting template is non-empty (contains content other than template
-// definitions) and would replace a non-empty template with the same name.
-// (In multiple calls to Parse with the same receiver template, only one call
-// can contain text other than space, comments, and template definitions.)
+// to parse definitions of templates to associate with t.
func (t *Template) Parse(text string) (*Template, error) {
t.init()
t.muFuncs.RLock()
@@ -207,25 +202,17 @@ func (t *Template) Parse(text string) (*Template, error) {
}
// associate installs the new template into the group of templates associated
-// with t. It is an error to reuse a name except to overwrite an empty
-// template. The two are already known to share the common structure.
-// The boolean return value reports wither to store this tree as t.Tree.
+// with t. The two are already known to share the common structure.
+// The boolean return value reports whether to store this tree as t.Tree.
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
if new.common != t.common {
panic("internal error: associate not common")
}
- name := new.name
- if old := t.tmpl[name]; old != nil {
- oldIsEmpty := parse.IsEmptyTree(old.Root)
- newIsEmpty := parse.IsEmptyTree(tree.Root)
- if newIsEmpty {
- // Whether old is empty or not, new is empty; no reason to replace old.
- return false, nil
- }
- if !oldIsEmpty {
- return false, fmt.Errorf("template: redefinition of template %q", name)
- }
+ if t.tmpl[new.name] != nil && parse.IsEmptyTree(tree.Root) {
+ // If a template by that name exists,
+ // don't replace it with an empty template.
+ return false, nil
}
- t.tmpl[name] = new
+ t.tmpl[new.name] = new
return true, nil
}