mirror of
https://github.com/golang/go
synced 2024-11-22 01:54:42 -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:
parent
5f134f9b5b
commit
0f7a1951b8
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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,25 +611,33 @@ 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()
|
||||||
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.
|
// parse is the helper for Parse.
|
||||||
@ -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))
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user