Commit 0f7a1951 authored by Rob Pike's avatar Rob Pike

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
parent 5f134f9b
......@@ -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)
}
......
......@@ -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)
}
......
......@@ -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)
......
......@@ -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))
......
......@@ -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
......
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