Skip to content

Commit

Permalink
Support Indexed and Keyed interfaces when removing nodes (#181)
Browse files Browse the repository at this point in the history
* Add remove method to Indexed interface

* Support Indexed and Keyed interfaces when removing nodes

* Move RemoveValueAtIndex to separate interface
  • Loading branch information
twelvelabs authored Aug 30, 2024
1 parent 19c93df commit e422fbd
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 0 deletions.
2 changes: 2 additions & 0 deletions jp/child.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func (f Child) remove(value any) (out any, changed bool) {
if _, changed = tv[key]; changed {
delete(tv, key)
}
case Keyed:
tv.RemoveValueForKey(key)
default:
if rt := reflect.TypeOf(value); rt != nil {
// Can't remove a field from a struct so only a map can be modified.
Expand Down
39 changes: 39 additions & 0 deletions jp/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ func (f Filter) remove(value any) (out any, changed bool) {
changed = true
}
}
case RemovableIndexed:
size := tv.Size()
for i := (size - 1); i >= 0; i-- {
v := tv.ValueAtIndex(i)
if f.Match(v) {
tv.RemoveValueAtIndex(i)
changed = true
}
}
case Keyed:
keys := tv.Keys()
for _, key := range keys {
v, _ := tv.ValueForKey(key)
if f.Match(v) {
tv.RemoveValueForKey(key)
changed = true
}
}
default:
rv := reflect.ValueOf(value)
switch rv.Kind() {
Expand Down Expand Up @@ -201,6 +219,27 @@ func (f Filter) removeOne(value any) (out any, changed bool) {
}
}
}
case RemovableIndexed:
size := tv.Size()
for i := 0; i < size; i++ {
v := tv.ValueAtIndex(i)
if f.Match(v) {
tv.RemoveValueAtIndex(i)
changed = true
break
}
}
case Keyed:
keys := tv.Keys()
sort.Strings(keys)
for _, key := range keys {
v, _ := tv.ValueForKey(key)
if f.Match(v) {
tv.RemoveValueForKey(key)
changed = true
break
}
}
default:
rv := reflect.ValueOf(value)
switch rv.Kind() {
Expand Down
9 changes: 9 additions & 0 deletions jp/indexed.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ type Indexed interface {
// Size should return the size for the collection.
Size() int
}

// RemovableIndexed describes an indexed collection that can remove items.
// Must be implemented to use [Expr.Remove].
type RemovableIndexed interface {
Indexed

// RemoveValueAtIndex removes an item from the collection.
RemoveValueAtIndex(index int)
}
9 changes: 9 additions & 0 deletions jp/nth.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ func (f Nth) remove(value any) (out any, changed bool) {
out = append(tv[:i], tv[i+1:]...)
changed = true
}
case RemovableIndexed:
size := tv.Size()
if i < 0 {
i = size + i
}
if 0 <= i && i < size {
tv.RemoveValueAtIndex(i)
changed = true
}
default:
if rt := reflect.TypeOf(value); rt != nil {
if rt.Kind() == reflect.Slice {
Expand Down
7 changes: 7 additions & 0 deletions jp/ordered_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ func (o *ordered) SetValueAtIndex(index int, value any) {
}
}

func (o *ordered) RemoveValueAtIndex(index int) {
if 0 <= index && index < len(o.entries) {
copy(o.entries[index:], o.entries[index+1:])
o.entries = o.entries[:len(o.entries)-1]
}
}

type keyed struct {
ordered
}
Expand Down
158 changes: 158 additions & 0 deletions jp/remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,3 +666,161 @@ func TestExprRemoveFilterMap(t *testing.T) {
result = x.MustRemoveOne(data)
tt.Equal(t, "{b: {x: 2} c: {x: 3}}", string(pw.Encode(result)))
}

func TestExprRemoveIndexedFilter(t *testing.T) {
x := jp.MustParseString("$[2][?@ > 11]")

data := indexedData()
result := x.MustRemove(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, false, jp.N(2).N(1).Has(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))

data = indexedData()
result = x.MustRemoveOne(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, 13, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))
}

func TestExprRemoveIndexedNth(t *testing.T) {
x := jp.MustParseString("$[2][1]")
data := indexedData()
result := x.MustRemove(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, 13, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))

x = jp.MustParseString("$[2][-1]")
data = indexedData()
result = x.MustRemove(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, 12, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))
}

func TestExprRemoveIndexedSlice(t *testing.T) {
x := jp.MustParseString("$[2][0:1]") // first two
data := indexedData()
result := x.MustRemove(data)
tt.Equal(t, 13, jp.N(2).N(0).First(result))
tt.Equal(t, false, jp.N(2).N(1).Has(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))
data = indexedData()
result = x.MustRemoveOne(data)
tt.Equal(t, 12, jp.N(2).N(0).First(result))
tt.Equal(t, 13, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))

x = jp.MustParseString("$[2][-2:-1]") // last two
data = indexedData()
result = x.MustRemove(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, false, jp.N(2).N(1).Has(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))
data = indexedData()
result = x.MustRemoveOne(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, 13, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))

x = jp.MustParseString("$[2][-999:999]") // out of bounds
data = indexedData()
result = x.MustRemove(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, 12, jp.N(2).N(1).First(result))
tt.Equal(t, 13, jp.N(2).N(2).First(result))
data = indexedData()
result = x.MustRemoveOne(data)
tt.Equal(t, 11, jp.N(2).N(0).First(result))
tt.Equal(t, 12, jp.N(2).N(1).First(result))
tt.Equal(t, 13, jp.N(2).N(2).First(result))
}

func TestExprRemoveIndexedUnion(t *testing.T) {
x := jp.MustParseString("$[2][0,1]")

data := indexedData()
result := x.MustRemove(data)
tt.Equal(t, 13, jp.N(2).N(0).First(result))
tt.Equal(t, false, jp.N(2).N(1).Has(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))

data = indexedData()
result = x.MustRemoveOne(data)
tt.Equal(t, 12, jp.N(2).N(0).First(result))
tt.Equal(t, 13, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))
}

func TestExprRemoveIndexedWild(t *testing.T) {
x := jp.MustParseString("$[2][*]")

data := indexedData()
result := x.MustRemove(data)
tt.Equal(t, false, jp.N(2).N(0).Has(result))
tt.Equal(t, false, jp.N(2).N(1).Has(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))

data = indexedData()
result = x.MustRemoveOne(data)
tt.Equal(t, 12, jp.N(2).N(0).First(result))
tt.Equal(t, 13, jp.N(2).N(1).First(result))
tt.Equal(t, false, jp.N(2).N(2).Has(result))
}

func TestExprRemoveKeyedChild(t *testing.T) {
x := jp.MustParseString("$.b")

data := keyedData()
result := x.MustRemove(data)
tt.Equal(t, false, jp.C("b").Has(result))
}

func TestExprRemoveKeyedFilter(t *testing.T) {
x := jp.MustParseString("$.c[?@ > 11]")

data := keyedData()
result := x.MustRemove(data)
tt.Equal(t, true, jp.C("c").C("c1").Has(result))
tt.Equal(t, false, jp.C("c").C("c2").Has(result))
tt.Equal(t, false, jp.C("c").C("c3").Has(result))

data = keyedData()
result = x.MustRemoveOne(data)
tt.Equal(t, true, jp.C("c").C("c1").Has(result))
tt.Equal(t, false, jp.C("c").C("c2").Has(result))
tt.Equal(t, true, jp.C("c").C("c3").Has(result))
}

func TestExprRemoveKeyedUnion(t *testing.T) {
x := jp.MustParseString("$.c['c1', 'c2']")

data := keyedData()
result := x.MustRemove(data)
tt.Equal(t, false, jp.C("c").C("c1").Has(result))
tt.Equal(t, false, jp.C("c").C("c2").Has(result))
tt.Equal(t, true, jp.C("c").C("c3").Has(result))

data = keyedData()
result = x.MustRemoveOne(data)
tt.Equal(t, false, jp.C("c").C("c1").Has(result))
tt.Equal(t, true, jp.C("c").C("c2").Has(result))
tt.Equal(t, true, jp.C("c").C("c3").Has(result))
}

func TestExprRemoveKeyedWild(t *testing.T) {
x := jp.MustParseString("$.c[*]")

data := keyedData()
result := x.MustRemove(data)
tt.Equal(t, false, jp.C("c").C("c1").Has(result))
tt.Equal(t, false, jp.C("c").C("c2").Has(result))
tt.Equal(t, false, jp.C("c").C("c3").Has(result))

data = keyedData()
result = x.MustRemoveOne(data)
tt.Equal(t, false, jp.C("c").C("c1").Has(result))
tt.Equal(t, true, jp.C("c").C("c2").Has(result))
tt.Equal(t, true, jp.C("c").C("c3").Has(result))
}
41 changes: 41 additions & 0 deletions jp/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,26 @@ func (f Slice) remove(value any) (out any, changed bool) {
if changed {
out = ns
}
case RemovableIndexed:
size := tv.Size()
if start < 0 {
start = size + start
}
if end < 0 {
end = size + end
}
if size <= end {
end = size - 1
}
if start < 0 || end < 0 || size <= start || size <= end || step == 0 {
return
}
for i := size - 1; 0 <= i; i-- {
if inStep(i, start, end, step) {
changed = true
tv.RemoveValueAtIndex(i)
}
}
default:
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Slice {
Expand Down Expand Up @@ -284,6 +304,27 @@ func (f Slice) removeOne(value any) (out any, changed bool) {
if changed {
out = ns
}
case RemovableIndexed:
size := tv.Size()
if start < 0 {
start = size + start
}
if end < 0 {
end = size + end
}
if size <= end {
end = size - 1
}
if start < 0 || end < 0 || size <= start || size <= end || step == 0 {
return
}
for i := 0; i < size; i++ {
if inStep(i, start, end, step) {
changed = true
tv.RemoveValueAtIndex(i)
break
}
}
default:
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Slice {
Expand Down
35 changes: 35 additions & 0 deletions jp/union.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ func (f Union) removeOne(value any) (out any, changed bool) {
}
}
}
case RemovableIndexed:
size := tv.Size()
for i := 0; i < size; i++ {
if f.hasN(int64(i)) {
tv.RemoveValueAtIndex(i)
changed = true
break
}
}
case Keyed:
keys := tv.Keys()
sort.Strings(keys)
for _, k := range keys {
if f.hasKey(k) {
tv.RemoveValueForKey(k)
changed = true
break
}
}
default:
rv := reflect.ValueOf(value)
switch rv.Kind() {
Expand Down Expand Up @@ -216,6 +235,22 @@ func (f Union) remove(value any) (out any, changed bool) {
changed = true
}
}
case RemovableIndexed:
size := tv.Size()
for i := (size - 1); i >= 0; i-- {
if f.hasN(int64(i)) {
tv.RemoveValueAtIndex(i)
changed = true
}
}
case Keyed:
keys := tv.Keys()
for _, k := range keys {
if f.hasKey(k) {
tv.RemoveValueForKey(k)
changed = true
}
}
default:
rv := reflect.ValueOf(value)
switch rv.Kind() {
Expand Down
Loading

0 comments on commit e422fbd

Please sign in to comment.