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.
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)
}
type state struct {
tmpl *Template
wr io.Writer
- set *Set
line int // line number for errors
vars []variable // push-down stack of variable values.
}
// 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}},
}
}
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)
}
}
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)
}
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)
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.
// 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.
// 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.
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))
}
// 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
}
}
// 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
t.errorf("unexpected %s in %s", end, context)
}
t.stopParse()
+ t.addToSet(s)
s.tmpl[t.name] = t
}
return nil