1
0
mirror of https://github.com/golang/go synced 2024-11-25 15:17:58 -07:00

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
This commit is contained in:
Rob Pike 2011-07-21 14:22:01 +10:00
parent 5f134f9b5b
commit 0f7a1951b8
5 changed files with 50 additions and 70 deletions

View File

@ -252,11 +252,12 @@ Template sets
Each template is named by a string specified when it is created. A template may 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 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 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 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 sets can be ignored. If it does contain invocations, though, the template
defined in which to look up the names. 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. 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 This defines two templates, T1 and T2, and a third T3 that invokes the other two
when it is executed. when it is executed.
The second way to build a template set is to use Set's Add method to add The second way to build a template set is to use Set's Add method to add a
a template to a set. A template may be bound to multiple sets. 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. 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, Two sets may therefore be constructed with a common base set of templates plus,
through a second Parse call each, specializations for some elements. 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 A template may be executed directly or through Set.Execute, which executes a
template invocations are possible. The method Template.ExecuteInSet provides a named template from the set. To invoke our example above, we might write,
way to specify a template set when executing a template directly.
A more direct technique is to use Set.Execute, which executes a named template err := set.Execute(os.Stdout, "T3", "no data needed")
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")
if err != nil { if err != nil {
log.Fatalf("execution failed: %s", err) log.Fatalf("execution failed: %s", err)
} }

View File

@ -20,7 +20,6 @@ import (
type state struct { type state struct {
tmpl *Template tmpl *Template
wr io.Writer wr io.Writer
set *Set
line int // line number for errors line int // line number for errors
vars []variable // push-down stack of variable values. 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, // Execute applies a parsed template to the specified data object,
// writing the output to wr. // writing the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) os.Error { func (t *Template) Execute(wr io.Writer, data interface{}) (err 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) {
defer t.recover(&err) defer t.recover(&err)
value := reflect.ValueOf(data) value := reflect.ValueOf(data)
state := &state{ state := &state{
tmpl: t, tmpl: t,
wr: wr, wr: wr,
set: set,
line: 1, line: 1,
vars: []variable{{"$", value}}, 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) { 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) 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 { if tmpl == nil {
s.errorf("template %q not in set", t.name) 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 { 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 { if !ok {
s.errorf("%q is not a defined function", name) s.errorf("%q is not a defined function", name)
} }

View File

@ -358,13 +358,13 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) {
funcs := FuncMap{"zeroArgs": zeroArgs, "oneArg": oneArg, "typeOf": typeOf} funcs := FuncMap{"zeroArgs": zeroArgs, "oneArg": oneArg, "typeOf": typeOf}
for _, test := range execTests { for _, test := range execTests {
tmpl := New(test.name).Funcs(funcs) tmpl := New(test.name).Funcs(funcs)
err := tmpl.Parse(test.input) err := tmpl.ParseInSet(test.input, set)
if err != nil { if err != nil {
t.Errorf("%s: parse error: %s", test.name, err) t.Errorf("%s: parse error: %s", test.name, err)
continue continue
} }
b.Reset() b.Reset()
err = tmpl.ExecuteInSet(b, test.data, set) err = tmpl.Execute(b, test.data)
switch { switch {
case !test.ok && err == nil: case !test.ok && err == nil:
t.Errorf("%s: expected error; got none", test.name) t.Errorf("%s: expected error; got none", test.name)

View File

@ -20,12 +20,13 @@ type Template struct {
name string name string
root *listNode root *listNode
funcs map[string]reflect.Value funcs map[string]reflect.Value
set *Set // can be nil.
// Parsing only; cleared after parse. // Parsing only; cleared after parse.
set *Set parseSet *Set // for function lookup during parse.
lex *lexer lex *lexer
token [2]item // two-token lookahead for parser token [2]item // two-token lookahead for parser.
peekCount int peekCount int
vars []string // variables defined at the moment vars []string // variables defined at the moment.
} }
// Name returns the name of the template. // 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. // startParse starts the template parsing from the lexer.
func (t *Template) startParse(set *Set, lex *lexer) { func (t *Template) startParse(set *Set, lex *lexer) {
t.root = nil t.root = nil
t.set = set
t.lex = lex t.lex = lex
t.vars = []string{"$"} t.vars = []string{"$"}
t.parseSet = set
} }
// stopParse terminates parsing. // stopParse terminates parsing.
func (t *Template) stopParse() { func (t *Template) stopParse() {
t.set, t.lex = nil, nil t.lex = nil
t.vars = nil t.vars = nil
t.parseSet = nil
} }
// atEOF returns true if, possibly after spaces, we're at EOF. // atEOF returns true if, possibly after spaces, we're at EOF.
@ -609,26 +611,34 @@ func (t *Template) atEOF() bool {
// Parse parses the template definition string to construct an internal // Parse parses the template definition string to construct an internal
// representation of the template for execution. // representation of the template for execution.
func (t *Template) Parse(s string) (err os.Error) { func (t *Template) Parse(s string) (err os.Error) {
t.startParse(nil, lex(t.name, s))
defer t.recover(&err) defer t.recover(&err)
t.startParse(t.set, lex(t.name, s))
t.parse(true) t.parse(true)
t.stopParse() t.stopParse()
return return
} }
// ParseInSet parses the template definition string to construct an internal // 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. // Function bindings are checked against those in the set.
func (t *Template) ParseInSet(s string, set *Set) (err os.Error) { func (t *Template) ParseInSet(s string, set *Set) (err os.Error) {
t.startParse(set, lex(t.name, s))
defer t.recover(&err) defer t.recover(&err)
t.startParse(set, lex(t.name, s))
t.parse(true) t.parse(true)
if len(t.vars) != 1 { // $ should still be defined
t.errorf("internal error: vars not popped")
}
t.stopParse() t.stopParse()
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 return
} }
// If double-assigned, Add will panic and we will turn that into an error.
set.Add(t)
}
// parse is the helper for Parse. // parse is the helper for Parse.
// It triggers an error if we expect EOF but don't reach it. // It triggers an error if we expect EOF but don't reach it.
@ -846,7 +856,7 @@ Loop:
case itemError: case itemError:
t.errorf("%s", token.val) t.errorf("%s", token.val)
case itemIdentifier: 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) t.errorf("function %q not defined", token.val)
} }
cmd.append(newIdentifier(token.val)) cmd.append(newIdentifier(token.val))

View 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 // 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. // The return value is the set, so calls can be chained.
func (s *Set) Add(templates ...*Template) *Set { func (s *Set) Add(templates ...*Template) *Set {
s.init() s.init()
for _, t := range templates { 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 { if _, ok := s.tmpl[t.name]; ok {
panic(fmt.Errorf("template: %q already defined in set", t.name)) panic(fmt.Errorf("template: %q already defined in set", t.name))
} }
s.tmpl[t.name] = t s.tmpl[t.name] = t
} t.set = s
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
} }
return 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 // 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 { func (s *Set) Execute(wr io.Writer, name string, data interface{}) os.Error {
tmpl := s.tmpl[name] tmpl := s.tmpl[name]
if tmpl == nil { if tmpl == nil {
return fmt.Errorf("template: no template %q in set", name) 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 // 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.errorf("unexpected %s in %s", end, context)
} }
t.stopParse() t.stopParse()
t.addToSet(s)
s.tmpl[t.name] = t s.tmpl[t.name] = t
} }
return nil return nil