exp/template: better template invocation

1) Make the value optional ({{template "foo"}})
2) Allow the template identifier to be a thing of type *Template.
The second makes it easy to drop templates in to a set dynamically
during invocation.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4671056
This commit is contained in:
Rob Pike 2011-07-10 07:32:01 +10:00
parent c17347eea9
commit 3987b91213
5 changed files with 50 additions and 15 deletions

View File

@ -220,13 +220,27 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) {
}
func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
name := s.evalArg(data, reflect.TypeOf("string"), t.name).String()
if s.set == nil {
s.errorf("no set defined in which to invoke template named %q", name)
// Can't use evalArg because there are two types we expect.
arg := s.evalEmptyInterface(data, t.name)
if !arg.IsValid() {
s.errorf("invalid value in template invocation; expected string or *Template")
}
tmpl := s.set.tmpl[name]
if tmpl == nil {
s.errorf("template %q not in set", name)
var tmpl *Template
if arg.Type() == reflect.TypeOf((*Template)(nil)) {
tmpl = arg.Interface().(*Template)
if tmpl == nil {
s.errorf("nil template")
}
} else {
s.validateType(arg, reflect.TypeOf(""))
name := arg.String()
if s.set == nil {
s.errorf("no set defined in which to invoke template named %q", name)
}
tmpl = s.set.tmpl[name]
if tmpl == nil {
s.errorf("template %q not in set", name)
}
}
defer s.pop(s.mark())
data = s.evalPipeline(data, t.pipe)
@ -245,8 +259,10 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
// pipeline has a variable declaration, the variable will be pushed on the
// stack. Callers should therefore pop the stack after they are finished
// executing commands depending on the pipeline value.
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value {
value := zero
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) (value reflect.Value) {
if pipe == nil {
return
}
for _, cmd := range pipe.cmds {
value = s.evalCommand(data, cmd, value) // previous value is this one's final arg.
// If the object has type interface{}, dig down one level to the thing inside.
@ -434,8 +450,11 @@ func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args
return result[0]
}
// validateType guarantees that the value is assignable to the type.
// validateType guarantees that the value is valid and assignable to the type.
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
if !value.IsValid() {
s.errorf("invalid value; expected %s", typ)
}
if !value.Type().AssignableTo(typ) {
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
}
@ -462,7 +481,7 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
return s.evalInteger(typ, n)
case reflect.Interface:
if typ.NumMethod() == 0 {
return s.evalEmptyInterface(data, typ, n)
return s.evalEmptyInterface(data, n)
}
case reflect.String:
return s.evalString(typ, n)
@ -533,7 +552,7 @@ func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
panic("not reached")
}
func (s *state) evalEmptyInterface(data reflect.Value, typ reflect.Type, n node) reflect.Value {
func (s *state) evalEmptyInterface(data reflect.Value, n node) reflect.Value {
switch n := n.(type) {
case *boolNode:
return reflect.ValueOf(n.true)

View File

@ -42,6 +42,8 @@ type T struct {
PI *int
PSI *[]int
NIL *int
// Template to test evaluation of templates.
Tmpl *Template
}
type U struct {
@ -67,6 +69,7 @@ var tVal = &T{
Empty4: &U{"v"},
PI: newInt(23),
PSI: newIntSlice(21, 22, 23),
Tmpl: New("x").MustParse("test template"), // "x" is the value of .X
}
// Helpers for creation.

View File

@ -469,6 +469,9 @@ func newTemplate(line int, name node, pipe *pipeNode) *templateNode {
}
func (t *templateNode) String() string {
if t.pipe == nil {
return fmt.Sprintf("{{template %s}}", t.name)
}
return fmt.Sprintf("{{template %s %s}}", t.name, t.pipe)
}
@ -748,7 +751,6 @@ func (t *Template) withControl() node {
return newWith(t.parseControl("with"))
}
// End:
// {{end}}
// End keyword is past.
@ -790,7 +792,12 @@ func (t *Template) templateControl() node {
default:
t.unexpected(token, "template invocation")
}
return newTemplate(t.lex.lineNumber(), name, t.pipeline("template"))
var pipe *pipeNode
if t.next().typ != itemRightDelim {
t.backup()
pipe = t.pipeline("template")
}
return newTemplate(t.lex.lineNumber(), name, pipe)
}
// command:

View File

@ -174,6 +174,8 @@ var parseTests = []parseTest{
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
{"template", "{{template `x`}}", noError,
"[{{template S=`x`}}]"},
{"template", "{{template `x` .Y}}", noError,
"[{{template S=`x` [(command: [F=[Y]])]}}]"},
{"with", "{{with .X}}hello{{end}}", noError,

View File

@ -76,11 +76,15 @@ func TestSetParse(t *testing.T) {
var setExecTests = []execTest{
{"empty", "", "", nil, true},
{"text", "some text", "some text", nil, true},
{"invoke text", `{{template "text" .SI}}`, "TEXT", tVal, true},
{"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
{"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true},
{"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
{"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
{"invoke template by field", `{{template .X}}`, "TEXT", tVal, true},
{"invoke template by template", `{{template .Tmpl}}`, "test template", tVal, true},
{"invalid: invoke template by []int", `{{template .SI}}`, "", tVal, false},
// User-defined function: test argument evaluator.
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
@ -88,7 +92,7 @@ var setExecTests = []execTest{
}
const setText = `
{{define "text"}}TEXT{{end}}
{{define "x"}}TEXT{{end}}
{{define "dotV"}}{{.V}}{{end}}
{{define "dot"}}{{.}}{{end}}
{{define "nested"}}{{template "dot" .}}{{end}}