-
-
Notifications
You must be signed in to change notification settings - Fork 72
/
Copy pathmarks.go
392 lines (362 loc) · 10.4 KB
/
marks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
package cty
import (
"fmt"
"strings"
)
// marker is an internal wrapper type used to add special "marks" to values.
//
// A "mark" is an annotation that can be used to represent additional
// characteristics of values that propagate through operation methods to
// result values. However, a marked value cannot be used with integration
// methods normally associated with its type, in order to ensure that
// calling applications don't inadvertently drop marks as they round-trip
// values out of cty and back in again.
//
// Marked values are created only explicitly by the calling application, so
// an application that never marks a value does not need to worry about
// encountering marked values.
type marker struct {
realV interface{}
marks ValueMarks
}
// ValueMarks is a map, representing a set, of "mark" values associated with
// a Value. See Value.Mark for more information on the usage of mark values.
type ValueMarks map[interface{}]struct{}
// NewValueMarks constructs a new ValueMarks set with the given mark values.
//
// If any of the arguments are already ValueMarks values then they'll be merged
// into the result, rather than used directly as individual marks.
func NewValueMarks(marks ...interface{}) ValueMarks {
if len(marks) == 0 {
return nil
}
ret := make(ValueMarks, len(marks))
for _, v := range marks {
if vm, ok := v.(ValueMarks); ok {
// Constructing a new ValueMarks with an existing ValueMarks
// implements a merge operation. (This can cause our result to
// have a larger size than we expected, but that's okay.)
for v := range vm {
ret[v] = struct{}{}
}
continue
}
ret[v] = struct{}{}
}
if len(ret) == 0 {
// If we were merging ValueMarks values together and they were all
// empty then we'll avoid returning a zero-length map and return a
// nil instead, as is conventional.
return nil
}
return ret
}
// Equal returns true if the receiver and the given ValueMarks both contain
// the same marks.
func (m ValueMarks) Equal(o ValueMarks) bool {
if len(m) != len(o) {
return false
}
for v := range m {
if _, ok := o[v]; !ok {
return false
}
}
return true
}
func (m ValueMarks) GoString() string {
var s strings.Builder
s.WriteString("cty.NewValueMarks(")
i := 0
for mv := range m {
if i != 0 {
s.WriteString(", ")
}
s.WriteString(fmt.Sprintf("%#v", mv))
i++
}
s.WriteString(")")
return s.String()
}
// PathValueMarks is a structure that enables tracking marks
// and the paths where they are located in one type
type PathValueMarks struct {
Path Path
Marks ValueMarks
}
func (p PathValueMarks) Equal(o PathValueMarks) bool {
if !p.Path.Equals(o.Path) {
return false
}
if !p.Marks.Equal(o.Marks) {
return false
}
return true
}
// IsMarked returns true if and only if the receiving value carries at least
// one mark. A marked value cannot be used directly with integration methods
// without explicitly unmarking it (and retrieving the markings) first.
func (val Value) IsMarked() bool {
_, ok := val.v.(marker)
return ok
}
// HasMark returns true if and only if the receiving value has the given mark.
func (val Value) HasMark(mark interface{}) bool {
if mr, ok := val.v.(marker); ok {
_, ok := mr.marks[mark]
return ok
}
return false
}
// ContainsMarked returns true if the receiving value or any value within it
// is marked.
//
// This operation is relatively expensive. If you only need a shallow result,
// use IsMarked instead.
func (val Value) ContainsMarked() bool {
ret := false
Walk(val, func(_ Path, v Value) (bool, error) {
if v.IsMarked() {
ret = true
return false, nil
}
return true, nil
})
return ret
}
func (val Value) assertUnmarked() {
if val.IsMarked() {
panic("value is marked, so must be unmarked first")
}
}
// Marks returns a map (representing a set) of all of the mark values
// associated with the receiving value, without changing the marks. Returns nil
// if the value is not marked at all.
func (val Value) Marks() ValueMarks {
if mr, ok := val.v.(marker); ok {
// copy so that the caller can't mutate our internals
ret := make(ValueMarks, len(mr.marks))
for k, v := range mr.marks {
ret[k] = v
}
return ret
}
return nil
}
// HasSameMarks returns true if an only if the receiver and the given other
// value have identical marks.
func (val Value) HasSameMarks(other Value) bool {
vm, vmOK := val.v.(marker)
om, omOK := other.v.(marker)
if vmOK != omOK {
return false
}
if vmOK {
return vm.marks.Equal(om.marks)
}
return true
}
// Mark returns a new value that as the same type and underlying value as
// the receiver but that also carries the given value as a "mark".
//
// Marks are used to carry additional application-specific characteristics
// associated with values. A marked value can be used with operation methods,
// in which case the marks are propagated to the operation results. A marked
// value _cannot_ be used with integration methods, so callers of those
// must derive an unmarked value using Unmark (and thus explicitly handle
// the markings) before calling the integration methods.
//
// The mark value can be any value that would be valid to use as a map key.
// The mark value should be of a named type in order to use the type itself
// as a namespace for markings. That type can be unexported if desired, in
// order to ensure that the mark can only be handled through the defining
// package's own functions.
//
// An application that never calls this method does not need to worry about
// handling marked values.
func (val Value) Mark(mark interface{}) Value {
if _, ok := mark.(ValueMarks); ok {
panic("cannot call Value.Mark with a ValueMarks value (use WithMarks instead)")
}
var newMarker marker
newMarker.realV = val.v
if mr, ok := val.v.(marker); ok {
// It's already a marker, so we'll retain existing marks.
newMarker.marks = make(ValueMarks, len(mr.marks)+1)
for k, v := range mr.marks {
newMarker.marks[k] = v
}
// unwrap the inner marked value, so we don't get multiple layers
// of marking.
newMarker.realV = mr.realV
} else {
// It's not a marker yet, so we're creating the first mark.
newMarker.marks = make(ValueMarks, 1)
}
newMarker.marks[mark] = struct{}{}
return Value{
ty: val.ty,
v: newMarker,
}
}
type applyPathValueMarksTransformer struct {
pvm []PathValueMarks
}
func (t *applyPathValueMarksTransformer) Enter(p Path, v Value) (Value, error) {
return v, nil
}
func (t *applyPathValueMarksTransformer) Exit(p Path, v Value) (Value, error) {
for _, path := range t.pvm {
if p.Equals(path.Path) {
return v.WithMarks(path.Marks), nil
}
}
return v, nil
}
// MarkWithPaths accepts a slice of PathValueMarks to apply
// markers to particular paths and returns the marked
// Value.
func (val Value) MarkWithPaths(pvm []PathValueMarks) Value {
ret, _ := TransformWithTransformer(val, &applyPathValueMarksTransformer{pvm})
return ret
}
// Unmark separates the marks of the receiving value from the value itself,
// removing a new unmarked value and a map (representing a set) of the marks.
//
// If the receiver isn't marked, Unmark returns it verbatim along with a nil
// map of marks.
func (val Value) Unmark() (Value, ValueMarks) {
if !val.IsMarked() {
return val, nil
}
mr := val.v.(marker)
marks := val.Marks() // copy so that the caller can't mutate our internals
return Value{
ty: val.ty,
v: mr.realV,
}, marks
}
type unmarkTransformer struct {
pvm []PathValueMarks
}
func (t *unmarkTransformer) Enter(p Path, v Value) (Value, error) {
unmarkedVal, marks := v.Unmark()
if len(marks) > 0 {
path := make(Path, len(p), len(p)+1)
copy(path, p)
t.pvm = append(t.pvm, PathValueMarks{path, marks})
}
return unmarkedVal, nil
}
func (t *unmarkTransformer) Exit(p Path, v Value) (Value, error) {
return v, nil
}
// UnmarkDeep is similar to Unmark, but it works with an entire nested structure
// rather than just the given value directly.
//
// The result is guaranteed to contain no nested values that are marked, and
// the returned marks set includes the superset of all of the marks encountered
// during the operation.
func (val Value) UnmarkDeep() (Value, ValueMarks) {
t := unmarkTransformer{}
ret, _ := TransformWithTransformer(val, &t)
marks := make(ValueMarks)
for _, pvm := range t.pvm {
for m, s := range pvm.Marks {
marks[m] = s
}
}
return ret, marks
}
// UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice
// of PathValueMarks rather than a superset of all marks. This allows
// a caller to know which marks are associated with which paths
// in the Value.
func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) {
t := unmarkTransformer{}
ret, _ := TransformWithTransformer(val, &t)
return ret, t.pvm
}
func (val Value) unmarkForce() Value {
unw, _ := val.Unmark()
return unw
}
// WithMarks returns a new value that has the same type and underlying value
// as the receiver and also has the marks from the given maps (representing
// sets).
func (val Value) WithMarks(marks ...ValueMarks) Value {
if len(marks) == 0 {
return val
}
ownMarks := val.Marks()
markCount := len(ownMarks)
for _, s := range marks {
markCount += len(s)
}
if markCount == 0 {
return val
}
newMarks := make(ValueMarks, markCount)
for m := range ownMarks {
newMarks[m] = struct{}{}
}
for _, s := range marks {
for m := range s {
newMarks[m] = struct{}{}
}
}
v := val.v
if mr, ok := v.(marker); ok {
v = mr.realV
}
return Value{
ty: val.ty,
v: marker{
realV: v,
marks: newMarks,
},
}
}
// WithSameMarks returns a new value that has the same type and underlying
// value as the receiver and also has the marks from the given source values.
//
// Use this if you are implementing your own higher-level operations against
// cty using the integration methods, to re-introduce the marks from the
// source values of the operation.
func (val Value) WithSameMarks(srcs ...Value) Value {
if len(srcs) == 0 {
return val
}
ownMarks := val.Marks()
markCount := len(ownMarks)
for _, sv := range srcs {
if mr, ok := sv.v.(marker); ok {
markCount += len(mr.marks)
}
}
if markCount == 0 {
return val
}
newMarks := make(ValueMarks, markCount)
for m := range ownMarks {
newMarks[m] = struct{}{}
}
for _, sv := range srcs {
if mr, ok := sv.v.(marker); ok {
for m := range mr.marks {
newMarks[m] = struct{}{}
}
}
}
v := val.v
if mr, ok := v.(marker); ok {
v = mr.realV
}
return Value{
ty: val.ty,
v: marker{
realV: v,
marks: newMarks,
},
}
}