]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package cty |
2 | ||
3 | // Walk visits all of the values in a possibly-complex structure, calling | |
4 | // a given function for each value. | |
5 | // | |
6 | // For example, given a list of strings the callback would first be called | |
7 | // with the whole list and then called once for each element of the list. | |
8 | // | |
9 | // The callback function may prevent recursive visits to child values by | |
10 | // returning false. The callback function my halt the walk altogether by | |
11 | // returning a non-nil error. If the returned error is about the element | |
12 | // currently being visited, it is recommended to use the provided path | |
13 | // value to produce a PathError describing that context. | |
14 | // | |
15 | // The path passed to the given function may not be used after that function | |
16 | // returns, since its backing array is re-used for other calls. | |
17 | func Walk(val Value, cb func(Path, Value) (bool, error)) error { | |
18 | var path Path | |
19 | return walk(path, val, cb) | |
20 | } | |
21 | ||
22 | func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error { | |
23 | deeper, err := cb(path, val) | |
24 | if err != nil { | |
25 | return err | |
26 | } | |
27 | if !deeper { | |
28 | return nil | |
29 | } | |
30 | ||
31 | if val.IsNull() || !val.IsKnown() { | |
32 | // Can't recurse into null or unknown values, regardless of type | |
33 | return nil | |
34 | } | |
35 | ||
36 | ty := val.Type() | |
37 | switch { | |
38 | case ty.IsObjectType(): | |
39 | for it := val.ElementIterator(); it.Next(); { | |
40 | nameVal, av := it.Element() | |
41 | path := append(path, GetAttrStep{ | |
42 | Name: nameVal.AsString(), | |
43 | }) | |
44 | err := walk(path, av, cb) | |
45 | if err != nil { | |
46 | return err | |
47 | } | |
48 | } | |
49 | case val.CanIterateElements(): | |
50 | for it := val.ElementIterator(); it.Next(); { | |
51 | kv, ev := it.Element() | |
52 | path := append(path, IndexStep{ | |
53 | Key: kv, | |
54 | }) | |
55 | err := walk(path, ev, cb) | |
56 | if err != nil { | |
57 | return err | |
58 | } | |
59 | } | |
60 | } | |
61 | return nil | |
62 | } | |
63 | ||
64 | // Transform visits all of the values in a possibly-complex structure, | |
65 | // calling a given function for each value which has an opportunity to | |
66 | // replace that value. | |
67 | // | |
68 | // Unlike Walk, Transform visits child nodes first, so for a list of strings | |
69 | // it would first visit the strings and then the _new_ list constructed | |
70 | // from the transformed values of the list items. | |
71 | // | |
72 | // This is useful for creating the effect of being able to make deep mutations | |
73 | // to a value even though values are immutable. However, it's the responsibility | |
74 | // of the given function to preserve expected invariants, such as homogenity of | |
75 | // element types in collections; this function can panic if such invariants | |
76 | // are violated, just as if new values were constructed directly using the | |
77 | // value constructor functions. An easy way to preserve invariants is to | |
78 | // ensure that the transform function never changes the value type. | |
79 | // | |
80 | // The callback function my halt the walk altogether by | |
81 | // returning a non-nil error. If the returned error is about the element | |
82 | // currently being visited, it is recommended to use the provided path | |
83 | // value to produce a PathError describing that context. | |
84 | // | |
85 | // The path passed to the given function may not be used after that function | |
86 | // returns, since its backing array is re-used for other calls. | |
87 | func Transform(val Value, cb func(Path, Value) (Value, error)) (Value, error) { | |
88 | var path Path | |
89 | return transform(path, val, cb) | |
90 | } | |
91 | ||
92 | func transform(path Path, val Value, cb func(Path, Value) (Value, error)) (Value, error) { | |
93 | ty := val.Type() | |
94 | var newVal Value | |
95 | ||
96 | switch { | |
97 | ||
98 | case val.IsNull() || !val.IsKnown(): | |
99 | // Can't recurse into null or unknown values, regardless of type | |
100 | newVal = val | |
101 | ||
102 | case ty.IsListType() || ty.IsSetType() || ty.IsTupleType(): | |
103 | l := val.LengthInt() | |
104 | switch l { | |
105 | case 0: | |
106 | // No deep transform for an empty sequence | |
107 | newVal = val | |
108 | default: | |
109 | elems := make([]Value, 0, l) | |
110 | for it := val.ElementIterator(); it.Next(); { | |
111 | kv, ev := it.Element() | |
112 | path := append(path, IndexStep{ | |
113 | Key: kv, | |
114 | }) | |
115 | newEv, err := transform(path, ev, cb) | |
116 | if err != nil { | |
117 | return DynamicVal, err | |
118 | } | |
119 | elems = append(elems, newEv) | |
120 | } | |
121 | switch { | |
122 | case ty.IsListType(): | |
123 | newVal = ListVal(elems) | |
124 | case ty.IsSetType(): | |
125 | newVal = SetVal(elems) | |
126 | case ty.IsTupleType(): | |
127 | newVal = TupleVal(elems) | |
128 | default: | |
129 | panic("unknown sequence type") // should never happen because of the case we are in | |
130 | } | |
131 | } | |
132 | ||
133 | case ty.IsMapType(): | |
134 | l := val.LengthInt() | |
135 | switch l { | |
136 | case 0: | |
137 | // No deep transform for an empty map | |
138 | newVal = val | |
139 | default: | |
140 | elems := make(map[string]Value) | |
141 | for it := val.ElementIterator(); it.Next(); { | |
142 | kv, ev := it.Element() | |
143 | path := append(path, IndexStep{ | |
144 | Key: kv, | |
145 | }) | |
146 | newEv, err := transform(path, ev, cb) | |
147 | if err != nil { | |
148 | return DynamicVal, err | |
149 | } | |
150 | elems[kv.AsString()] = newEv | |
151 | } | |
152 | newVal = MapVal(elems) | |
153 | } | |
154 | ||
155 | case ty.IsObjectType(): | |
156 | switch { | |
157 | case ty.Equals(EmptyObject): | |
158 | // No deep transform for an empty object | |
159 | newVal = val | |
160 | default: | |
161 | atys := ty.AttributeTypes() | |
162 | newAVs := make(map[string]Value) | |
163 | for name := range atys { | |
164 | av := val.GetAttr(name) | |
165 | path := append(path, GetAttrStep{ | |
166 | Name: name, | |
167 | }) | |
168 | newAV, err := transform(path, av, cb) | |
169 | if err != nil { | |
170 | return DynamicVal, err | |
171 | } | |
172 | newAVs[name] = newAV | |
173 | } | |
174 | newVal = ObjectVal(newAVs) | |
175 | } | |
176 | ||
177 | default: | |
178 | newVal = val | |
179 | } | |
180 | ||
181 | return cb(path, newVal) | |
182 | } |