Commit 4e5ac45e authored by Rob Pike's avatar Rob Pike

text/template: provide a mechanism for options

Add one option, which is the motivating example, a way to control
what happens when a map is indexed with a key that is not in the map.
Rather than do something specific for that case, we provide a simple
general option mechanism to avoid adding API if something else
comes up. This general approach also makes it easy for html/template
to track (and adapt, should that become important).

New method: Option(option string...). The option strings are key=value
pairs or just simple strings (no =).

New option:

 missingkey: Control the behavior during execution if a map is
 indexed with a key that is not present in the map.
	"missingkey=default" or "missingkey=invalid"
		The default behavior: Do nothing and continue execution.
		If printed, the result of the index operation is the string
		"<no value>".
	"missingkey=zero"
		The operation returns the zero value for the map type's element.
	"missingkey=error"
		Execution stops immediately with an error.

Fixes #6288.

Change-Id: Id811e2b99dc05aff324d517faac113ef3c25293a
Reviewed-on: https://go-review.googlesource.com/8462Reviewed-by: 's avatarRobert Griesemer <gri@golang.org>
parent 4f2a7301
......@@ -51,6 +51,29 @@ func (t *Template) Templates() []*Template {
return m
}
// Option sets options for the template. Options are described by
// strings, either a simple string or "key=value". There can be at
// most one equals sign in an option string. If the option string
// is unrecognized or otherwise invalid, Option panics.
//
// Known options:
//
// missingkey: Control the behavior during execution if a map is
// indexed with a key that is not present in the map.
// "missingkey=default" or "missingkey=invalid"
// The default behavior: Do nothing and continue execution.
// If printed, the result of the index operation is the string
// "<no value>".
// "missingkey=zero"
// The operation returns the zero value for the map type's element.
// "missingkey=error"
// Execution stops immediately with an error.
//
func (t *Template) Option(opt ...string) *Template {
t.text.Option(opt...)
return t
}
// escape escapes all associated templates.
func (t *Template) escape() error {
t.nameSpace.mu.Lock()
......
......@@ -519,7 +519,18 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
if hasArgs {
s.errorf("%s is not a method but has arguments", fieldName)
}
return receiver.MapIndex(nameVal)
result := receiver.MapIndex(nameVal)
if !result.IsValid() {
switch s.tmpl.option.missingKey {
case mapInvalid:
// Just use the invalid value.
case mapZeroValue:
result = reflect.Zero(receiver.Type().Elem())
case mapError:
s.errorf("map has no entry for key %q", fieldName)
}
}
return result
}
}
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
......
......@@ -1044,3 +1044,54 @@ func TestComparison(t *testing.T) {
}
}
}
func TestMissingMapKey(t *testing.T) {
data := map[string]int{
"x": 99,
}
tmpl, err := New("t1").Parse("{{.x}} {{.y}}")
if err != nil {
t.Fatal(err)
}
var b bytes.Buffer
// By default, just get "<no value>"
err = tmpl.Execute(&b, data)
if err != nil {
t.Fatal(err)
}
want := "99 <no value>"
got := b.String()
if got != want {
t.Errorf("got %q; expected %q", got, want)
}
// Same if we set the option explicitly to the default.
tmpl.Option("missingkey=default")
b.Reset()
err = tmpl.Execute(&b, data)
if err != nil {
t.Fatal("default:", err)
}
want = "99 <no value>"
got = b.String()
if got != want {
t.Errorf("got %q; expected %q", got, want)
}
// Next we ask for a zero value
tmpl.Option("missingkey=zero")
b.Reset()
err = tmpl.Execute(&b, data)
if err != nil {
t.Fatal("zero:", err)
}
want = "99 0"
got = b.String()
if got != want {
t.Errorf("got %q; expected %q", got, want)
}
// Now we ask for an error.
tmpl.Option("missingkey=error")
err = tmpl.Execute(&b, data)
if err == nil {
t.Errorf("expected error; got none")
}
}
......@@ -590,7 +590,7 @@ func evalArgs(args []interface{}) string {
a, ok := printableValue(reflect.ValueOf(arg))
if ok {
args[i] = a
} // else left fmt do its thing
} // else let fmt do its thing
}
s = fmt.Sprint(args...)
}
......
// Copyright 2015 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.
// This file contains the code to handle template options.
package template
import "strings"
// missingKeyAction defines how to respond to indexing a map with a key that is not present.
type missingKeyAction int
const (
mapInvalid missingKeyAction = iota // Return an invalid reflect.Value.
mapZeroValue // Return the zero value for the map element.
mapError // Error out
)
type option struct {
missingKey missingKeyAction
}
// Option sets options for the template. Options are described by
// strings, either a simple string or "key=value". There can be at
// most one equals sign in an option string. If the option string
// is unrecognized or otherwise invalid, Option panics.
//
// Known options:
//
// missingkey: Control the behavior during execution if a map is
// indexed with a key that is not present in the map.
// "missingkey=default" or "missingkey=invalid"
// The default behavior: Do nothing and continue execution.
// If printed, the result of the index operation is the string
// "<no value>".
// "missingkey=zero"
// The operation returns the zero value for the map type's element.
// "missingkey=error"
// Execution stops immediately with an error.
//
func (t *Template) Option(opt ...string) *Template {
for _, s := range opt {
t.setOption(s)
}
return t
}
func (t *Template) setOption(opt string) {
if opt == "" {
panic("empty option string")
}
elems := strings.Split(opt, "=")
switch len(elems) {
case 2:
// key=value
switch elems[0] {
case "missingkey":
switch elems[1] {
case "invalid", "default":
t.option.missingKey = mapInvalid
return
case "zero":
t.option.missingKey = mapZeroValue
return
case "error":
t.option.missingKey = mapError
return
}
}
}
panic("unrecognized option: " + opt)
}
......@@ -18,6 +18,7 @@ type common struct {
// expose reflection to the client.
parseFuncs FuncMap
execFuncs map[string]reflect.Value
option option
}
// Template is the representation of a parsed template. The *parse.Tree
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment