@@ -2,7 +2,9 @@ package template
22
33import (
44 "bytes"
5+ "fmt"
56 "io"
7+ "reflect"
68 "strings"
79 "sync"
810 "text/template"
@@ -11,6 +13,30 @@ import (
1113 log "github.com/Sirupsen/logrus"
1214)
1315
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+
1440// Options contains parameters for customizing the behavior of the engine
1541type Options struct {
1642
@@ -87,10 +113,10 @@ func (t *Template) Validate() (*Template, error) {
87113 t .lock .Lock ()
88114 t .parsed = nil
89115 t .lock .Unlock ()
90- return t , t .build ()
116+ return t , t .build (nil )
91117}
92118
93- func (t * Template ) build () error {
119+ func (t * Template ) build (context Context ) error {
94120 t .lock .Lock ()
95121 defer t .lock .Unlock ()
96122
@@ -105,7 +131,21 @@ func (t *Template) build() error {
105131 }
106132
107133 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+ }
109149 }
110150
111151 parsed , err := template .New (t .url ).Funcs (fm ).Parse (string (t .body ))
@@ -119,18 +159,75 @@ func (t *Template) build() error {
119159
120160// Execute is a drop-in replace of the execute method of template
121161func (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 {
123163 return err
124164 }
125165 return t .parsed .Execute (output , context )
126166}
127167
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+
128178// Render renders the template given the context
129179func (t * Template ) Render (context interface {}) (string , error ) {
130- if err := t .build (); err != nil {
180+ if err := t .build (toContext ( context ) ); err != nil {
131181 return "" , err
132182 }
133183 var buff bytes.Buffer
134184 err := t .parsed .Execute (& buff , context )
135185 return buff .String (), err
136186}
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