Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions fieldpath/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package fieldpath

import (
"fmt"
"iter"
"sort"
"strings"

Expand Down Expand Up @@ -47,6 +48,36 @@ type PathElement struct {
Index *int
}

// FieldNameElement creates a new FieldName PathElement.
func FieldNameElement(name string) PathElement {
return PathElement{FieldName: &name}
}

// KeyElement creates a new Key PathElement with the key fields.
func KeyElement(fields ...value.Field) PathElement {
l := value.FieldList(fields)
return PathElement{Key: &l}
}

// KeyElementByFields creates a new Key PathElement from names and values.
// `nameValues` must have an even number of entries, alternating
// names (type must be string) with values (type must be value.Value). If these
// conditions are not met, KeyByFields will panic--it's intended for static
// construction and shouldn't have user-produced values passed to it.
func KeyElementByFields(nameValues ...any) PathElement {
return PathElement{Key: KeyByFields(nameValues...)}
}

// ValueElement creates a new Value PathElement.
func ValueElement(value value.Value) PathElement {
return PathElement{Value: &value}
}

// IndexElement creates a new Index PathElement.
func IndexElement(index int) PathElement {
return PathElement{Index: &index}
}

// Less provides an order for path elements.
func (e PathElement) Less(rhs PathElement) bool {
return e.Compare(rhs) < 0
Expand Down Expand Up @@ -156,6 +187,25 @@ func (e PathElement) String() string {
}
}

// Copy returns a copy of the PathElement.
// This is not a full deep copy as any contained value.Value is not copied.
func (e PathElement) Copy() PathElement {
if e.FieldName != nil {
return PathElement{FieldName: e.FieldName}
}
if e.Key != nil {
c := e.Key.Copy()
return PathElement{Key: &c}
}
if e.Value != nil {
return PathElement{Value: e.Value}
}
if e.Index != nil {
return PathElement{Index: e.Index}
}
return e // zero value
}

// KeyByFields is a helper function which constructs a key for an associative
// list type. `nameValues` must have an even number of entries, alternating
// names (type must be string) with values (type must be value.Value). If these
Expand Down Expand Up @@ -193,6 +243,16 @@ func (spe sortedPathElements) Len() int { return len(spe) }
func (spe sortedPathElements) Less(i, j int) bool { return spe[i].Less(spe[j]) }
func (spe sortedPathElements) Swap(i, j int) { spe[i], spe[j] = spe[j], spe[i] }

// Copy returns a copy of the PathElementSet.
// This is not a full deep copy as any contained value.Value is not copied.
func (s PathElementSet) Copy() PathElementSet {
out := make(sortedPathElements, len(s.members))
for i := range s.members {
out[i] = s.members[i].Copy()
}
return PathElementSet{members: out}
}

// Insert adds pe to the set.
func (s *PathElementSet) Insert(pe PathElement) {
loc := sort.Search(len(s.members), func(i int) bool {
Expand Down Expand Up @@ -315,3 +375,14 @@ func (s *PathElementSet) Iterate(f func(PathElement)) {
f(pe)
}
}

// All iterates over each PathElement in the set. The order is deterministic.
func (s *PathElementSet) All() iter.Seq[PathElement] {
return func(yield func(element PathElement) bool) {
for _, pe := range s.members {
if !yield(pe) {
return
}
}
}
}
85 changes: 53 additions & 32 deletions fieldpath/element_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func TestPathElementSet(t *testing.T) {
if !s2.Has(PathElement{}) {
t.Errorf("expected to have something: %#v", s2)
}
c2 := s2.Copy()
if !c2.Equals(s2) {
t.Errorf("expected copy to equal original: %#v, %#v", s2, c2)
}

n1 := "aoeu"
n2 := "asdf"
Expand Down Expand Up @@ -60,6 +64,20 @@ func TestPathElementSet(t *testing.T) {
}
i++
})
i = 0
for pe := range s2.All() {
e, a := expected[i], pe.FieldName
if e == nil || a == nil {
if e != a {
t.Errorf("index %v wanted %#v, got %#v", i, e, a)
}
} else {
if *e != *a {
t.Errorf("index %v wanted %#v, got %#v", i, *e, *a)
}
}
i++
}
}

func strptr(s string) *string { return &s }
Expand All @@ -68,6 +86,9 @@ func valptr(i interface{}) *value.Value {
v := value.NewValueInterface(i)
return &v
}
func val(i interface{}) value.Value {
return value.NewValueInterface(i)
}

func TestPathElementLess(t *testing.T) {
table := []struct {
Expand All @@ -84,71 +105,71 @@ func TestPathElementLess(t *testing.T) {
eq: true,
}, {
name: "FieldName-1",
a: PathElement{FieldName: strptr("anteater")},
b: PathElement{FieldName: strptr("zebra")},
a: FieldNameElement("anteater"),
b: FieldNameElement("zebra"),
}, {
name: "FieldName-2",
a: PathElement{FieldName: strptr("bee")},
b: PathElement{FieldName: strptr("bee")},
a: FieldNameElement("bee"),
b: FieldNameElement("bee"),
eq: true,
}, {
name: "FieldName-3",
a: PathElement{FieldName: strptr("capybara")},
b: PathElement{Key: KeyByFields("dog", 3)},
a: FieldNameElement("capybara"),
b: KeyElementByFields("dog", 3),
}, {
name: "FieldName-4",
a: PathElement{FieldName: strptr("elephant")},
b: PathElement{Value: valptr(4)},
a: FieldNameElement("elephant"),
b: ValueElement(val(4)),
}, {
name: "FieldName-5",
a: PathElement{FieldName: strptr("falcon")},
b: PathElement{Index: intptr(5)},
a: FieldNameElement("falcon"),
b: IndexElement(5),
}, {
name: "Key-1",
a: PathElement{Key: KeyByFields("goat", 1)},
b: PathElement{Key: KeyByFields("goat", 1)},
a: KeyElementByFields("goat", 1),
b: KeyElementByFields("goat", 1),
eq: true,
}, {
name: "Key-2",
a: PathElement{Key: KeyByFields("horse", 1)},
b: PathElement{Key: KeyByFields("horse", 2)},
a: KeyElementByFields("horse", 1),
b: KeyElementByFields("horse", 2),
}, {
name: "Key-3",
a: PathElement{Key: KeyByFields("ibex", 1)},
b: PathElement{Key: KeyByFields("jay", 1)},
a: KeyElementByFields("ibex", 1),
b: KeyElementByFields("jay", 1),
}, {
name: "Key-4",
a: PathElement{Key: KeyByFields("kite", 1)},
b: PathElement{Key: KeyByFields("kite", 1, "kite-2", 1)},
a: KeyElementByFields("kite", 1),
b: KeyElementByFields("kite", 1, "kite-2", 1),
}, {
name: "Key-5",
a: PathElement{Key: KeyByFields("kite", 1)},
b: PathElement{Value: valptr(1)},
a: KeyElementByFields("kite", 1),
b: ValueElement(val(1)),
}, {
name: "Key-6",
a: PathElement{Key: KeyByFields("kite", 1)},
b: PathElement{Index: intptr(5)},
a: KeyElementByFields("kite", 1),
b: IndexElement(5),
}, {
name: "Value-1",
a: PathElement{Value: valptr(1)},
b: PathElement{Value: valptr(2)},
a: ValueElement(val(1)),
b: ValueElement(val(2)),
}, {
name: "Value-2",
a: PathElement{Value: valptr(1)},
b: PathElement{Value: valptr(1)},
a: ValueElement(val(1)),
b: ValueElement(val(1)),
eq: true,
}, {
name: "Value-3",
a: PathElement{Value: valptr(1)},
b: PathElement{Index: intptr(1)},
a: ValueElement(val(1)),
b: IndexElement(1),
}, {
name: "Index-1",
a: PathElement{Index: intptr(1)},
b: PathElement{Index: intptr(2)},
a: IndexElement(1),
b: IndexElement(2),
}, {
name: "Index-2",
a: PathElement{Index: intptr(1)},
b: PathElement{Index: intptr(1)},
a: IndexElement(1),
b: IndexElement(1),
eq: true,
},
}
Expand Down
38 changes: 38 additions & 0 deletions fieldpath/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package fieldpath

import (
"fmt"
"iter"
"sort"
"strings"

Expand Down Expand Up @@ -47,6 +48,15 @@ func NewSet(paths ...Path) *Set {
return s
}

// Copy returns a copy of the Set.
// This is not a full deep copy as any contained value.Value is not copied.
func (s *Set) Copy() *Set {
return &Set{
Members: s.Members.Copy(),
Children: s.Children.Copy(),
}
}

// Insert adds the field identified by `p` to the set. Important: parent fields
// are NOT added to the set; if that is desired, they must be added separately.
func (s *Set) Insert(p Path) {
Expand Down Expand Up @@ -386,6 +396,15 @@ func (s *Set) Iterate(f func(Path)) {
s.iteratePrefix(Path{}, f)
}

// All iterates over each Path in the set (preorder DFS).
func (s *Set) All() iter.Seq[Path] {
return func(yield func(Path) bool) {
s.Iterate(func(p Path) {
yield(p)
})
}
}

func (s *Set) iteratePrefix(prefix Path, f func(Path)) {
s.Members.Iterate(func(pe PathElement) { f(append(prefix, pe)) })
s.Children.iteratePrefix(prefix, f)
Expand Down Expand Up @@ -455,6 +474,16 @@ func (s sortedSetNode) Len() int { return len(s) }
func (s sortedSetNode) Less(i, j int) bool { return s[i].pathElement.Less(s[j].pathElement) }
func (s sortedSetNode) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

// Copy returns a copy of the SetNodeMap.
// This is not a full deep copy as any contained value.Value is not copied.
func (s *SetNodeMap) Copy() SetNodeMap {
out := make(sortedSetNode, len(s.members))
for i, v := range s.members {
out[i] = setNode{pathElement: v.pathElement.Copy(), set: v.set.Copy()}
}
return SetNodeMap{members: out}
}

// Descend adds pe to the set if necessary, returning the associated subset.
func (s *SetNodeMap) Descend(pe PathElement) *Set {
loc := sort.Search(len(s.members), func(i int) bool {
Expand Down Expand Up @@ -705,6 +734,15 @@ func (s *SetNodeMap) Iterate(f func(PathElement)) {
}
}

// All iterates over each PathElement in the set.
func (s *SetNodeMap) All() iter.Seq[PathElement] {
return func(yield func(PathElement) bool) {
s.Iterate(func(pe PathElement) {
yield(pe)
})
}
}

func (s *SetNodeMap) iteratePrefix(prefix Path, f func(Path)) {
for _, n := range s.members {
pe := n.pathElement
Expand Down
27 changes: 26 additions & 1 deletion fieldpath/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ func TestSetIterSize(t *testing.T) {
)

s2 := NewSet()

addedCount := 0
s1.Iterate(func(p Path) {
if s2.Size() != addedCount {
Expand All @@ -246,6 +245,19 @@ func TestSetIterSize(t *testing.T) {
addedCount++
})

s2 = NewSet()
addedCount = 0
for p := range s1.All() {
if s2.Size() != addedCount {
t.Errorf("added %v items to set, but size is %v", addedCount, s2.Size())
}
if addedCount > 0 == s2.Empty() {
t.Errorf("added %v items to set, but s2.Empty() is %v", addedCount, s2.Empty())
}
s2.Insert(p)
addedCount++
}

if !s1.Equals(s2) {
// No point in using String() if iterate is broken...
t.Errorf("Iterate missed something?\n%#v\n%#v", s1, s2)
Expand Down Expand Up @@ -756,6 +768,19 @@ func TestSetNodeMapIterate(t *testing.T) {
t.Errorf("expected to have iterated over %v, but never did", pe)
}
}

iteratedElements = make(map[string]bool, toAdd)
for pe := range set.All() {
iteratedElements[pe.String()] = true
}
if len(iteratedElements) != toAdd {
t.Errorf("expected %v elements to be iterated over, got %v", toAdd, len(iteratedElements))
}
for _, pe := range addedElements {
if _, ok := iteratedElements[pe]; !ok {
t.Errorf("expected to have iterated over %v, but never did", pe)
}
}
}

func TestFilterByPattern(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
)

go 1.19
go 1.23
8 changes: 8 additions & 0 deletions value/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ type Field struct {
// have a different name.
type FieldList []Field

// Copy returns a copy of the FieldList.
// Values are not copied.
func (f FieldList) Copy() FieldList {
c := make(FieldList, len(f))
copy(c, f)
return c
}

// Sort sorts the field list by Name.
func (f FieldList) Sort() {
if len(f) < 2 {
Expand Down