345 lines
9.0 KiB
Go
345 lines
9.0 KiB
Go
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.
|
|
func NewValueMarks(marks ...interface{}) ValueMarks {
|
|
if len(marks) == 0 {
|
|
return nil
|
|
}
|
|
ret := make(ValueMarks, len(marks))
|
|
for _, v := range marks {
|
|
ret[v] = struct{}{}
|
|
}
|
|
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 {
|
|
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
|
|
}
|
|
} 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,
|
|
}
|
|
}
|
|
|
|
// 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, _ := Transform(val, func(p Path, v Value) (Value, error) {
|
|
for _, path := range pvm {
|
|
if p.Equals(path.Path) {
|
|
return v.WithMarks(path.Marks), nil
|
|
}
|
|
}
|
|
return v, nil
|
|
})
|
|
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
|
|
}
|
|
|
|
// 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) {
|
|
marks := make(ValueMarks)
|
|
ret, _ := Transform(val, func(_ Path, v Value) (Value, error) {
|
|
unmarkedV, valueMarks := v.Unmark()
|
|
for m, s := range valueMarks {
|
|
marks[m] = s
|
|
}
|
|
return unmarkedV, nil
|
|
})
|
|
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) {
|
|
var marks []PathValueMarks
|
|
ret, _ := Transform(val, func(p Path, v Value) (Value, error) {
|
|
unmarkedV, valueMarks := v.Unmark()
|
|
if v.IsMarked() {
|
|
marks = append(marks, PathValueMarks{p, valueMarks})
|
|
}
|
|
return unmarkedV, nil
|
|
})
|
|
return ret, marks
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
}
|