Skip to content

Commit

Permalink
Merge pull request #38 from TomWright/escape-selectors
Browse files Browse the repository at this point in the history
Escape selectors
  • Loading branch information
TomWright authored Nov 12, 2020
2 parents 9819b05 + f080b55 commit b45d76c
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 44 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ colourCodes:
rgb: 0000ff
```
You can escape values in selectors using a backslash `\`. The main use for this is to allow you to target fields that contain a dot or space in their name.
### Property
Property selectors are used to reference a single property of an object.
Expand Down Expand Up @@ -483,7 +485,7 @@ The next available index selector is used when adding to a list of items. It all
#### Any Index
The any index selector is used to select *all* items of a list.
- `colours[*]`
- `colours.[*]`
This must be used in conjunction with `-m`,`--multiple`.
Expand Down
16 changes: 12 additions & 4 deletions internal/command/root_put_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,23 @@ func TestRootCMD_Put(t *testing.T) {
t.Run("InvalidSingleSelector", expectErrFromInput(
`{"name": "Tom"}`,
[]string{"put", "string", "-f", "stdin", "-o", "stdout", "-p", "json", "-s", "[-]", "Frank"},
"selector is not supported here: [-]",
"invalid index: -",
))
t.Run("InvalidMultiSelector", expectErrFromInput(
`{"name": "Tom"}`,
[]string{"put", "string", "-f", "stdin", "-o", "stdout", "-p", "json", "-m", "-s", "[-]", "Frank"},
"selector is not supported here: [-]",
"invalid index: -",
))

t.Run("InvalidObjectSingleSelector", expectErrFromInput(
`{"name": "Tom"}`,
[]string{"put", "object", "-f", "stdin", "-o", "stdout", "-p", "json", "-t", "string", "-s", "[-]", "Frank"},
"selector is not supported here: [-]",
"invalid index: -",
))
t.Run("InvalidMultiSelector", expectErrFromInput(
`{"name": "Tom"}`,
[]string{"put", "object", "-f", "stdin", "-o", "stdout", "-p", "json", "-m", "-t", "string", "-s", "[-]", "Frank"},
"selector is not supported here: [-]",
"invalid index: -",
))
}

Expand Down Expand Up @@ -317,6 +317,14 @@ id: x
id: "1"
---
id: z
`, nil))

t.Run("StringWithDotInName", putStringTest(`
id: "asd"
my.name: "Tom"
`, "yaml", `my\.name`, "Jim", `
id: asd
my.name: Jim
`, nil))
}

Expand Down
6 changes: 3 additions & 3 deletions internal/command/root_select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ func TestRootCMD_Select(t *testing.T) {
t.Run("InvalidSingleSelector", expectErrFromInput(
`{"name": "Tom"}`,
[]string{"select", "-p", "json", "-s", "[-]"},
"selector is not supported here: [-]",
"invalid index: -",
))
t.Run("InvalidMultiSelector", expectErrFromInput(
`{"name": "Tom"}`,
[]string{"select", "-p", "json", "-m", "-s", "[-]"},
"selector is not supported here: [-]",
"invalid index: -",
))
}

Expand Down Expand Up @@ -229,7 +229,7 @@ func TestRootCmd_Select_JSON(t *testing.T) {
t.Run("DynamicString", selectTest(jsonData, "json", ".details.addresses.(postcode=YYY YYY).street", newline(`"34 Another Street"`), nil))
t.Run("QueryFromFile", selectTestFromFile("./../../tests/assets/example.json", ".preferences.favouriteColour", newline(`"red"`), nil))

t.Run("MultiProperty", selectTest(jsonData, "json", ".details.addresses[*].street", newline(`"101 Some Street"
t.Run("MultiProperty", selectTest(jsonData, "json", ".details.addresses.[*].street", newline(`"101 Some Street"
"34 Another Street"`), nil, "-m"))

t.Run("MultiRoot", selectTest(jsonDataSingle, "json", ".", newline(`{
Expand Down
62 changes: 28 additions & 34 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,39 +104,18 @@ func ParseSelector(selector string) (Selector, error) {
Conditions: make([]Condition, 0),
}

if match := propertyRegexp.FindStringSubmatch(selector); len(match) != 0 {
sel.Type = "PROPERTY"
sel.Current = match[0]
sel.Property = match[1]
} else if match := indexRegexp.FindStringSubmatch(selector); len(match) != 0 {
sel.Current = match[0]
switch match[1] {
case "":
sel.Type = "NEXT_AVAILABLE_INDEX"
case "*":
sel.Type = "INDEX_ANY"
default:
sel.Type = "INDEX"
var err error
index, err := strconv.ParseInt(match[1], 10, 32)
if err != nil {
return sel, &InvalidIndexErr{Index: match[1]}
}
sel.Index = int(index)
}
} else {
// todo : re-work this logic to base the entire parsing using ExtractNextSelector.
// This will be much easier instead of regex.
nextSelectorString := ExtractNextSelector(selector)

// Check if the selector starts with an open bracket.
if !strings.HasPrefix(strings.TrimPrefix(nextSelectorString, "."), "(") {
return sel, &UnsupportedSelector{Selector: nextSelectorString}
}
{
nextSelector, read := ExtractNextSelector(sel.Raw)
sel.Current = nextSelector
sel.Remaining = sel.Raw[read:]
}

sel.Current = nextSelectorString
nextSel := strings.TrimPrefix(sel.Current, ".")

dynamicGroups, err := DynamicSelectorToGroups(nextSelectorString)
switch {
case strings.HasPrefix(nextSel, "(") && strings.HasSuffix(nextSel, ")"):
sel.Type = "DYNAMIC"
dynamicGroups, err := DynamicSelectorToGroups(nextSel)
if err != nil {
return sel, err
}
Expand All @@ -158,10 +137,25 @@ func ParseSelector(selector string) (Selector, error) {
sel.Conditions = append(sel.Conditions, cond)
}

sel.Type = "DYNAMIC"
}
case nextSel == "[]":
sel.Type = "NEXT_AVAILABLE_INDEX"

sel.Remaining = strings.TrimPrefix(sel.Raw, sel.Current)
case nextSel == "[*]":
sel.Type = "INDEX_ANY"

case strings.HasPrefix(nextSel, "[") && strings.HasSuffix(nextSel, "]"):
sel.Type = "INDEX"
indexStr := nextSel[1 : len(nextSel)-1]
index, err := strconv.ParseInt(indexStr, 10, 32)
if err != nil {
return sel, &InvalidIndexErr{Index: indexStr}
}
sel.Index = int(index)

default:
sel.Type = "PROPERTY"
sel.Property = nextSel
}

return sel, nil
}
Expand Down
20 changes: 18 additions & 2 deletions dynamic_parser.go → selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,38 @@ import (
var ErrDynamicSelectorBracketMismatch = errors.New("dynamic selector bracket mismatch")

// ExtractNextSelector returns the next selector from the given input.
func ExtractNextSelector(input string) string {
func ExtractNextSelector(input string) (string, int) {
escapedIndex := -1
res := ""
i := 0
read := 0
for k, v := range input {
if escapedIndex == k-1 && k != 0 {
// last character was escape character
res += string(v)
read++
continue
}

if v == '(' || v == '[' {
i++
} else if v == ')' || v == ']' {
i--
}

if v == '\\' {
escapedIndex = k
read++
continue
}

if i == 0 && v == '.' && k != 0 {
break
}
res += string(v)
read++
}
return res
return res, read
}

// DynamicSelectorToGroups takes a dynamic selector and splits it into groups.
Expand Down
28 changes: 28 additions & 0 deletions selector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dasel_test

import (
"github.com/tomwright/dasel"
"testing"
)

func testExtractNextSelector(in string, exp string, expRead int) func(t *testing.T) {
return func(t *testing.T) {
got, read := dasel.ExtractNextSelector(in)
if exp != got {
t.Errorf("expected %v, got %v", exp, got)
}
if read != expRead {
t.Errorf("expected read of %d, got %d", expRead, read)
}
}
}

func TestExtractNextSelector(t *testing.T) {
t.Run("Simple", testExtractNextSelector(`.metadata.name`, `.metadata`, 9))
t.Run("EscapedDot", testExtractNextSelector(`.before\.after.name`, `.before.after`, 14))
t.Run("EscapedSpace", testExtractNextSelector(`.before\ after.name`, `.before after`, 14))
t.Run("DynamicWithPath", testExtractNextSelector(`.(.before.a=b).after.name`, `.(.before.a=b)`, 14))
t.Run("EscapedFirstDot", testExtractNextSelector(`\.name`, `.name`, 6))
t.Run("SimpleProp", testExtractNextSelector(`.name`, `.name`, 5))
t.Run("SimpleIndex", testExtractNextSelector(`.[123]`, `.[123]`, 6))
}

0 comments on commit b45d76c

Please sign in to comment.