@@ -2,7 +2,9 @@ package template
2
2
3
3
import (
4
4
"bytes"
5
+ "fmt"
5
6
"io"
7
+ "reflect"
6
8
"strings"
7
9
"sync"
8
10
"text/template"
@@ -11,6 +13,30 @@ import (
11
13
log "github.com/Sirupsen/logrus"
12
14
)
13
15
16
+ // Function contains the description of an exported template function
17
+ type Function struct {
18
+
19
+ // Name is the function name to bind in the template
20
+ Name string
21
+
22
+ // Description provides help for the function
23
+ Description string
24
+
25
+ // Func is the reference to the actual function
26
+ Func interface {}
27
+ }
28
+
29
+ // Context is a marker interface for a user-defined struct that is passed into the template engine (as context)
30
+ // and accessible in the exported template functions. Template functions can have the signature
31
+ // func(template.Context, arg1, arg2 ...) (string, error) and when functions like this are registered, the template
32
+ // engine will dynamically create and export a function of the form func(arg1, arg2...) (string, error) where
33
+ // the context instance becomes an out-of-band struct that can be mutated by functions. This in essence allows
34
+ // structured data as output of the template, in addition to a string from evaluating the template.
35
+ type Context interface {
36
+ // Funcs returns a list of special template functions of the form func(template.Context, arg1, arg2) interface{}
37
+ Funcs () []Function
38
+ }
39
+
14
40
// Options contains parameters for customizing the behavior of the engine
15
41
type Options struct {
16
42
@@ -87,10 +113,10 @@ func (t *Template) Validate() (*Template, error) {
87
113
t .lock .Lock ()
88
114
t .parsed = nil
89
115
t .lock .Unlock ()
90
- return t , t .build ()
116
+ return t , t .build (nil )
91
117
}
92
118
93
- func (t * Template ) build () error {
119
+ func (t * Template ) build (context Context ) error {
94
120
t .lock .Lock ()
95
121
defer t .lock .Unlock ()
96
122
@@ -105,7 +131,21 @@ func (t *Template) build() error {
105
131
}
106
132
107
133
for k , v := range t .funcs {
108
- fm [k ] = v
134
+ if tf , err := makeTemplateFunc (context , v ); err == nil {
135
+ fm [k ] = tf
136
+ } else {
137
+ return err
138
+ }
139
+ }
140
+
141
+ if context != nil {
142
+ for _ , f := range context .Funcs () {
143
+ if tf , err := makeTemplateFunc (context , f .Func ); err == nil {
144
+ fm [f .Name ] = tf
145
+ } else {
146
+ return err
147
+ }
148
+ }
109
149
}
110
150
111
151
parsed , err := template .New (t .url ).Funcs (fm ).Parse (string (t .body ))
@@ -119,18 +159,75 @@ func (t *Template) build() error {
119
159
120
160
// Execute is a drop-in replace of the execute method of template
121
161
func (t * Template ) Execute (output io.Writer , context interface {}) error {
122
- if err := t .build (); err != nil {
162
+ if err := t .build (toContext ( context ) ); err != nil {
123
163
return err
124
164
}
125
165
return t .parsed .Execute (output , context )
126
166
}
127
167
168
+ func toContext (in interface {}) Context {
169
+ var context Context
170
+ if in != nil {
171
+ if s , is := in .(Context ); is {
172
+ context = s
173
+ }
174
+ }
175
+ return context
176
+ }
177
+
128
178
// Render renders the template given the context
129
179
func (t * Template ) Render (context interface {}) (string , error ) {
130
- if err := t .build (); err != nil {
180
+ if err := t .build (toContext ( context ) ); err != nil {
131
181
return "" , err
132
182
}
133
183
var buff bytes.Buffer
134
184
err := t .parsed .Execute (& buff , context )
135
185
return buff .String (), err
136
186
}
187
+
188
+ // converts a function of f(Context, ags...) to a regular template function
189
+ func makeTemplateFunc (ctx Context , f interface {}) (interface {}, error ) {
190
+
191
+ contextType := reflect .TypeOf ((* Context )(nil )).Elem ()
192
+
193
+ ff := reflect .Indirect (reflect .ValueOf (f ))
194
+ // first we check to see if f has the special signature where the first
195
+ // parameter is the context parameter...
196
+ if ff .Kind () != reflect .Func {
197
+ return nil , fmt .Errorf ("not a function:%v" , f )
198
+ }
199
+
200
+ if ff .Type ().In (0 ).AssignableTo (contextType ) {
201
+
202
+ in := make ([]reflect.Type , ff .Type ().NumIn ()- 1 ) // exclude the context param
203
+ out := make ([]reflect.Type , ff .Type ().NumOut ())
204
+
205
+ for i := 1 ; i < ff .Type ().NumIn (); i ++ {
206
+ in [i - 1 ] = ff .Type ().In (i )
207
+ }
208
+ variadic := false
209
+ if len (in ) > 0 {
210
+ variadic = in [len (in )- 1 ].Kind () == reflect .Slice
211
+ }
212
+ for i := 0 ; i < ff .Type ().NumOut (); i ++ {
213
+ out [i ] = ff .Type ().Out (i )
214
+ }
215
+ funcType := reflect .FuncOf (in , out , variadic )
216
+ funcImpl := func (in []reflect.Value ) []reflect.Value {
217
+ if ! variadic {
218
+ return ff .Call (append ([]reflect.Value {reflect .ValueOf (ctx )}, in ... ))
219
+ }
220
+
221
+ variadicParam := in [len (in )- 1 ]
222
+ last := make ([]reflect.Value , variadicParam .Len ())
223
+ for i := 0 ; i < variadicParam .Len (); i ++ {
224
+ last [i ] = variadicParam .Index (i )
225
+ }
226
+ return ff .Call (append (append ([]reflect.Value {reflect .ValueOf (ctx )}, in [0 :len (in )- 1 ]... ), last ... ))
227
+ }
228
+
229
+ newFunc := reflect .MakeFunc (funcType , funcImpl )
230
+ return newFunc .Interface (), nil
231
+ }
232
+ return ff .Interface (), nil
233
+ }
0 commit comments