11package template
22
33import (
4+ "bytes"
45 "encoding/json"
56 "fmt"
67 "reflect"
@@ -10,6 +11,24 @@ import (
1011 "github.com/jmespath/go-jmespath"
1112)
1213
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+
1332// QueryObject applies a JMESPath query specified by the expression, against the target object.
1433func QueryObject (exp string , target interface {}) (interface {}, error ) {
1534 query , err := jmespath .Compile (exp )
@@ -119,8 +138,14 @@ func (t *Template) DefaultFuncs() []Function {
119138 Description : []string {
120139 "Source / evaluate the template at the input location (as URL)." ,
121140 "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." ,
122143 },
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+ }
124149 loc := p
125150 if strings .Index (loc , "str://" ) == - 1 {
126151 buff , err := getURL (t .url , p )
@@ -133,48 +158,54 @@ func (t *Template) DefaultFuncs() []Function {
133158 if err != nil {
134159 return "" , err
135160 }
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- }
144161 // set this as the parent of the sourced template so its global can mutate the globals in this
145162 sourced .parent = t
163+ sourced .forkFrom (t )
164+ sourced .context = t .context
146165
166+ if o == nil {
167+ o = sourced .context
168+ }
147169 // 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 )
149171 },
150172 },
151173 {
152174 Name : "include" ,
153175 Description : []string {
154176 "Render content found at URL as template and include here." ,
155177 "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." ,
156181 },
157182 Func : func (p string , opt ... interface {}) (string , error ) {
158183 var o interface {}
159184 if len (opt ) > 0 {
160185 o = opt [0 ]
161186 }
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
165194 }
166195 included , err := NewTemplate (loc , t .options )
167196 if err != nil {
168197 return "" , err
169198 }
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
173202 }
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
177207 }
208+
178209 return included .Render (o )
179210 },
180211 },
@@ -193,10 +224,10 @@ func (t *Template) DefaultFuncs() []Function {
193224 "Defines a variable with the first argument as name and last argument value as the default." ,
194225 "It's also ok to pass a third optional parameter, in the middle, as the documentation string." ,
195226 },
196- Func : func (name string , args ... interface {}) (string , error ) {
227+ Func : func (name string , args ... interface {}) (Void , error ) {
197228 if _ , has := t .defaults [name ]; has {
198229 // 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 )
200231 }
201232 var doc string
202233 var value interface {}
@@ -210,7 +241,7 @@ func (t *Template) DefaultFuncs() []Function {
210241 value = args [1 ]
211242 }
212243 t .AddDef (name , value , doc )
213- return "" , nil
244+ return voidValue , nil
214245 },
215246 },
216247 {
@@ -220,11 +251,9 @@ func (t *Template) DefaultFuncs() []Function {
220251 "This is similar to def (which sets the default value)." ,
221252 "Global variables are propagated to all templates that are rendered via the 'include' function." ,
222253 },
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
228257 },
229258 },
230259 {
@@ -233,14 +262,7 @@ func (t *Template) DefaultFuncs() []Function {
233262 "References / gets the variable named after the first argument." ,
234263 "The values must be set first by either def or global." ,
235264 },
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 ,
244266 },
245267 {
246268 Name : "q" ,
0 commit comments