diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/traversal.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcl/traversal.go | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcl/traversal.go b/vendor/github.com/hashicorp/hcl2/hcl/traversal.go new file mode 100644 index 0000000..24f4c91 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/traversal.go | |||
@@ -0,0 +1,352 @@ | |||
1 | package hcl | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | ) | ||
8 | |||
9 | // A Traversal is a description of traversing through a value through a | ||
10 | // series of operations such as attribute lookup, index lookup, etc. | ||
11 | // | ||
12 | // It is used to look up values in scopes, for example. | ||
13 | // | ||
14 | // The traversal operations are implementations of interface Traverser. | ||
15 | // This is a closed set of implementations, so the interface cannot be | ||
16 | // implemented from outside this package. | ||
17 | // | ||
18 | // A traversal can be absolute (its first value is a symbol name) or relative | ||
19 | // (starts from an existing value). | ||
20 | type Traversal []Traverser | ||
21 | |||
22 | // TraversalJoin appends a relative traversal to an absolute traversal to | ||
23 | // produce a new absolute traversal. | ||
24 | func TraversalJoin(abs Traversal, rel Traversal) Traversal { | ||
25 | if abs.IsRelative() { | ||
26 | panic("first argument to TraversalJoin must be absolute") | ||
27 | } | ||
28 | if !rel.IsRelative() { | ||
29 | panic("second argument to TraversalJoin must be relative") | ||
30 | } | ||
31 | |||
32 | ret := make(Traversal, len(abs)+len(rel)) | ||
33 | copy(ret, abs) | ||
34 | copy(ret[len(abs):], rel) | ||
35 | return ret | ||
36 | } | ||
37 | |||
38 | // TraverseRel applies the receiving traversal to the given value, returning | ||
39 | // the resulting value. This is supported only for relative traversals, | ||
40 | // and will panic if applied to an absolute traversal. | ||
41 | func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { | ||
42 | if !t.IsRelative() { | ||
43 | panic("can't use TraverseRel on an absolute traversal") | ||
44 | } | ||
45 | |||
46 | current := val | ||
47 | var diags Diagnostics | ||
48 | for _, tr := range t { | ||
49 | var newDiags Diagnostics | ||
50 | current, newDiags = tr.TraversalStep(current) | ||
51 | diags = append(diags, newDiags...) | ||
52 | if newDiags.HasErrors() { | ||
53 | return cty.DynamicVal, diags | ||
54 | } | ||
55 | } | ||
56 | return current, diags | ||
57 | } | ||
58 | |||
59 | // TraverseAbs applies the receiving traversal to the given eval context, | ||
60 | // returning the resulting value. This is supported only for absolute | ||
61 | // traversals, and will panic if applied to a relative traversal. | ||
62 | func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { | ||
63 | if t.IsRelative() { | ||
64 | panic("can't use TraverseAbs on a relative traversal") | ||
65 | } | ||
66 | |||
67 | split := t.SimpleSplit() | ||
68 | root := split.Abs[0].(TraverseRoot) | ||
69 | name := root.Name | ||
70 | |||
71 | thisCtx := ctx | ||
72 | hasNonNil := false | ||
73 | for thisCtx != nil { | ||
74 | if thisCtx.Variables == nil { | ||
75 | thisCtx = thisCtx.parent | ||
76 | continue | ||
77 | } | ||
78 | hasNonNil = true | ||
79 | val, exists := thisCtx.Variables[name] | ||
80 | if exists { | ||
81 | return split.Rel.TraverseRel(val) | ||
82 | } | ||
83 | thisCtx = thisCtx.parent | ||
84 | } | ||
85 | |||
86 | if !hasNonNil { | ||
87 | return cty.DynamicVal, Diagnostics{ | ||
88 | { | ||
89 | Severity: DiagError, | ||
90 | Summary: "Variables not allowed", | ||
91 | Detail: "Variables may not be used here.", | ||
92 | Subject: &root.SrcRange, | ||
93 | }, | ||
94 | } | ||
95 | } | ||
96 | |||
97 | suggestions := make([]string, 0, len(ctx.Variables)) | ||
98 | thisCtx = ctx | ||
99 | for thisCtx != nil { | ||
100 | for k := range thisCtx.Variables { | ||
101 | suggestions = append(suggestions, k) | ||
102 | } | ||
103 | thisCtx = thisCtx.parent | ||
104 | } | ||
105 | suggestion := nameSuggestion(name, suggestions) | ||
106 | if suggestion != "" { | ||
107 | suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) | ||
108 | } | ||
109 | |||
110 | return cty.DynamicVal, Diagnostics{ | ||
111 | { | ||
112 | Severity: DiagError, | ||
113 | Summary: "Unknown variable", | ||
114 | Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion), | ||
115 | Subject: &root.SrcRange, | ||
116 | }, | ||
117 | } | ||
118 | } | ||
119 | |||
120 | // IsRelative returns true if the receiver is a relative traversal, or false | ||
121 | // otherwise. | ||
122 | func (t Traversal) IsRelative() bool { | ||
123 | if len(t) == 0 { | ||
124 | return true | ||
125 | } | ||
126 | if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot { | ||
127 | return false | ||
128 | } | ||
129 | return true | ||
130 | } | ||
131 | |||
132 | // SimpleSplit returns a TraversalSplit where the name lookup is the absolute | ||
133 | // part and the remainder is the relative part. Supported only for | ||
134 | // absolute traversals, and will panic if applied to a relative traversal. | ||
135 | // | ||
136 | // This can be used by applications that have a relatively-simple variable | ||
137 | // namespace where only the top-level is directly populated in the scope, with | ||
138 | // everything else handled by relative lookups from those initial values. | ||
139 | func (t Traversal) SimpleSplit() TraversalSplit { | ||
140 | if t.IsRelative() { | ||
141 | panic("can't use SimpleSplit on a relative traversal") | ||
142 | } | ||
143 | return TraversalSplit{ | ||
144 | Abs: t[0:1], | ||
145 | Rel: t[1:], | ||
146 | } | ||
147 | } | ||
148 | |||
149 | // RootName returns the root name for a absolute traversal. Will panic if | ||
150 | // called on a relative traversal. | ||
151 | func (t Traversal) RootName() string { | ||
152 | if t.IsRelative() { | ||
153 | panic("can't use RootName on a relative traversal") | ||
154 | |||
155 | } | ||
156 | return t[0].(TraverseRoot).Name | ||
157 | } | ||
158 | |||
159 | // SourceRange returns the source range for the traversal. | ||
160 | func (t Traversal) SourceRange() Range { | ||
161 | if len(t) == 0 { | ||
162 | // Nothing useful to return here, but we'll return something | ||
163 | // that's correctly-typed at least. | ||
164 | return Range{} | ||
165 | } | ||
166 | |||
167 | return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange()) | ||
168 | } | ||
169 | |||
170 | // TraversalSplit represents a pair of traversals, the first of which is | ||
171 | // an absolute traversal and the second of which is relative to the first. | ||
172 | // | ||
173 | // This is used by calling applications that only populate prefixes of the | ||
174 | // traversals in the scope, with Abs representing the part coming from the | ||
175 | // scope and Rel representing the remaining steps once that part is | ||
176 | // retrieved. | ||
177 | type TraversalSplit struct { | ||
178 | Abs Traversal | ||
179 | Rel Traversal | ||
180 | } | ||
181 | |||
182 | // TraverseAbs traverses from a scope to the value resulting from the | ||
183 | // absolute traversal. | ||
184 | func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { | ||
185 | return t.Abs.TraverseAbs(ctx) | ||
186 | } | ||
187 | |||
188 | // TraverseRel traverses from a given value, assumed to be the result of | ||
189 | // TraverseAbs on some scope, to a final result for the entire split traversal. | ||
190 | func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { | ||
191 | return t.Rel.TraverseRel(val) | ||
192 | } | ||
193 | |||
194 | // Traverse is a convenience function to apply TraverseAbs followed by | ||
195 | // TraverseRel. | ||
196 | func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) { | ||
197 | v1, diags := t.TraverseAbs(ctx) | ||
198 | if diags.HasErrors() { | ||
199 | return cty.DynamicVal, diags | ||
200 | } | ||
201 | v2, newDiags := t.TraverseRel(v1) | ||
202 | diags = append(diags, newDiags...) | ||
203 | return v2, diags | ||
204 | } | ||
205 | |||
206 | // Join concatenates together the Abs and Rel parts to produce a single | ||
207 | // absolute traversal. | ||
208 | func (t TraversalSplit) Join() Traversal { | ||
209 | return TraversalJoin(t.Abs, t.Rel) | ||
210 | } | ||
211 | |||
212 | // RootName returns the root name for the absolute part of the split. | ||
213 | func (t TraversalSplit) RootName() string { | ||
214 | return t.Abs.RootName() | ||
215 | } | ||
216 | |||
217 | // A Traverser is a step within a Traversal. | ||
218 | type Traverser interface { | ||
219 | TraversalStep(cty.Value) (cty.Value, Diagnostics) | ||
220 | SourceRange() Range | ||
221 | isTraverserSigil() isTraverser | ||
222 | } | ||
223 | |||
224 | // Embed this in a struct to declare it as a Traverser | ||
225 | type isTraverser struct { | ||
226 | } | ||
227 | |||
228 | func (tr isTraverser) isTraverserSigil() isTraverser { | ||
229 | return isTraverser{} | ||
230 | } | ||
231 | |||
232 | // TraverseRoot looks up a root name in a scope. It is used as the first step | ||
233 | // of an absolute Traversal, and cannot itself be traversed directly. | ||
234 | type TraverseRoot struct { | ||
235 | isTraverser | ||
236 | Name string | ||
237 | SrcRange Range | ||
238 | } | ||
239 | |||
240 | // TraversalStep on a TraverseName immediately panics, because absolute | ||
241 | // traversals cannot be directly traversed. | ||
242 | func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) { | ||
243 | panic("Cannot traverse an absolute traversal") | ||
244 | } | ||
245 | |||
246 | func (tn TraverseRoot) SourceRange() Range { | ||
247 | return tn.SrcRange | ||
248 | } | ||
249 | |||
250 | // TraverseAttr looks up an attribute in its initial value. | ||
251 | type TraverseAttr struct { | ||
252 | isTraverser | ||
253 | Name string | ||
254 | SrcRange Range | ||
255 | } | ||
256 | |||
257 | func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { | ||
258 | if val.IsNull() { | ||
259 | return cty.DynamicVal, Diagnostics{ | ||
260 | { | ||
261 | Severity: DiagError, | ||
262 | Summary: "Attempt to get attribute from null value", | ||
263 | Detail: "This value is null, so it does not have any attributes.", | ||
264 | Subject: &tn.SrcRange, | ||
265 | }, | ||
266 | } | ||
267 | } | ||
268 | |||
269 | ty := val.Type() | ||
270 | switch { | ||
271 | case ty.IsObjectType(): | ||
272 | if !ty.HasAttribute(tn.Name) { | ||
273 | return cty.DynamicVal, Diagnostics{ | ||
274 | { | ||
275 | Severity: DiagError, | ||
276 | Summary: "Unsupported attribute", | ||
277 | Detail: fmt.Sprintf("This object does not have an attribute named %q.", tn.Name), | ||
278 | Subject: &tn.SrcRange, | ||
279 | }, | ||
280 | } | ||
281 | } | ||
282 | |||
283 | if !val.IsKnown() { | ||
284 | return cty.UnknownVal(ty.AttributeType(tn.Name)), nil | ||
285 | } | ||
286 | |||
287 | return val.GetAttr(tn.Name), nil | ||
288 | case ty.IsMapType(): | ||
289 | if !val.IsKnown() { | ||
290 | return cty.UnknownVal(ty.ElementType()), nil | ||
291 | } | ||
292 | |||
293 | idx := cty.StringVal(tn.Name) | ||
294 | if val.HasIndex(idx).False() { | ||
295 | return cty.DynamicVal, Diagnostics{ | ||
296 | { | ||
297 | Severity: DiagError, | ||
298 | Summary: "Missing map element", | ||
299 | Detail: fmt.Sprintf("This map does not have an element with the key %q.", tn.Name), | ||
300 | Subject: &tn.SrcRange, | ||
301 | }, | ||
302 | } | ||
303 | } | ||
304 | |||
305 | return val.Index(idx), nil | ||
306 | case ty == cty.DynamicPseudoType: | ||
307 | return cty.DynamicVal, nil | ||
308 | default: | ||
309 | return cty.DynamicVal, Diagnostics{ | ||
310 | { | ||
311 | Severity: DiagError, | ||
312 | Summary: "Unsupported attribute", | ||
313 | Detail: "This value does not have any attributes.", | ||
314 | Subject: &tn.SrcRange, | ||
315 | }, | ||
316 | } | ||
317 | } | ||
318 | } | ||
319 | |||
320 | func (tn TraverseAttr) SourceRange() Range { | ||
321 | return tn.SrcRange | ||
322 | } | ||
323 | |||
324 | // TraverseIndex applies the index operation to its initial value. | ||
325 | type TraverseIndex struct { | ||
326 | isTraverser | ||
327 | Key cty.Value | ||
328 | SrcRange Range | ||
329 | } | ||
330 | |||
331 | func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { | ||
332 | return Index(val, tn.Key, &tn.SrcRange) | ||
333 | } | ||
334 | |||
335 | func (tn TraverseIndex) SourceRange() Range { | ||
336 | return tn.SrcRange | ||
337 | } | ||
338 | |||
339 | // TraverseSplat applies the splat operation to its initial value. | ||
340 | type TraverseSplat struct { | ||
341 | isTraverser | ||
342 | Each Traversal | ||
343 | SrcRange Range | ||
344 | } | ||
345 | |||
346 | func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { | ||
347 | panic("TraverseSplat not yet implemented") | ||
348 | } | ||
349 | |||
350 | func (tn TraverseSplat) SourceRange() Range { | ||
351 | return tn.SrcRange | ||
352 | } | ||