7 "github.com/zclconf/go-cty/cty/set"
10 // PathSet represents a set of Path objects. This can be used, for example,
11 // to talk about a subset of paths within a value that meet some criteria,
12 // without directly modifying the values at those paths.
17 // NewPathSet creates and returns a PathSet, with initial contents optionally
18 // set by the given arguments.
19 func NewPathSet(paths ...Path) PathSet {
21 set: set.NewSet(pathSetRules{}),
24 for _, path := range paths {
31 // Add inserts a single given path into the set.
33 // Paths are immutable after construction by convention. It is particularly
34 // important not to mutate a path after it has been placed into a PathSet.
35 // If a Path is mutated while in a set, behavior is undefined.
36 func (s PathSet) Add(path Path) {
40 // AddAllSteps is like Add but it also adds all of the steps leading to
43 // For example, if given a path representing "foo.bar", it will add both
45 func (s PathSet) AddAllSteps(path Path) {
46 for i := 1; i <= len(path); i++ {
51 // Has returns true if the given path is in the receiving set.
52 func (s PathSet) Has(path Path) bool {
53 return s.set.Has(path)
56 // List makes and returns a slice of all of the paths in the receiving set,
57 // in an undefined but consistent order.
58 func (s PathSet) List() []Path {
62 ret := make([]Path, 0, s.set.Length())
63 for it := s.set.Iterator(); it.Next(); {
64 ret = append(ret, it.Value().(Path))
69 // Remove modifies the receving set to no longer include the given path.
70 // If the given path was already absent, this is a no-op.
71 func (s PathSet) Remove(path Path) {
75 // Empty returns true if the length of the receiving set is zero.
76 func (s PathSet) Empty() bool {
77 return s.set.Length() == 0
80 // Union returns a new set whose contents are the union of the receiver and
81 // the given other set.
82 func (s PathSet) Union(other PathSet) PathSet {
84 set: s.set.Union(other.set),
88 // Intersection returns a new set whose contents are the intersection of the
89 // receiver and the given other set.
90 func (s PathSet) Intersection(other PathSet) PathSet {
92 set: s.set.Intersection(other.set),
96 // Subtract returns a new set whose contents are those from the receiver with
97 // any elements of the other given set subtracted.
98 func (s PathSet) Subtract(other PathSet) PathSet {
100 set: s.set.Subtract(other.set),
104 // SymmetricDifference returns a new set whose contents are the symmetric
105 // difference of the receiver and the given other set.
106 func (s PathSet) SymmetricDifference(other PathSet) PathSet {
108 set: s.set.SymmetricDifference(other.set),
112 // Equal returns true if and only if both the receiver and the given other
113 // set contain exactly the same paths.
114 func (s PathSet) Equal(other PathSet) bool {
115 if s.set.Length() != other.set.Length() {
118 // Now we know the lengths are the same we only need to test in one
119 // direction whether everything in one is in the other.
120 for it := s.set.Iterator(); it.Next(); {
121 if !other.set.Has(it.Value()) {
128 var crc64Table = crc64.MakeTable(crc64.ISO)
130 var indexStepPlaceholder = []byte("#")
132 // pathSetRules is an implementation of set.Rules from the set package,
133 // used internally within PathSet.
134 type pathSetRules struct {
137 func (r pathSetRules) Hash(v interface{}) int {
139 hash := crc64.New(crc64Table)
141 for _, rawStep := range path {
142 switch step := rawStep.(type) {
144 // (this creates some garbage converting the string name to a
145 // []byte, but that's okay since cty is not designed to be
146 // used in tight loops under memory pressure.)
147 hash.Write([]byte(step.Name))
149 // For any other step type we just append a predefined value,
150 // which means that e.g. all indexes into a given collection will
151 // hash to the same value but we assume that collections are
152 // small and thus this won't hurt too much.
153 hash.Write(indexStepPlaceholder)
157 // We discard half of the hash on 32-bit platforms; collisions just make
158 // our lookups take marginally longer, so not a big deal.
159 return int(hash.Sum64())
162 func (r pathSetRules) Equivalent(a, b interface{}) bool {
166 if len(aPath) != len(bPath) {
170 for i := range aPath {
171 switch aStep := aPath[i].(type) {
173 bStep, ok := bPath[i].(GetAttrStep)
178 if aStep.Name != bStep.Name {
182 bStep, ok := bPath[i].(IndexStep)
187 eq := aStep.Key.Equals(bStep.Key)
188 if !eq.IsKnown() || eq.False() {
192 // Should never happen, since we document PathStep as a closed type.
193 panic(fmt.Errorf("unsupported step type %T", aStep))