]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: A template can be in one set only.
authorRob Pike <r@golang.org>
Thu, 21 Jul 2011 04:22:01 +0000 (14:22 +1000)
committerRob Pike <r@golang.org>
Thu, 21 Jul 2011 04:22:01 +0000 (14:22 +1000)
This simplifies the API and makes it easier to make the template
invocation statically secure, at the cost of some minor flexibility.

R=golang-dev, dsymonds, r
CC=golang-dev
https://golang.org/cl/4794045

src/pkg/exp/template/doc.go
src/pkg/exp/template/exec.go
src/pkg/exp/template/exec_test.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/set.go

index 41e6002786974cda822f4e66fb5211f1a68dbe3c..0a458e14c75913ed2e7d2c6cec15148f034f959b 100644 (file)
@@ -252,11 +252,12 @@ Template sets
 Each template is named by a string specified when it is created.  A template may
 use a template invocation to instantiate another template directly or by its
 name; see the explanation of the template action above. The name is looked up
-in the template set active during the invocation.
+in the template set associated with the template.
 
 If no template invocation actions occur in the template, the issue of template
-sets can be ignored. If it does contain invocations, though, a set must be
-defined in which to look up the names.
+sets can be ignored.  If it does contain invocations, though, the template
+containing the invocations must be part of a template set in which to look up
+the names.
 
 There are two ways to construct template sets.
 
@@ -274,22 +275,19 @@ constant. Here is a simple example of input to Set.Parse:
 This defines two templates, T1 and T2, and a third T3 that invokes the other two
 when it is executed.
 
-The second way to build a template set is to use Set's Add method to add
-a template to a set. A template may be bound to multiple sets.
+The second way to build a template set is to use Set's Add method to add a
+parsed template to a set.  A template may be bound at most one set.  If it's
+necessary to have a template in multiple sets, the template definition must be
+parsed multiple times to create distinct *Template values.
 
 Set.Parse may be called multiple times on different inputs to construct the set.
 Two sets may therefore be constructed with a common base set of templates plus,
 through a second Parse call each, specializations for some elements.
 
-When a template is executed via Template.Execute, no set is defined and so no
-template invocations are possible. The method Template.ExecuteInSet provides a
-way to specify a template set when executing a template directly.
+A template may be executed directly or through Set.Execute, which executes a
+named template from the set.  To invoke our example above, we might write,
 
-A more direct technique is to use Set.Execute, which executes a named template
-from the set and provides the context for looking up templates in template
-invocations. To invoke our example above, we might write,
-
-       err := set.Execute("T3", os.Stdout, "no data needed")
+       err := set.Execute(os.Stdout, "T3", "no data needed")
        if err != nil {
                log.Fatalf("execution failed: %s", err)
        }
index 4ec738f0dfdcbb503849b14e7947ca93bbfae36e..d5a86d87224bb9d10106d657701aedeaff6cfdde 100644 (file)
@@ -20,7 +20,6 @@ import (
 type state struct {
        tmpl *Template
        wr   io.Writer
-       set  *Set
        line int        // line number for errors
        vars []variable // push-down stack of variable values.
 }
@@ -77,20 +76,12 @@ func (s *state) error(err os.Error) {
 
 // Execute applies a parsed template to the specified data object,
 // writing the output to wr.
-func (t *Template) Execute(wr io.Writer, data interface{}) os.Error {
-       return t.ExecuteInSet(wr, data, nil)
-}
-
-// ExecuteInSet applies a parsed template to the specified data object,
-// writing the output to wr. Nested template invocations will be resolved
-// from the specified set.
-func (t *Template) ExecuteInSet(wr io.Writer, data interface{}, set *Set) (err os.Error) {
+func (t *Template) Execute(wr io.Writer, data interface{}) (err os.Error) {
        defer t.recover(&err)
        value := reflect.ValueOf(data)
        state := &state{
                tmpl: t,
                wr:   wr,
-               set:  set,
                line: 1,
                vars: []variable{{"$", value}},
        }
@@ -225,10 +216,11 @@ func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
 }
 
 func (s *state) walkTemplate(dot reflect.Value, t *templateNode) {
-       if s.set == nil {
+       set := s.tmpl.set
+       if set == nil {
                s.errorf("no set defined in which to invoke template named %q", t.name)
        }
-       tmpl := s.set.tmpl[t.name]
+       tmpl := set.tmpl[t.name]
        if tmpl == nil {
                s.errorf("template %q not in set", t.name)
        }
@@ -349,7 +341,7 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args
 }
 
 func (s *state) evalFunction(dot reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
-       function, ok := findFunction(name, s.tmpl, s.set)
+       function, ok := findFunction(name, s.tmpl, s.tmpl.set)
        if !ok {
                s.errorf("%q is not a defined function", name)
        }
index eb5ab71187a8b63a75f9f355846d5afbfb8f0245..36eaabe5f07c5c3a675173d75ecb4c27667a9984 100644 (file)
@@ -358,13 +358,13 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) {
        funcs := FuncMap{"zeroArgs": zeroArgs, "oneArg": oneArg, "typeOf": typeOf}
        for _, test := range execTests {
                tmpl := New(test.name).Funcs(funcs)
-               err := tmpl.Parse(test.input)
+               err := tmpl.ParseInSet(test.input, set)
                if err != nil {
                        t.Errorf("%s: parse error: %s", test.name, err)
                        continue
                }
                b.Reset()
-               err = tmpl.ExecuteInSet(b, test.data, set)
+               err = tmpl.Execute(b, test.data)
                switch {
                case !test.ok && err == nil:
                        t.Errorf("%s: expected error; got none", test.name)
index 9208d0d04d66f3ee1b4c21b7e01457465d300373..aa75eb8d94225e6b3b5740c41d31c89eb4373535 100644 (file)
@@ -20,12 +20,13 @@ type Template struct {
        name  string
        root  *listNode
        funcs map[string]reflect.Value
+       set   *Set // can be nil.
        // Parsing only; cleared after parse.
-       set       *Set
+       parseSet  *Set // for function lookup during parse.
        lex       *lexer
-       token     [2]item // two-token lookahead for parser
+       token     [2]item // two-token lookahead for parser.
        peekCount int
-       vars      []string // variables defined at the moment
+       vars      []string // variables defined at the moment.
 }
 
 // Name returns the name of the template.
@@ -574,15 +575,16 @@ func (t *Template) recover(errp *os.Error) {
 // startParse starts the template parsing from the lexer.
 func (t *Template) startParse(set *Set, lex *lexer) {
        t.root = nil
-       t.set = set
        t.lex = lex
        t.vars = []string{"$"}
+       t.parseSet = set
 }
 
 // stopParse terminates parsing.
 func (t *Template) stopParse() {
-       t.set, t.lex = nil, nil
+       t.lex = nil
        t.vars = nil
+       t.parseSet = nil
 }
 
 // atEOF returns true if, possibly after spaces, we're at EOF.
@@ -609,25 +611,33 @@ func (t *Template) atEOF() bool {
 // Parse parses the template definition string to construct an internal
 // representation of the template for execution.
 func (t *Template) Parse(s string) (err os.Error) {
-       t.startParse(nil, lex(t.name, s))
        defer t.recover(&err)
+       t.startParse(t.set, lex(t.name, s))
        t.parse(true)
        t.stopParse()
        return
 }
 
 // ParseInSet parses the template definition string to construct an internal
-// representation of the template for execution.
+// representation of the template for execution. It also adds the template
+// to the set.
 // Function bindings are checked against those in the set.
 func (t *Template) ParseInSet(s string, set *Set) (err os.Error) {
-       t.startParse(set, lex(t.name, s))
        defer t.recover(&err)
+       t.startParse(set, lex(t.name, s))
        t.parse(true)
-       if len(t.vars) != 1 { // $ should still be defined
-               t.errorf("internal error: vars not popped")
-       }
        t.stopParse()
-       return
+       t.addToSet(set)
+       return nil
+}
+
+// addToSet adds the template to the set, verifying it's not being double-assigned.
+func (t *Template) addToSet(set *Set) {
+       if set == nil || t.set == set {
+               return
+       }
+       // If double-assigned, Add will panic and we will turn that into an error.
+       set.Add(t)
 }
 
 // parse is the helper for Parse.
@@ -846,7 +856,7 @@ Loop:
                case itemError:
                        t.errorf("%s", token.val)
                case itemIdentifier:
-                       if _, ok := findFunction(token.val, t, t.set); !ok {
+                       if _, ok := findFunction(token.val, t, t.parseSet); !ok {
                                t.errorf("function %q not defined", token.val)
                        }
                        cmd.append(newIdentifier(token.val))
index 0c12bfff49cb12d0e92ed88dfc10657ace2137fc..ddf024eaf4f1b760a61a01d170c245c384d9ab03 100644 (file)
@@ -39,41 +39,20 @@ func (s *Set) Funcs(funcMap FuncMap) *Set {
 }
 
 // Add adds the argument templates to the set. It panics if two templates
-// with the same name are added.
+// with the same name are added or if a template is already a member of
+// a set.
 // The return value is the set, so calls can be chained.
 func (s *Set) Add(templates ...*Template) *Set {
        s.init()
        for _, t := range templates {
-               if _, ok := s.tmpl[t.name]; ok {
-                       panic(fmt.Errorf("template: %q already defined in set", t.name))
+               if t.set != nil {
+                       panic(fmt.Errorf("template: %q already in a set", t.name))
                }
-               s.tmpl[t.name] = t
-       }
-       return s
-}
-
-// AddSet adds the templates from the provided set to the to the receiver.
-// It panics if the call attempts to reuse a name defined in the set.
-// The return value is the set, so calls can be chained.
-func (s *Set) AddSet(set *Set) *Set {
-       s.init()
-       for _, t := range set.tmpl {
                if _, ok := s.tmpl[t.name]; ok {
                        panic(fmt.Errorf("template: %q already defined in set", t.name))
                }
                s.tmpl[t.name] = t
-       }
-       return s
-}
-
-// Union adds the templates from the provided set to the to the receiver.
-// Unlike AddSet, it does not panic if a name is reused; instead the old
-// template is replaced.
-// The return value is the set, so calls can be chained.
-func (s *Set) Union(set *Set) *Set {
-       s.init()
-       for _, t := range set.tmpl {
-               s.tmpl[t.name] = t
+               t.set = s
        }
        return s
 }
@@ -85,13 +64,13 @@ func (s *Set) Template(name string) *Template {
 }
 
 // Execute applies the named template to the specified data object, writing
-// the output to wr.  Nested template invocations will be resolved from the set.
+// the output to wr.
 func (s *Set) Execute(wr io.Writer, name string, data interface{}) os.Error {
        tmpl := s.tmpl[name]
        if tmpl == nil {
                return fmt.Errorf("template: no template %q in set", name)
        }
-       return tmpl.ExecuteInSet(wr, data, s)
+       return tmpl.Execute(wr, data)
 }
 
 // recover is the handler that turns panics into returns from the top
@@ -140,6 +119,7 @@ func (s *Set) Parse(text string) (err os.Error) {
                        t.errorf("unexpected %s in %s", end, context)
                }
                t.stopParse()
+               t.addToSet(s)
                s.tmpl[t.name] = t
        }
        return nil