Skip to content

Commit 379e2f9

Browse files
authored
Merge pull request #291 from jpbetz/fieldpath-utils
Add copy and iterator functions to fieldpath types
2 parents e8775fe + 1a373a9 commit 379e2f9

File tree

6 files changed

+197
-34
lines changed

6 files changed

+197
-34
lines changed

fieldpath/element.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package fieldpath
1818

1919
import (
2020
"fmt"
21+
"iter"
2122
"sort"
2223
"strings"
2324

@@ -47,6 +48,36 @@ type PathElement struct {
4748
Index *int
4849
}
4950

51+
// FieldNameElement creates a new FieldName PathElement.
52+
func FieldNameElement(name string) PathElement {
53+
return PathElement{FieldName: &name}
54+
}
55+
56+
// KeyElement creates a new Key PathElement with the key fields.
57+
func KeyElement(fields ...value.Field) PathElement {
58+
l := value.FieldList(fields)
59+
return PathElement{Key: &l}
60+
}
61+
62+
// KeyElementByFields creates a new Key PathElement from names and values.
63+
// `nameValues` must have an even number of entries, alternating
64+
// names (type must be string) with values (type must be value.Value). If these
65+
// conditions are not met, KeyByFields will panic--it's intended for static
66+
// construction and shouldn't have user-produced values passed to it.
67+
func KeyElementByFields(nameValues ...any) PathElement {
68+
return PathElement{Key: KeyByFields(nameValues...)}
69+
}
70+
71+
// ValueElement creates a new Value PathElement.
72+
func ValueElement(value value.Value) PathElement {
73+
return PathElement{Value: &value}
74+
}
75+
76+
// IndexElement creates a new Index PathElement.
77+
func IndexElement(index int) PathElement {
78+
return PathElement{Index: &index}
79+
}
80+
5081
// Less provides an order for path elements.
5182
func (e PathElement) Less(rhs PathElement) bool {
5283
return e.Compare(rhs) < 0
@@ -156,6 +187,25 @@ func (e PathElement) String() string {
156187
}
157188
}
158189

190+
// Copy returns a copy of the PathElement.
191+
// This is not a full deep copy as any contained value.Value is not copied.
192+
func (e PathElement) Copy() PathElement {
193+
if e.FieldName != nil {
194+
return PathElement{FieldName: e.FieldName}
195+
}
196+
if e.Key != nil {
197+
c := e.Key.Copy()
198+
return PathElement{Key: &c}
199+
}
200+
if e.Value != nil {
201+
return PathElement{Value: e.Value}
202+
}
203+
if e.Index != nil {
204+
return PathElement{Index: e.Index}
205+
}
206+
return e // zero value
207+
}
208+
159209
// KeyByFields is a helper function which constructs a key for an associative
160210
// list type. `nameValues` must have an even number of entries, alternating
161211
// names (type must be string) with values (type must be value.Value). If these
@@ -193,6 +243,16 @@ func (spe sortedPathElements) Len() int { return len(spe) }
193243
func (spe sortedPathElements) Less(i, j int) bool { return spe[i].Less(spe[j]) }
194244
func (spe sortedPathElements) Swap(i, j int) { spe[i], spe[j] = spe[j], spe[i] }
195245

246+
// Copy returns a copy of the PathElementSet.
247+
// This is not a full deep copy as any contained value.Value is not copied.
248+
func (s PathElementSet) Copy() PathElementSet {
249+
out := make(sortedPathElements, len(s.members))
250+
for i := range s.members {
251+
out[i] = s.members[i].Copy()
252+
}
253+
return PathElementSet{members: out}
254+
}
255+
196256
// Insert adds pe to the set.
197257
func (s *PathElementSet) Insert(pe PathElement) {
198258
loc := sort.Search(len(s.members), func(i int) bool {
@@ -315,3 +375,14 @@ func (s *PathElementSet) Iterate(f func(PathElement)) {
315375
f(pe)
316376
}
317377
}
378+
379+
// All iterates over each PathElement in the set. The order is deterministic.
380+
func (s *PathElementSet) All() iter.Seq[PathElement] {
381+
return func(yield func(element PathElement) bool) {
382+
for _, pe := range s.members {
383+
if !yield(pe) {
384+
return
385+
}
386+
}
387+
}
388+
}

fieldpath/element_test.go

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ func TestPathElementSet(t *testing.T) {
3333
if !s2.Has(PathElement{}) {
3434
t.Errorf("expected to have something: %#v", s2)
3535
}
36+
c2 := s2.Copy()
37+
if !c2.Equals(s2) {
38+
t.Errorf("expected copy to equal original: %#v, %#v", s2, c2)
39+
}
3640

3741
n1 := "aoeu"
3842
n2 := "asdf"
@@ -60,6 +64,20 @@ func TestPathElementSet(t *testing.T) {
6064
}
6165
i++
6266
})
67+
i = 0
68+
for pe := range s2.All() {
69+
e, a := expected[i], pe.FieldName
70+
if e == nil || a == nil {
71+
if e != a {
72+
t.Errorf("index %v wanted %#v, got %#v", i, e, a)
73+
}
74+
} else {
75+
if *e != *a {
76+
t.Errorf("index %v wanted %#v, got %#v", i, *e, *a)
77+
}
78+
}
79+
i++
80+
}
6381
}
6482

6583
func strptr(s string) *string { return &s }
@@ -68,6 +86,9 @@ func valptr(i interface{}) *value.Value {
6886
v := value.NewValueInterface(i)
6987
return &v
7088
}
89+
func val(i interface{}) value.Value {
90+
return value.NewValueInterface(i)
91+
}
7192

7293
func TestPathElementLess(t *testing.T) {
7394
table := []struct {
@@ -84,71 +105,71 @@ func TestPathElementLess(t *testing.T) {
84105
eq: true,
85106
}, {
86107
name: "FieldName-1",
87-
a: PathElement{FieldName: strptr("anteater")},
88-
b: PathElement{FieldName: strptr("zebra")},
108+
a: FieldNameElement("anteater"),
109+
b: FieldNameElement("zebra"),
89110
}, {
90111
name: "FieldName-2",
91-
a: PathElement{FieldName: strptr("bee")},
92-
b: PathElement{FieldName: strptr("bee")},
112+
a: FieldNameElement("bee"),
113+
b: FieldNameElement("bee"),
93114
eq: true,
94115
}, {
95116
name: "FieldName-3",
96-
a: PathElement{FieldName: strptr("capybara")},
97-
b: PathElement{Key: KeyByFields("dog", 3)},
117+
a: FieldNameElement("capybara"),
118+
b: KeyElementByFields("dog", 3),
98119
}, {
99120
name: "FieldName-4",
100-
a: PathElement{FieldName: strptr("elephant")},
101-
b: PathElement{Value: valptr(4)},
121+
a: FieldNameElement("elephant"),
122+
b: ValueElement(val(4)),
102123
}, {
103124
name: "FieldName-5",
104-
a: PathElement{FieldName: strptr("falcon")},
105-
b: PathElement{Index: intptr(5)},
125+
a: FieldNameElement("falcon"),
126+
b: IndexElement(5),
106127
}, {
107128
name: "Key-1",
108-
a: PathElement{Key: KeyByFields("goat", 1)},
109-
b: PathElement{Key: KeyByFields("goat", 1)},
129+
a: KeyElementByFields("goat", 1),
130+
b: KeyElementByFields("goat", 1),
110131
eq: true,
111132
}, {
112133
name: "Key-2",
113-
a: PathElement{Key: KeyByFields("horse", 1)},
114-
b: PathElement{Key: KeyByFields("horse", 2)},
134+
a: KeyElementByFields("horse", 1),
135+
b: KeyElementByFields("horse", 2),
115136
}, {
116137
name: "Key-3",
117-
a: PathElement{Key: KeyByFields("ibex", 1)},
118-
b: PathElement{Key: KeyByFields("jay", 1)},
138+
a: KeyElementByFields("ibex", 1),
139+
b: KeyElementByFields("jay", 1),
119140
}, {
120141
name: "Key-4",
121-
a: PathElement{Key: KeyByFields("kite", 1)},
122-
b: PathElement{Key: KeyByFields("kite", 1, "kite-2", 1)},
142+
a: KeyElementByFields("kite", 1),
143+
b: KeyElementByFields("kite", 1, "kite-2", 1),
123144
}, {
124145
name: "Key-5",
125-
a: PathElement{Key: KeyByFields("kite", 1)},
126-
b: PathElement{Value: valptr(1)},
146+
a: KeyElementByFields("kite", 1),
147+
b: ValueElement(val(1)),
127148
}, {
128149
name: "Key-6",
129-
a: PathElement{Key: KeyByFields("kite", 1)},
130-
b: PathElement{Index: intptr(5)},
150+
a: KeyElementByFields("kite", 1),
151+
b: IndexElement(5),
131152
}, {
132153
name: "Value-1",
133-
a: PathElement{Value: valptr(1)},
134-
b: PathElement{Value: valptr(2)},
154+
a: ValueElement(val(1)),
155+
b: ValueElement(val(2)),
135156
}, {
136157
name: "Value-2",
137-
a: PathElement{Value: valptr(1)},
138-
b: PathElement{Value: valptr(1)},
158+
a: ValueElement(val(1)),
159+
b: ValueElement(val(1)),
139160
eq: true,
140161
}, {
141162
name: "Value-3",
142-
a: PathElement{Value: valptr(1)},
143-
b: PathElement{Index: intptr(1)},
163+
a: ValueElement(val(1)),
164+
b: IndexElement(1),
144165
}, {
145166
name: "Index-1",
146-
a: PathElement{Index: intptr(1)},
147-
b: PathElement{Index: intptr(2)},
167+
a: IndexElement(1),
168+
b: IndexElement(2),
148169
}, {
149170
name: "Index-2",
150-
a: PathElement{Index: intptr(1)},
151-
b: PathElement{Index: intptr(1)},
171+
a: IndexElement(1),
172+
b: IndexElement(1),
152173
eq: true,
153174
},
154175
}

fieldpath/set.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package fieldpath
1818

1919
import (
2020
"fmt"
21+
"iter"
2122
"sort"
2223
"strings"
2324

@@ -47,6 +48,15 @@ func NewSet(paths ...Path) *Set {
4748
return s
4849
}
4950

51+
// Copy returns a copy of the Set.
52+
// This is not a full deep copy as any contained value.Value is not copied.
53+
func (s *Set) Copy() *Set {
54+
return &Set{
55+
Members: s.Members.Copy(),
56+
Children: s.Children.Copy(),
57+
}
58+
}
59+
5060
// Insert adds the field identified by `p` to the set. Important: parent fields
5161
// are NOT added to the set; if that is desired, they must be added separately.
5262
func (s *Set) Insert(p Path) {
@@ -386,6 +396,15 @@ func (s *Set) Iterate(f func(Path)) {
386396
s.iteratePrefix(Path{}, f)
387397
}
388398

399+
// All iterates over each Path in the set (preorder DFS).
400+
func (s *Set) All() iter.Seq[Path] {
401+
return func(yield func(Path) bool) {
402+
s.Iterate(func(p Path) {
403+
yield(p)
404+
})
405+
}
406+
}
407+
389408
func (s *Set) iteratePrefix(prefix Path, f func(Path)) {
390409
s.Members.Iterate(func(pe PathElement) { f(append(prefix, pe)) })
391410
s.Children.iteratePrefix(prefix, f)
@@ -455,6 +474,16 @@ func (s sortedSetNode) Len() int { return len(s) }
455474
func (s sortedSetNode) Less(i, j int) bool { return s[i].pathElement.Less(s[j].pathElement) }
456475
func (s sortedSetNode) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
457476

477+
// Copy returns a copy of the SetNodeMap.
478+
// This is not a full deep copy as any contained value.Value is not copied.
479+
func (s *SetNodeMap) Copy() SetNodeMap {
480+
out := make(sortedSetNode, len(s.members))
481+
for i, v := range s.members {
482+
out[i] = setNode{pathElement: v.pathElement.Copy(), set: v.set.Copy()}
483+
}
484+
return SetNodeMap{members: out}
485+
}
486+
458487
// Descend adds pe to the set if necessary, returning the associated subset.
459488
func (s *SetNodeMap) Descend(pe PathElement) *Set {
460489
loc := sort.Search(len(s.members), func(i int) bool {
@@ -705,6 +734,15 @@ func (s *SetNodeMap) Iterate(f func(PathElement)) {
705734
}
706735
}
707736

737+
// All iterates over each PathElement in the set.
738+
func (s *SetNodeMap) All() iter.Seq[PathElement] {
739+
return func(yield func(PathElement) bool) {
740+
s.Iterate(func(pe PathElement) {
741+
yield(pe)
742+
})
743+
}
744+
}
745+
708746
func (s *SetNodeMap) iteratePrefix(prefix Path, f func(Path)) {
709747
for _, n := range s.members {
710748
pe := n.pathElement

fieldpath/set_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ func TestSetIterSize(t *testing.T) {
233233
)
234234

235235
s2 := NewSet()
236-
237236
addedCount := 0
238237
s1.Iterate(func(p Path) {
239238
if s2.Size() != addedCount {
@@ -246,6 +245,19 @@ func TestSetIterSize(t *testing.T) {
246245
addedCount++
247246
})
248247

248+
s2 = NewSet()
249+
addedCount = 0
250+
for p := range s1.All() {
251+
if s2.Size() != addedCount {
252+
t.Errorf("added %v items to set, but size is %v", addedCount, s2.Size())
253+
}
254+
if addedCount > 0 == s2.Empty() {
255+
t.Errorf("added %v items to set, but s2.Empty() is %v", addedCount, s2.Empty())
256+
}
257+
s2.Insert(p)
258+
addedCount++
259+
}
260+
249261
if !s1.Equals(s2) {
250262
// No point in using String() if iterate is broken...
251263
t.Errorf("Iterate missed something?\n%#v\n%#v", s1, s2)
@@ -756,6 +768,19 @@ func TestSetNodeMapIterate(t *testing.T) {
756768
t.Errorf("expected to have iterated over %v, but never did", pe)
757769
}
758770
}
771+
772+
iteratedElements = make(map[string]bool, toAdd)
773+
for pe := range set.All() {
774+
iteratedElements[pe.String()] = true
775+
}
776+
if len(iteratedElements) != toAdd {
777+
t.Errorf("expected %v elements to be iterated over, got %v", toAdd, len(iteratedElements))
778+
}
779+
for _, pe := range addedElements {
780+
if _, ok := iteratedElements[pe]; !ok {
781+
t.Errorf("expected to have iterated over %v, but never did", pe)
782+
}
783+
}
759784
}
760785

761786
func TestFilterByPattern(t *testing.T) {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ require (
1212
github.com/modern-go/reflect2 v1.0.2 // indirect
1313
)
1414

15-
go 1.19
15+
go 1.23

value/fields.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ type Field struct {
3131
// have a different name.
3232
type FieldList []Field
3333

34+
// Copy returns a copy of the FieldList.
35+
// Values are not copied.
36+
func (f FieldList) Copy() FieldList {
37+
c := make(FieldList, len(f))
38+
copy(c, f)
39+
return c
40+
}
41+
3442
// Sort sorts the field list by Name.
3543
func (f FieldList) Sort() {
3644
if len(f) < 2 {

0 commit comments

Comments
 (0)