summaryrefslogtreecommitdiff
path: root/src/pkg/exp/template/set.go
blob: a685e996de1ba6a96c421618a08f76cfffbf1c83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package template

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"runtime"
	"strconv"
)

// Set holds a set of related templates that can refer to one another by name.
// The zero value represents an empty set.
// A template may be a member of multiple sets.
type Set struct {
	tmpl  map[string]*Template
	funcs map[string]reflect.Value
}

func (s *Set) init() {
	if s.tmpl == nil {
		s.tmpl = make(map[string]*Template)
		s.funcs = make(map[string]reflect.Value)
	}
}

// Funcs adds the elements of the argument map to the set's function map.  It
// panics if a value in the map is not a function with appropriate return
// type.
// The return value is the set, so calls can be chained.
func (s *Set) Funcs(funcMap FuncMap) *Set {
	s.init()
	addFuncs(s.funcs, funcMap)
	return s
}

// Add adds the argument templates to the set. It panics if two templates
// 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 {
	for _, t := range templates {
		if err := s.add(t); err != nil {
			panic(err)
		}
	}
	return s
}

// add adds the argument template to the set.
func (s *Set) add(t *Template) os.Error {
	s.init()
	if t.set != nil {
		return fmt.Errorf("template: %q already in a set", t.name)
	}
	if _, ok := s.tmpl[t.name]; ok {
		return fmt.Errorf("template: %q already defined in set", t.name)
	}
	s.tmpl[t.name] = t
	t.set = s
	return nil
}

// Template returns the template with the given name in the set,
// or nil if there is no such template.
func (s *Set) Template(name string) *Template {
	return s.tmpl[name]
}

// Execute applies the named template to the specified data object, writing
// 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.Execute(wr, data)
}

// recover is the handler that turns panics into returns from the top
// level of Parse.
func (s *Set) recover(errp *os.Error) {
	e := recover()
	if e != nil {
		if _, ok := e.(runtime.Error); ok {
			panic(e)
		}
		s.tmpl = nil
		*errp = e.(os.Error)
	}
	return
}

// Parse parses a string into a set of named templates.  Parse may be called
// multiple times for a given set, adding the templates defined in the string
// to the set.  If a template is redefined, the element in the set is
// overwritten with the new definition.
func (s *Set) Parse(text string) (set *Set, err os.Error) {
	set = s
	s.init()
	defer s.recover(&err)
	lex := lex("set", text)
	const context = "define clause"
	for {
		t := New("set") // name will be updated once we know it.
		t.startParse(s, lex)
		// Expect EOF or "{{ define name }}".
		if t.atEOF() {
			return
		}
		t.expect(itemLeftDelim, context)
		t.expect(itemDefine, context)
		name := t.expect(itemString, context)
		t.name, err = strconv.Unquote(name.val)
		if err != nil {
			t.error(err)
		}
		t.expect(itemRightDelim, context)
		end := t.parse(false)
		if end == nil {
			t.errorf("unexpected EOF in %s", context)
		}
		if end.typ() != nodeEnd {
			t.errorf("unexpected %s in %s", end, context)
		}
		t.stopParse()
		t.addToSet(s)
		s.tmpl[t.name] = t
	}
	return s, nil
}