1
1
package template
2
2
3
3
import (
4
+ "bytes"
4
5
"encoding/json"
5
6
"fmt"
6
7
"reflect"
@@ -10,6 +11,24 @@ import (
10
11
"github.com/jmespath/go-jmespath"
11
12
)
12
13
14
+ // DeepCopyObject makes a deep copy of the argument, using encoding/gob encode/decode.
15
+ func DeepCopyObject (from interface {}) (interface {}, error ) {
16
+ var mod bytes.Buffer
17
+ enc := json .NewEncoder (& mod )
18
+ dec := json .NewDecoder (& mod )
19
+ err := enc .Encode (from )
20
+ if err != nil {
21
+ return nil , err
22
+ }
23
+
24
+ copy := reflect .New (reflect .TypeOf (from ))
25
+ err = dec .Decode (copy .Interface ())
26
+ if err != nil {
27
+ return nil , err
28
+ }
29
+ return reflect .Indirect (copy ).Interface (), nil
30
+ }
31
+
13
32
// QueryObject applies a JMESPath query specified by the expression, against the target object.
14
33
func QueryObject (exp string , target interface {}) (interface {}, error ) {
15
34
query , err := jmespath .Compile (exp )
@@ -119,8 +138,14 @@ func (t *Template) DefaultFuncs() []Function {
119
138
Description : []string {
120
139
"Source / evaluate the template at the input location (as URL)." ,
121
140
"This will make all of the global variables declared there visible in this template's context." ,
141
+ "Similar to 'source' in bash, sourcing another template means applying it in the same context " ,
142
+ "as the calling template. The context (e.g. variables) of the calling template as a result can be mutated." ,
122
143
},
123
- Func : func (p string ) (string , error ) {
144
+ Func : func (p string , opt ... interface {}) (string , error ) {
145
+ var o interface {}
146
+ if len (opt ) > 0 {
147
+ o = opt [0 ]
148
+ }
124
149
loc := p
125
150
if strings .Index (loc , "str://" ) == - 1 {
126
151
buff , err := getURL (t .url , p )
@@ -133,48 +158,54 @@ func (t *Template) DefaultFuncs() []Function {
133
158
if err != nil {
134
159
return "" , err
135
160
}
136
- // copy the binds in the parent scope into the child
137
- for k , v := range t .binds {
138
- sourced .binds [k ] = v
139
- }
140
- // inherit the functions defined for this template
141
- for k , v := range t .funcs {
142
- sourced .AddFunc (k , v )
143
- }
144
161
// set this as the parent of the sourced template so its global can mutate the globals in this
145
162
sourced .parent = t
163
+ sourced .forkFrom (t )
164
+ sourced .context = t .context
146
165
166
+ if o == nil {
167
+ o = sourced .context
168
+ }
147
169
// TODO(chungers) -- let the sourced template define new functions that can be called in the parent.
148
- return sourced .Render (nil )
170
+ return sourced .Render (o )
149
171
},
150
172
},
151
173
{
152
174
Name : "include" ,
153
175
Description : []string {
154
176
"Render content found at URL as template and include here." ,
155
177
"The optional second parameter is the context to use when rendering the template." ,
178
+ "Conceptually similar to exec in bash, where the template included is applied using a fork " ,
179
+ "of current context in the calling template. Any mutations to the context via 'global' will not " ,
180
+ "be visible in the calling template's context." ,
156
181
},
157
182
Func : func (p string , opt ... interface {}) (string , error ) {
158
183
var o interface {}
159
184
if len (opt ) > 0 {
160
185
o = opt [0 ]
161
186
}
162
- loc , err := getURL (t .url , p )
163
- if err != nil {
164
- return "" , err
187
+ loc := p
188
+ if strings .Index (loc , "str://" ) == - 1 {
189
+ buff , err := getURL (t .url , p )
190
+ if err != nil {
191
+ return "" , err
192
+ }
193
+ loc = buff
165
194
}
166
195
included , err := NewTemplate (loc , t .options )
167
196
if err != nil {
168
197
return "" , err
169
198
}
170
- // copy the binds in the parent scope into the child
171
- for k , v := range t . binds {
172
- included . binds [ k ] = v
199
+ dotCopy , err := included . forkFrom ( t )
200
+ if err != nil {
201
+ return "" , err
173
202
}
174
- // inherit the functions defined for this template
175
- for k , v := range t .funcs {
176
- included .AddFunc (k , v )
203
+ included .context = dotCopy
204
+
205
+ if o == nil {
206
+ o = included .context
177
207
}
208
+
178
209
return included .Render (o )
179
210
},
180
211
},
@@ -193,10 +224,10 @@ func (t *Template) DefaultFuncs() []Function {
193
224
"Defines a variable with the first argument as name and last argument value as the default." ,
194
225
"It's also ok to pass a third optional parameter, in the middle, as the documentation string." ,
195
226
},
196
- Func : func (name string , args ... interface {}) (string , error ) {
227
+ Func : func (name string , args ... interface {}) (Void , error ) {
197
228
if _ , has := t .defaults [name ]; has {
198
229
// not sure if this is good, but should complain loudly
199
- return "" , fmt .Errorf ("already defined: %v" , name )
230
+ return voidValue , fmt .Errorf ("already defined: %v" , name )
200
231
}
201
232
var doc string
202
233
var value interface {}
@@ -210,7 +241,7 @@ func (t *Template) DefaultFuncs() []Function {
210
241
value = args [1 ]
211
242
}
212
243
t .AddDef (name , value , doc )
213
- return "" , nil
244
+ return voidValue , nil
214
245
},
215
246
},
216
247
{
@@ -220,11 +251,9 @@ func (t *Template) DefaultFuncs() []Function {
220
251
"This is similar to def (which sets the default value)." ,
221
252
"Global variables are propagated to all templates that are rendered via the 'include' function." ,
222
253
},
223
- Func : func (name string , v interface {}) interface {} {
224
- for here := t ; here != nil ; here = here .parent {
225
- here .updateGlobal (name , v )
226
- }
227
- return ""
254
+ Func : func (n string , v interface {}) Void {
255
+ t .Global (n , v )
256
+ return voidValue
228
257
},
229
258
},
230
259
{
@@ -233,14 +262,7 @@ func (t *Template) DefaultFuncs() []Function {
233
262
"References / gets the variable named after the first argument." ,
234
263
"The values must be set first by either def or global." ,
235
264
},
236
- Func : func (name string ) interface {} {
237
- if found , has := t .binds [name ]; has {
238
- return found
239
- } else if v , has := t .defaults [name ]; has {
240
- return v .Value
241
- }
242
- return nil
243
- },
265
+ Func : t .Ref ,
244
266
},
245
267
{
246
268
Name : "q" ,
0 commit comments