Commit 3987b912 authored by Rob Pike's avatar Rob Pike

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
parent c17347ee
...@@ -220,13 +220,27 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) { ...@@ -220,13 +220,27 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) {
} }
func (s *state) walkTemplate(data reflect.Value, t *templateNode) { func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
name := s.evalArg(data, reflect.TypeOf("string"), t.name).String() // Can't use evalArg because there are two types we expect.
if s.set == nil { arg := s.evalEmptyInterface(data, t.name)
s.errorf("no set defined in which to invoke template named %q", name) if !arg.IsValid() {
} s.errorf("invalid value in template invocation; expected string or *Template")
tmpl := s.set.tmpl[name] }
if tmpl == nil { var tmpl *Template
s.errorf("template %q not in set", name) 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()) defer s.pop(s.mark())
data = s.evalPipeline(data, t.pipe) data = s.evalPipeline(data, t.pipe)
...@@ -245,8 +259,10 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) { ...@@ -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 // pipeline has a variable declaration, the variable will be pushed on the
// stack. Callers should therefore pop the stack after they are finished // stack. Callers should therefore pop the stack after they are finished
// executing commands depending on the pipeline value. // executing commands depending on the pipeline value.
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value { func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) (value reflect.Value) {
value := zero if pipe == nil {
return
}
for _, cmd := range pipe.cmds { for _, cmd := range pipe.cmds {
value = s.evalCommand(data, cmd, value) // previous value is this one's final arg. 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. // 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 ...@@ -434,8 +450,11 @@ func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args
return result[0] 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 { 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) { if !value.Type().AssignableTo(typ) {
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type()) 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 ...@@ -462,7 +481,7 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
return s.evalInteger(typ, n) return s.evalInteger(typ, n)
case reflect.Interface: case reflect.Interface:
if typ.NumMethod() == 0 { if typ.NumMethod() == 0 {
return s.evalEmptyInterface(data, typ, n) return s.evalEmptyInterface(data, n)
} }
case reflect.String: case reflect.String:
return s.evalString(typ, n) return s.evalString(typ, n)
...@@ -533,7 +552,7 @@ func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value { ...@@ -533,7 +552,7 @@ func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
panic("not reached") 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) { switch n := n.(type) {
case *boolNode: case *boolNode:
return reflect.ValueOf(n.true) return reflect.ValueOf(n.true)
......
...@@ -42,6 +42,8 @@ type T struct { ...@@ -42,6 +42,8 @@ type T struct {
PI *int PI *int
PSI *[]int PSI *[]int
NIL *int NIL *int
// Template to test evaluation of templates.
Tmpl *Template
} }
type U struct { type U struct {
...@@ -67,6 +69,7 @@ var tVal = &T{ ...@@ -67,6 +69,7 @@ var tVal = &T{
Empty4: &U{"v"}, Empty4: &U{"v"},
PI: newInt(23), PI: newInt(23),
PSI: newIntSlice(21, 22, 23), PSI: newIntSlice(21, 22, 23),
Tmpl: New("x").MustParse("test template"), // "x" is the value of .X
} }
// Helpers for creation. // Helpers for creation.
......
...@@ -469,6 +469,9 @@ func newTemplate(line int, name node, pipe *pipeNode) *templateNode { ...@@ -469,6 +469,9 @@ func newTemplate(line int, name node, pipe *pipeNode) *templateNode {
} }
func (t *templateNode) String() string { 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) return fmt.Sprintf("{{template %s %s}}", t.name, t.pipe)
} }
...@@ -748,7 +751,6 @@ func (t *Template) withControl() node { ...@@ -748,7 +751,6 @@ func (t *Template) withControl() node {
return newWith(t.parseControl("with")) return newWith(t.parseControl("with"))
} }
// End: // End:
// {{end}} // {{end}}
// End keyword is past. // End keyword is past.
...@@ -790,7 +792,12 @@ func (t *Template) templateControl() node { ...@@ -790,7 +792,12 @@ func (t *Template) templateControl() node {
default: default:
t.unexpected(token, "template invocation") 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: // command:
......
...@@ -174,6 +174,8 @@ var parseTests = []parseTest{ ...@@ -174,6 +174,8 @@ var parseTests = []parseTest{
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`}, `[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError, {"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`}, `[({{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", "{{template `x` .Y}}", noError,
"[{{template S=`x` [(command: [F=[Y]])]}}]"}, "[{{template S=`x` [(command: [F=[Y]])]}}]"},
{"with", "{{with .X}}hello{{end}}", noError, {"with", "{{with .X}}hello{{end}}", noError,
......
...@@ -76,11 +76,15 @@ func TestSetParse(t *testing.T) { ...@@ -76,11 +76,15 @@ func TestSetParse(t *testing.T) {
var setExecTests = []execTest{ var setExecTests = []execTest{
{"empty", "", "", nil, true}, {"empty", "", "", nil, true},
{"text", "some text", "some text", 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" .I}}`, "17", tVal, true},
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true}, {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true}, {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
{"invoke nested int", `{{template "nested" .I}}`, "17", 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. // User-defined function: test argument evaluator.
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true}, {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
...@@ -88,7 +92,7 @@ var setExecTests = []execTest{ ...@@ -88,7 +92,7 @@ var setExecTests = []execTest{
} }
const setText = ` const setText = `
{{define "text"}}TEXT{{end}} {{define "x"}}TEXT{{end}}
{{define "dotV"}}{{.V}}{{end}} {{define "dotV"}}{{.V}}{{end}}
{{define "dot"}}{{.}}{{end}} {{define "dot"}}{{.}}{{end}}
{{define "nested"}}{{template "dot" .}}{{end}} {{define "nested"}}{{template "dot" .}}{{end}}
......
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