]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: better template invocation
authorRob Pike <r@golang.org>
Sat, 9 Jul 2011 21:32:01 +0000 (07:32 +1000)
committerRob Pike <r@golang.org>
Sat, 9 Jul 2011 21:32:01 +0000 (07:32 +1000)
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
src/pkg/exp/template/exec_test.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/parse_test.go
src/pkg/exp/template/set_test.go

index b5d4a1d8e72368931c95d2aab50a2b74bc12459f..21e8e812ea48852de1692b8a1a1ecdffa5c1fdf5 100644 (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)
-       }
-       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)
index 8e50f17dcab8fc8d7179a8fad19931947a08fd8c..831113c4081fdc57d1367bd1b0cc4b0bbd74fada 100644 (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.
index 00d987546099685aaf0072ce904086e88f58c8a2..effd8245045b107e16dbb9e2dc4c056ea30f011b 100644 (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:
index 70267954e2f85925d731cf438f82e9c4bf88a2a1..1d358209abcbdf53fa6a7b2e6fcd34f08bb0cdd7 100644 (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,
index 628839a724b4273f07babd3acf8cec2a6e920fb4..99310b62349e47eaa453ad99ea74104fa04f30ae 100644 (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}}