Skip to content

Commit

Permalink
lib: allow specification of dotted path elements in collate and drop
Browse files Browse the repository at this point in the history
  • Loading branch information
efd6 committed Dec 5, 2022
1 parent 028bf4e commit fa5b7a0
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 12 deletions.
52 changes: 49 additions & 3 deletions lib/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ import (
// v.collate(["a.b", "b.b"]) // return [1, 2, 3, -1, -2, -3]
// v.collate(["a", "b.b"]) // return [{"b": 1 }, {"b": 2 }, {"b": 3 }, -1, -2, -3 ]
//
// If the the path to be dropped includes a dot, it can be escaped with a literal
// backslash. See drop below.
//
//
// Drop
//
Expand Down Expand Up @@ -111,6 +114,22 @@ import (
// v.drop(["a.b", "b.b"]) // return {"a": [{}, {}, {}], "b": [{"c": 10}, {"c": 20}, {"c": 30}]}
// v.drop(["a", "b.b"]) // return {"b": [{"c": 10}, {"c": 20}, {"c": 30}]}
//
// If the the path to be dropped includes a dot, it can be escaped with a literal
// backslash.
//
// Examples:
//
// Given v:
// {
// "dotted.path": [
// {"b": -1, "c": 10},
// {"b": -2, "c": 20},
// {"b": -3, "c": 30}
// ]
// }
//
// v.drop("dotted\\.path.b") // return {"dotted.path": [{"c": 10}, {"c": 20}, {"c": 30}]}
//
//
// Drop Empty
//
Expand Down Expand Up @@ -665,7 +684,7 @@ func dropFieldPath(arg ref.Val, path types.String) (val ref.Val) {
return types.NewRefValList(types.DefaultTypeAdapter, new)

case traits.Mapper:
dotIdx := strings.Index(string(path), ".")
dotIdx, escaped := pathSepIndex(string(path))
switch {
case dotIdx == 0, dotIdx == len(path)-1:
return types.NewErr("invalid parameter path for drop: %s", path)
Expand All @@ -690,6 +709,9 @@ func dropFieldPath(arg ref.Val, path types.String) (val ref.Val) {
return types.NewErr("unable to convert map to native: %v", err)
}
head := path[:dotIdx]
if escaped {
head = types.String(strings.ReplaceAll(string(head), `\.`, "."))
}
tail := path[dotIdx+1:]
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(head) == types.True {
Expand Down Expand Up @@ -718,7 +740,7 @@ func hasFieldPath(arg ref.Val, path types.String) bool {
return false

case traits.Mapper:
dotIdx := strings.Index(string(path), ".")
dotIdx, escaped := pathSepIndex(string(path))
switch {
case dotIdx == 0, dotIdx == len(path)-1:
panic(types.NewErr("invalid parameter path for drop: %s", path))
Expand All @@ -741,6 +763,9 @@ func hasFieldPath(arg ref.Val, path types.String) bool {
panic(types.NewErr("unable to convert map to native: %v", err))
}
head := path[:dotIdx]
if escaped {
head = types.String(strings.ReplaceAll(string(head), `\.`, "."))
}
tail := path[dotIdx+1:]
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(head) == types.True {
Expand Down Expand Up @@ -793,7 +818,7 @@ func collateFieldPath(arg ref.Val, path types.String) []ref.Val {
return collation

case traits.Mapper:
dotIdx := strings.Index(string(path), ".")
dotIdx, escaped := pathSepIndex(string(path))
switch {
case dotIdx == 0, dotIdx == len(path)-1:
panic(types.NewErr("invalid parameter path for drop: %s", path))
Expand Down Expand Up @@ -823,6 +848,9 @@ func collateFieldPath(arg ref.Val, path types.String) []ref.Val {
panic(types.NewErr("unable to convert map to native: %v", err))
}
head := path[:dotIdx]
if escaped {
head = types.String(strings.ReplaceAll(string(head), `\.`, "."))
}
tail := path[dotIdx+1:]
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(head) == types.True {
Expand Down Expand Up @@ -888,3 +916,21 @@ func makeAs(eh parser.ExprHelper, target *expr.Expr, args []*expr.Expr) (*expr.E
fold := eh.Fold(label, target, parser.AccumulatorName, init, condition, step, accuExpr)
return eh.GlobalCall(operators.Index, fold, eh.LiteralInt(0)), nil
}

// pathSepIndex returns the offset to a non-escaped dot path separator and
// whether the path element before the separator contains a backslash-escaped
// path separator.
func pathSepIndex(s string) (off int, escaped bool) {
for {
idx := strings.IndexByte(s[off:], '.')
if idx == -1 {
return -1, escaped
}
off += idx
if idx == 0 || s[off-1] != '\\' {
return off, escaped
}
off++
escaped = true
}
}
12 changes: 8 additions & 4 deletions testdata/collate_a_b.b.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ cmp stdout want.txt
{"b": 1},
{"b": 2},
{"b": 3}
],
],
"b": [
{"b": -1, "c": 10},
{"b": -2, "c": 20},
{"b": -3, "c": 30}
]
}.collate(["a","b.b"])
],
"c.d": [
{"e": "dotted path element", "f": 10},
]
}.collate(["a","b.b","c\\.d.e"])
-- want.txt --
[
{
Expand All @@ -28,5 +31,6 @@ cmp stdout want.txt
},
-1,
-2,
-3
-3,
"dotted path element"
]
19 changes: 14 additions & 5 deletions testdata/drop_a_b.b.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ cmp stdout want.txt
{"b": 1},
{"b": 2},
{"b": 3}
],
"b": [
],
"b": [
{"b": -1, "c": 10},
{"b": -2, "c": 20},
{"b": -3, "c": 30}
]
}.drop(["a","b.b"])
],
"b.b": "don't drop",
"drop.this": {
"path": true,
"keep": true
}
}.drop(["a","b.b","drop\\.this.path"])
-- want.txt --
{
"b": [
Expand All @@ -27,5 +32,9 @@ cmp stdout want.txt
{
"c": 30
}
]
],
"b.b": "don't drop",
"drop.this": {
"keep": true
}
}

0 comments on commit fa5b7a0

Please sign in to comment.