Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Indexed and Keyed interfaces when removing nodes #181

Merged
merged 3 commits into from
Aug 30, 2024
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
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
Loading