From 3987b91213e6e0d2e0b87fe8c79d2b0fe4923909 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Sun, 10 Jul 2011 07:32:01 +1000 Subject: [PATCH] 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 --- src/pkg/exp/template/exec.go | 43 +++++++++++++++++++++--------- src/pkg/exp/template/exec_test.go | 3 +++ src/pkg/exp/template/parse.go | 11 ++++++-- src/pkg/exp/template/parse_test.go | 2 ++ src/pkg/exp/template/set_test.go | 8 ++++-- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index b5d4a1d8e7..21e8e812ea 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -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) - } - tmpl := s.set.tmpl[name] - if tmpl == nil { - s.errorf("template %q not in set", 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") + } + 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) diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index 8e50f17dca..831113c408 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -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. diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go index 00d9875460..effd824504 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -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: diff --git a/src/pkg/exp/template/parse_test.go b/src/pkg/exp/template/parse_test.go index 70267954e2..1d358209ab 100644 --- a/src/pkg/exp/template/parse_test.go +++ b/src/pkg/exp/template/parse_test.go @@ -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, diff --git a/src/pkg/exp/template/set_test.go b/src/pkg/exp/template/set_test.go index 628839a724..99310b6234 100644 --- a/src/pkg/exp/template/set_test.go +++ b/src/pkg/exp/template/set_test.go @@ -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}} -- 2.48.1