mirror of https://github.com/golang/go.git
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/8462 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
4f2a73011f
commit
4e5ac45ec5
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
// 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue