]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/hil/walk.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hil / walk.go
1 package hil
2
3 import (
4 "fmt"
5 "reflect"
6 "strings"
7
8 "github.com/hashicorp/hil/ast"
9 "github.com/mitchellh/reflectwalk"
10 )
11
12 // WalkFn is the type of function to pass to Walk. Modify fields within
13 // WalkData to control whether replacement happens.
14 type WalkFn func(*WalkData) error
15
16 // WalkData is the structure passed to the callback of the Walk function.
17 //
18 // This structure contains data passed in as well as fields that are expected
19 // to be written by the caller as a result. Please see the documentation for
20 // each field for more information.
21 type WalkData struct {
22 // Root is the parsed root of this HIL program
23 Root ast.Node
24
25 // Location is the location within the structure where this
26 // value was found. This can be used to modify behavior within
27 // slices and so on.
28 Location reflectwalk.Location
29
30 // The below two values must be set by the callback to have any effect.
31 //
32 // Replace, if true, will replace the value in the structure with
33 // ReplaceValue. It is up to the caller to make sure this is a string.
34 Replace bool
35 ReplaceValue string
36 }
37
38 // Walk will walk an arbitrary Go structure and parse any string as an
39 // HIL program and call the callback cb to determine what to replace it
40 // with.
41 //
42 // This function is very useful for arbitrary HIL program interpolation
43 // across a complex configuration structure. Due to the heavy use of
44 // reflection in this function, it is recommend to write many unit tests
45 // with your typical configuration structures to hilp mitigate the risk
46 // of panics.
47 func Walk(v interface{}, cb WalkFn) error {
48 walker := &interpolationWalker{F: cb}
49 return reflectwalk.Walk(v, walker)
50 }
51
52 // interpolationWalker implements interfaces for the reflectwalk package
53 // (github.com/mitchellh/reflectwalk) that can be used to automatically
54 // execute a callback for an interpolation.
55 type interpolationWalker struct {
56 F WalkFn
57
58 key []string
59 lastValue reflect.Value
60 loc reflectwalk.Location
61 cs []reflect.Value
62 csKey []reflect.Value
63 csData interface{}
64 sliceIndex int
65 unknownKeys []string
66 }
67
68 func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
69 w.loc = loc
70 return nil
71 }
72
73 func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
74 w.loc = reflectwalk.None
75
76 switch loc {
77 case reflectwalk.Map:
78 w.cs = w.cs[:len(w.cs)-1]
79 case reflectwalk.MapValue:
80 w.key = w.key[:len(w.key)-1]
81 w.csKey = w.csKey[:len(w.csKey)-1]
82 case reflectwalk.Slice:
83 // Split any values that need to be split
84 w.splitSlice()
85 w.cs = w.cs[:len(w.cs)-1]
86 case reflectwalk.SliceElem:
87 w.csKey = w.csKey[:len(w.csKey)-1]
88 }
89
90 return nil
91 }
92
93 func (w *interpolationWalker) Map(m reflect.Value) error {
94 w.cs = append(w.cs, m)
95 return nil
96 }
97
98 func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
99 w.csData = k
100 w.csKey = append(w.csKey, k)
101 w.key = append(w.key, k.String())
102 w.lastValue = v
103 return nil
104 }
105
106 func (w *interpolationWalker) Slice(s reflect.Value) error {
107 w.cs = append(w.cs, s)
108 return nil
109 }
110
111 func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error {
112 w.csKey = append(w.csKey, reflect.ValueOf(i))
113 w.sliceIndex = i
114 return nil
115 }
116
117 func (w *interpolationWalker) Primitive(v reflect.Value) error {
118 setV := v
119
120 // We only care about strings
121 if v.Kind() == reflect.Interface {
122 setV = v
123 v = v.Elem()
124 }
125 if v.Kind() != reflect.String {
126 return nil
127 }
128
129 astRoot, err := Parse(v.String())
130 if err != nil {
131 return err
132 }
133
134 // If the AST we got is just a literal string value with the same
135 // value then we ignore it. We have to check if its the same value
136 // because it is possible to input a string, get out a string, and
137 // have it be different. For example: "foo-$${bar}" turns into
138 // "foo-${bar}"
139 if n, ok := astRoot.(*ast.LiteralNode); ok {
140 if s, ok := n.Value.(string); ok && s == v.String() {
141 return nil
142 }
143 }
144
145 if w.F == nil {
146 return nil
147 }
148
149 data := WalkData{Root: astRoot, Location: w.loc}
150 if err := w.F(&data); err != nil {
151 return fmt.Errorf(
152 "%s in:\n\n%s",
153 err, v.String())
154 }
155
156 if data.Replace {
157 /*
158 if remove {
159 w.removeCurrent()
160 return nil
161 }
162 */
163
164 resultVal := reflect.ValueOf(data.ReplaceValue)
165 switch w.loc {
166 case reflectwalk.MapKey:
167 m := w.cs[len(w.cs)-1]
168
169 // Delete the old value
170 var zero reflect.Value
171 m.SetMapIndex(w.csData.(reflect.Value), zero)
172
173 // Set the new key with the existing value
174 m.SetMapIndex(resultVal, w.lastValue)
175
176 // Set the key to be the new key
177 w.csData = resultVal
178 case reflectwalk.MapValue:
179 // If we're in a map, then the only way to set a map value is
180 // to set it directly.
181 m := w.cs[len(w.cs)-1]
182 mk := w.csData.(reflect.Value)
183 m.SetMapIndex(mk, resultVal)
184 default:
185 // Otherwise, we should be addressable
186 setV.Set(resultVal)
187 }
188 }
189
190 return nil
191 }
192
193 func (w *interpolationWalker) removeCurrent() {
194 // Append the key to the unknown keys
195 w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
196
197 for i := 1; i <= len(w.cs); i++ {
198 c := w.cs[len(w.cs)-i]
199 switch c.Kind() {
200 case reflect.Map:
201 // Zero value so that we delete the map key
202 var val reflect.Value
203
204 // Get the key and delete it
205 k := w.csData.(reflect.Value)
206 c.SetMapIndex(k, val)
207 return
208 }
209 }
210
211 panic("No container found for removeCurrent")
212 }
213
214 func (w *interpolationWalker) replaceCurrent(v reflect.Value) {
215 c := w.cs[len(w.cs)-2]
216 switch c.Kind() {
217 case reflect.Map:
218 // Get the key and delete it
219 k := w.csKey[len(w.csKey)-1]
220 c.SetMapIndex(k, v)
221 }
222 }
223
224 func (w *interpolationWalker) splitSlice() {
225 // Get the []interface{} slice so we can do some operations on
226 // it without dealing with reflection. We'll document each step
227 // here to be clear.
228 var s []interface{}
229 raw := w.cs[len(w.cs)-1]
230 switch v := raw.Interface().(type) {
231 case []interface{}:
232 s = v
233 case []map[string]interface{}:
234 return
235 default:
236 panic("Unknown kind: " + raw.Kind().String())
237 }
238
239 // Check if we have any elements that we need to split. If not, then
240 // just return since we're done.
241 split := false
242 if !split {
243 return
244 }
245
246 // Make a new result slice that is twice the capacity to fit our growth.
247 result := make([]interface{}, 0, len(s)*2)
248
249 // Go over each element of the original slice and start building up
250 // the resulting slice by splitting where we have to.
251 for _, v := range s {
252 sv, ok := v.(string)
253 if !ok {
254 // Not a string, so just set it
255 result = append(result, v)
256 continue
257 }
258
259 // Not a string list, so just set it
260 result = append(result, sv)
261 }
262
263 // Our slice is now done, we have to replace the slice now
264 // with this new one that we have.
265 w.replaceCurrent(reflect.ValueOf(result))
266 }