diff --git a/fieldpath/element.go b/fieldpath/element.go index 90a48b95..73436912 100644 --- a/fieldpath/element.go +++ b/fieldpath/element.go @@ -18,6 +18,7 @@ package fieldpath import ( "fmt" + "iter" "sort" "strings" @@ -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 @@ -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 @@ -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 { @@ -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 + } + } + } +} diff --git a/fieldpath/element_test.go b/fieldpath/element_test.go index ae52c839..aba96b14 100644 --- a/fieldpath/element_test.go +++ b/fieldpath/element_test.go @@ -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" @@ -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 } @@ -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 { @@ -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, }, } diff --git a/fieldpath/set.go b/fieldpath/set.go index 0c285d17..d2d8c8a4 100644 --- a/fieldpath/set.go +++ b/fieldpath/set.go @@ -18,6 +18,7 @@ package fieldpath import ( "fmt" + "iter" "sort" "strings" @@ -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) { @@ -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) @@ -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 { @@ -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 diff --git a/fieldpath/set_test.go b/fieldpath/set_test.go index bb159c9a..3826d4ee 100644 --- a/fieldpath/set_test.go +++ b/fieldpath/set_test.go @@ -233,7 +233,6 @@ func TestSetIterSize(t *testing.T) { ) s2 := NewSet() - addedCount := 0 s1.Iterate(func(p Path) { if s2.Size() != addedCount { @@ -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) @@ -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) { diff --git a/go.mod b/go.mod index 12c3c539..c29a9fd3 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,4 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect ) -go 1.19 +go 1.23 diff --git a/value/fields.go b/value/fields.go index be3c6724..042b0487 100644 --- a/value/fields.go +++ b/value/fields.go @@ -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 {