From 0f7a1951b82b80dcf1b8398e8ead03ed158e029e Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Thu, 21 Jul 2011 14:22:01 +1000 Subject: [PATCH] exp/template: A template can be in one set only. 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 | 24 +++++++++---------- src/pkg/exp/template/exec.go | 18 ++++----------- src/pkg/exp/template/exec_test.go | 4 ++-- src/pkg/exp/template/parse.go | 36 ++++++++++++++++++----------- src/pkg/exp/template/set.go | 38 ++++++++----------------------- 5 files changed, 50 insertions(+), 70 deletions(-) diff --git a/src/pkg/exp/template/doc.go b/src/pkg/exp/template/doc.go index 41e60027869..0a458e14c75 100644 --- a/src/pkg/exp/template/doc.go +++ b/src/pkg/exp/template/doc.go @@ -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) } diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index 4ec738f0dfd..d5a86d87224 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -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) } diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index eb5ab71187a..36eaabe5f07 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -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) diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go index 9208d0d04d6..aa75eb8d942 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -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)) diff --git a/src/pkg/exp/template/set.go b/src/pkg/exp/template/set.go index 0c12bfff49c..ddf024eaf4f 100644 --- a/src/pkg/exp/template/set.go +++ b/src/pkg/exp/template/set.go @@ -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 t.set != nil { + panic(fmt.Errorf("template: %q already in a set", t.name)) + } 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 -} - -// 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