mirror of
https://github.com/TomWright/dasel.git
synced 2022-05-22 02:32:45 +03:00
Add multi value selector. (#34)
Select/Put multiple values in one query
This commit is contained in:
69
README.md
69
README.md
@@ -49,6 +49,7 @@ Comparable to [jq](https://github.com/stedolan/jq) / [yq](https://github.com/kis
|
||||
* [Child](#child-elements)
|
||||
* [Index](#index)
|
||||
* [Next available index](#next-available-index)
|
||||
* [Any index](#any-index)
|
||||
* [Dynamic](#dynamic)
|
||||
* [Using queries in dynamic selectors](#using-queries-in-dynamic-selectors)
|
||||
* [Examples](#examples)
|
||||
@@ -141,7 +142,7 @@ An important note is that if no sub-command is given, dasel will default to `sel
|
||||
|
||||
### Select
|
||||
```bash
|
||||
dasel select -f <file> -p <parser> <selector>
|
||||
dasel select -f <file> -p <parser> -m <selector>
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
@@ -160,6 +161,22 @@ This is required if you are piping in data, otherwise dasel will use the given f
|
||||
|
||||
See [supported file types](#supported-file-types).
|
||||
|
||||
##### `-m`, `--multiple`
|
||||
|
||||
Tells dasel to select multiple items.
|
||||
|
||||
This causes the [dynamic](#dynamic) selector to return all matching results rather than the first, and enables the [any index](#any-index) selector.
|
||||
|
||||
All matches will be output on a new line.
|
||||
|
||||
E.g.
|
||||
|
||||
```
|
||||
echo '[{"name": "Tom"}, {"name": "Jim"}]' | dasel -p json -m '.[*].name'
|
||||
"Tom"
|
||||
"Jim"
|
||||
```
|
||||
|
||||
##### `-s`, `--selector`, `<selector>`
|
||||
|
||||
Specify the selector to use. See [Selectors](#selectors) for more information.
|
||||
@@ -190,7 +207,7 @@ cat deployment.yaml | dasel select -p yaml "spec.template.spec.containers.(name=
|
||||
|
||||
### Put
|
||||
```bash
|
||||
dasel put <type> -f <file> -o <out> -p <parser> <selector> <value>
|
||||
dasel put <type> -f <file> -o <out> -p <parser> -m <selector> <value>
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -230,6 +247,26 @@ This is required if you are piping in data, otherwise dasel will use the given f
|
||||
|
||||
See [supported file types](#supported-file-types).
|
||||
|
||||
##### `-m`, `--multiple`
|
||||
|
||||
Tells dasel to put multiple items.
|
||||
|
||||
This causes the [dynamic](#dynamic) selector to return all matching results rather than the first, and enables the [any index](#any-index) selector.
|
||||
|
||||
E.g.
|
||||
|
||||
```
|
||||
echo '[{"name": "Tom"}, {"name": "Jim"}]' | dasel put string -p json -m '.[*].name' Frank
|
||||
[
|
||||
{
|
||||
"name": "Frank"
|
||||
},
|
||||
{
|
||||
"name": "Frank"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
##### `-s`, `--selector`, `<selector>`
|
||||
|
||||
Specify the selector to use. See [Selectors](#selectors) for more information.
|
||||
@@ -251,7 +288,7 @@ This is required.
|
||||
Putting objects works slightly differently to a standard put, but the same principles apply.
|
||||
|
||||
```bash
|
||||
dasel put object -f <file> -o <out> -p <parser> -t <type> <selector> <values>
|
||||
dasel put object -f <file> -o <out> -p <parser> -m -t <type> <selector> <values>
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
@@ -287,6 +324,26 @@ This is required if you are piping in data, otherwise dasel will use the given f
|
||||
|
||||
See [supported file types](#supported-file-types).
|
||||
|
||||
##### `-m`, `--multiple`
|
||||
|
||||
Tells dasel to put multiple items.
|
||||
|
||||
This causes the [dynamic](#dynamic) selector to return all matching results rather than the first, and enables the [any index](#any-index) selector.
|
||||
|
||||
E.g.
|
||||
|
||||
```
|
||||
echo '[{"name": "Tom"}, {"name": "Jim"}]' | dasel put object -p json -m -t string '.[*]' 'name=Frank'
|
||||
[
|
||||
{
|
||||
"name": "Frank"
|
||||
},
|
||||
{
|
||||
"name": "Frank"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
##### `-s`, `--selector`, `<selector>`
|
||||
|
||||
Specify the selector to use. See [Selectors](#selectors) for more information.
|
||||
@@ -424,6 +481,12 @@ green
|
||||
The next available index selector is used when adding to a list of items. It allows you to append to a list.
|
||||
- `colours.[]`
|
||||
|
||||
#### Any Index
|
||||
The any index selector is used to select *all* items of a list.
|
||||
- `colours[*]`
|
||||
|
||||
This must be used in conjunction with `-m`,`--multiple`.
|
||||
|
||||
#### Dynamic
|
||||
Dynamic selectors are used with lists when you don't know the index of the item, but instead want to find the index based on some other criteria.
|
||||
|
||||
|
||||
11
error.go
11
error.go
@@ -3,6 +3,7 @@ package dasel
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ErrMissingPreviousNode is returned when findValue doesn't have access to the previous node.
|
||||
@@ -51,17 +52,13 @@ func (e UnsupportedTypeForSelector) Error() string {
|
||||
|
||||
// ValueNotFound is returned when a selector string cannot be fully resolved.
|
||||
type ValueNotFound struct {
|
||||
Selector string
|
||||
Node *Node
|
||||
Selector string
|
||||
PreviousValue reflect.Value
|
||||
}
|
||||
|
||||
// Error returns the error message.
|
||||
func (e ValueNotFound) Error() string {
|
||||
var previousValue interface{}
|
||||
if e.Node != nil && e.Node.Previous != nil {
|
||||
previousValue = e.Node.Previous.Value
|
||||
}
|
||||
return fmt.Sprintf("no value found for selector: %s: %v", e.Selector, previousValue)
|
||||
return fmt.Sprintf("no value found for selector: %s: %v", e.Selector, e.PreviousValue)
|
||||
}
|
||||
|
||||
// UnexpectedPreviousNilValue is returned when the previous node contains a nil value.
|
||||
|
||||
@@ -27,22 +27,10 @@ func TestErrorMessages(t *testing.T) {
|
||||
}, Out: "selector [INDEX] does not support value: map[string]interface {}: map[]"},
|
||||
{In: &dasel.ValueNotFound{
|
||||
Selector: ".name",
|
||||
Node: &dasel.Node{
|
||||
Selector: dasel.Selector{
|
||||
Current: ".name",
|
||||
},
|
||||
},
|
||||
}, Out: "no value found for selector: .name: <nil>"},
|
||||
}, Out: "no value found for selector: .name: <invalid reflect.Value>"},
|
||||
{In: &dasel.ValueNotFound{
|
||||
Selector: ".name",
|
||||
Node: &dasel.Node{
|
||||
Previous: &dasel.Node{
|
||||
Value: reflect.ValueOf(map[string]interface{}{}),
|
||||
},
|
||||
Selector: dasel.Selector{
|
||||
Current: ".name",
|
||||
},
|
||||
},
|
||||
Selector: ".name",
|
||||
PreviousValue: reflect.ValueOf(map[string]interface{}{}),
|
||||
}, Out: "no value found for selector: .name: map[]"},
|
||||
{In: &dasel.UnexpectedPreviousNilValue{Selector: ".name"}, Out: "previous value is nil: .name"},
|
||||
{In: &dasel.UnhandledCheckType{Value: ""}, Out: "unhandled check type: string"},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tomwright/dasel"
|
||||
@@ -140,8 +141,70 @@ func writeNodeToOutput(opts writeNodeToOutputOpts, cmd *cobra.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type writeNodesToOutputOpts struct {
|
||||
Nodes []*dasel.Node
|
||||
Parser storage.Parser
|
||||
File string
|
||||
Out string
|
||||
Writer io.Writer
|
||||
Plain bool
|
||||
}
|
||||
|
||||
func writeNodesToOutput(opts writeNodesToOutputOpts, cmd *cobra.Command) error {
|
||||
if opts.Writer == nil {
|
||||
switch {
|
||||
case opts.Out == "" && shouldReadFromStdin(opts.File):
|
||||
// No out flag and we read from stdin.
|
||||
opts.Writer = cmd.OutOrStdout()
|
||||
|
||||
case opts.Out == "stdout", opts.Out == "-":
|
||||
// Out flag wants to write to stdout.
|
||||
opts.Writer = cmd.OutOrStdout()
|
||||
|
||||
case opts.Out == "":
|
||||
// No out flag... write to the file we read from.
|
||||
f, err := os.Create(opts.File)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open output file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
opts.Writer = f
|
||||
|
||||
case opts.Out != "":
|
||||
// Out flag was set.
|
||||
f, err := os.Create(opts.Out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open output file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
opts.Writer = f
|
||||
}
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for i, n := range opts.Nodes {
|
||||
subOpts := writeNodeToOutputOpts{
|
||||
Node: n,
|
||||
Parser: opts.Parser,
|
||||
Writer: buf,
|
||||
Plain: opts.Plain,
|
||||
}
|
||||
if err := writeNodeToOutput(subOpts, cmd); err != nil {
|
||||
return fmt.Errorf("could not write node %d to output: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := io.Copy(opts.Writer, buf); err != nil {
|
||||
return fmt.Errorf("could not copy buffer to real output: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func putCommand() *cobra.Command {
|
||||
var fileFlag, selectorFlag, parserFlag, outFlag string
|
||||
var multiFlag bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "put -f <file> -s <selector>",
|
||||
@@ -159,6 +222,7 @@ func putCommand() *cobra.Command {
|
||||
cmd.PersistentFlags().StringVarP(&selectorFlag, "selector", "s", "", "The selector to use when querying the data structure.")
|
||||
cmd.PersistentFlags().StringVarP(&parserFlag, "parser", "p", "", "The parser to use with the given file.")
|
||||
cmd.PersistentFlags().StringVarP(&outFlag, "out", "o", "", "Output destination.")
|
||||
cmd.PersistentFlags().BoolVarP(&multiFlag, "multiple", "m", false, "Select multiple results.")
|
||||
|
||||
_ = cmd.MarkPersistentFlagFilename("file")
|
||||
|
||||
@@ -175,6 +239,7 @@ type genericPutOptions struct {
|
||||
Init func(genericPutOptions) genericPutOptions
|
||||
Reader io.Reader
|
||||
Writer io.Writer
|
||||
Multi bool
|
||||
}
|
||||
|
||||
func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOptions) genericPutOptions {
|
||||
@@ -183,6 +248,7 @@ func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOp
|
||||
opts.Out = cmd.Flag("out").Value.String()
|
||||
opts.Parser = cmd.Flag("parser").Value.String()
|
||||
opts.Selector = cmd.Flag("selector").Value.String()
|
||||
opts.Multi, _ = cmd.Flags().GetBool("multiple")
|
||||
|
||||
if opts.Selector == "" && len(args) > 0 {
|
||||
opts.Selector = args[0]
|
||||
@@ -219,8 +285,14 @@ func runGenericPutCommand(opts genericPutOptions, cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rootNode.Put(opts.Selector, updateValue); err != nil {
|
||||
return fmt.Errorf("could not put value: %w", err)
|
||||
if opts.Multi {
|
||||
if err := rootNode.PutMultiple(opts.Selector, updateValue); err != nil {
|
||||
return fmt.Errorf("could not put multi value: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := rootNode.Put(opts.Selector, updateValue); err != nil {
|
||||
return fmt.Errorf("could not put value: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeNodeToOutput(writeNodeToOutputOpts{
|
||||
|
||||
@@ -17,6 +17,7 @@ type putObjectOpts struct {
|
||||
InputValues []string
|
||||
Reader io.Reader
|
||||
Writer io.Writer
|
||||
Multi bool
|
||||
}
|
||||
|
||||
func getMapFromTypesValues(inputTypes []string, inputValues []string) (map[string]interface{}, error) {
|
||||
@@ -59,8 +60,14 @@ func runPutObjectCommand(opts putObjectOpts, cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rootNode.Put(opts.Selector, updateValue); err != nil {
|
||||
return fmt.Errorf("could not put value: %w", err)
|
||||
if opts.Multi {
|
||||
if err := rootNode.PutMultiple(opts.Selector, updateValue); err != nil {
|
||||
return fmt.Errorf("could not put object multi value: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := rootNode.Put(opts.Selector, updateValue); err != nil {
|
||||
return fmt.Errorf("could not put object value: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeNodeToOutput(writeNodeToOutputOpts{
|
||||
@@ -92,6 +99,8 @@ func putObjectCommand() *cobra.Command {
|
||||
InputTypes: typeList.Strings,
|
||||
InputValues: args,
|
||||
}
|
||||
opts.Multi, _ = cmd.Flags().GetBool("multiple")
|
||||
|
||||
if opts.Selector == "" && len(opts.InputValues) > 0 {
|
||||
opts.Selector = opts.InputValues[0]
|
||||
opts.InputValues = opts.InputValues[1:]
|
||||
|
||||
@@ -1,10 +1,46 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPut_Object(t *testing.T) {
|
||||
t.Run("SingleFailingWriter", func(t *testing.T) {
|
||||
err := runPutObjectCommand(putObjectOpts{
|
||||
Parser: "json",
|
||||
Selector: ".[0]",
|
||||
Reader: strings.NewReader(`[{"name": "Tom"}]`),
|
||||
InputValues: []string{"name=Frank"},
|
||||
InputTypes: []string{"string"},
|
||||
Writer: &failingWriter{},
|
||||
}, nil)
|
||||
|
||||
if err == nil || !errors.Is(err, errFailingWriterErr) {
|
||||
t.Errorf("expected error %v, got %v", errFailingWriterErr, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("MultiFailingWriter", func(t *testing.T) {
|
||||
err := runPutObjectCommand(putObjectOpts{
|
||||
Parser: "json",
|
||||
Selector: ".[*]",
|
||||
Reader: strings.NewReader(`[{"name": "Tom"}]`),
|
||||
InputValues: []string{"name=Frank"},
|
||||
InputTypes: []string{"string"},
|
||||
Writer: &failingWriter{},
|
||||
Multi: true,
|
||||
}, nil)
|
||||
|
||||
if err == nil || !errors.Is(err, errFailingWriterErr) {
|
||||
t.Errorf("expected error %v, got %v", errFailingWriterErr, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetMapFromTypes(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
exp := map[string]interface{}{
|
||||
|
||||
@@ -33,6 +33,28 @@ 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: [-]",
|
||||
))
|
||||
t.Run("InvalidMultiSelector", expectErrFromInput(
|
||||
`{"name": "Tom"}`,
|
||||
[]string{"put", "string", "-f", "stdin", "-o", "stdout", "-p", "json", "-m", "-s", "[-]", "Frank"},
|
||||
"selector is not supported here: [-]",
|
||||
))
|
||||
|
||||
t.Run("InvalidObjectSingleSelector", expectErrFromInput(
|
||||
`{"name": "Tom"}`,
|
||||
[]string{"put", "object", "-f", "stdin", "-o", "stdout", "-p", "json", "-t", "string", "-s", "[-]", "Frank"},
|
||||
"selector is not supported here: [-]",
|
||||
))
|
||||
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: [-]",
|
||||
))
|
||||
}
|
||||
|
||||
func putTest(in string, varType string, parser string, selector string, value string, out string, expErr error, additionalArgs ...string) func(t *testing.T) {
|
||||
@@ -126,6 +148,126 @@ func TestRootCMD_Put_JSON(t *testing.T) {
|
||||
}
|
||||
]
|
||||
}`, nil))
|
||||
|
||||
t.Run("MultipleObject", putObjectTest(`{
|
||||
"numbers": [
|
||||
{
|
||||
"rank": 1,
|
||||
"number": "one"
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"number": "two"
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"number": "three"
|
||||
}
|
||||
]
|
||||
}`, "json", ".numbers.[*]", []string{"number=five", "rank=5"}, []string{"string", "int"}, `{
|
||||
"numbers": [
|
||||
{
|
||||
"number": "five",
|
||||
"rank": 5
|
||||
},
|
||||
{
|
||||
"number": "five",
|
||||
"rank": 5
|
||||
},
|
||||
{
|
||||
"number": "five",
|
||||
"rank": 5
|
||||
}
|
||||
]
|
||||
}`, nil, "-m"))
|
||||
|
||||
t.Run("AppendObject", putObjectTest(`{
|
||||
"numbers": [
|
||||
{
|
||||
"rank": 1,
|
||||
"number": "one"
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"number": "two"
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"number": "three"
|
||||
}
|
||||
]
|
||||
}`, "json", ".numbers.[]", []string{"rank=4", "number=four"}, []string{"int", "string"}, `{
|
||||
"numbers": [
|
||||
{
|
||||
"number": "one",
|
||||
"rank": 1
|
||||
},
|
||||
{
|
||||
"number": "two",
|
||||
"rank": 2
|
||||
},
|
||||
{
|
||||
"number": "three",
|
||||
"rank": 3
|
||||
},
|
||||
{
|
||||
"number": "four",
|
||||
"rank": 4
|
||||
}
|
||||
]
|
||||
}`, nil))
|
||||
|
||||
t.Run("AppendObjectMulti", putObjectTest(`{
|
||||
"numbers": [
|
||||
{
|
||||
"rank": 1,
|
||||
"number": "one"
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"number": "two"
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"number": "three"
|
||||
}
|
||||
]
|
||||
}`, "json", ".numbers.[]", []string{"rank=4", "number=four"}, []string{"int", "string"}, `{
|
||||
"numbers": [
|
||||
{
|
||||
"number": "one",
|
||||
"rank": 1
|
||||
},
|
||||
{
|
||||
"number": "two",
|
||||
"rank": 2
|
||||
},
|
||||
{
|
||||
"number": "three",
|
||||
"rank": 3
|
||||
},
|
||||
{
|
||||
"number": "four",
|
||||
"rank": 4
|
||||
}
|
||||
]
|
||||
}`, nil, "-m"))
|
||||
|
||||
t.Run("MultipleString", putStringTest(`[
|
||||
{"value": "A"},
|
||||
{"value": "B"},
|
||||
{"value": "C"}
|
||||
]`, "json", "[*].value", "X", `[
|
||||
{
|
||||
"value": "X"
|
||||
},
|
||||
{
|
||||
"value": "X"
|
||||
},
|
||||
{
|
||||
"value": "X"
|
||||
}
|
||||
]`, nil, "-m"))
|
||||
}
|
||||
|
||||
func TestRootCMD_Put_YAML(t *testing.T) {
|
||||
@@ -270,17 +412,19 @@ func TestRootCMD_Put_CSV(t *testing.T) {
|
||||
`, nil))
|
||||
}
|
||||
|
||||
func putObjectTest(in string, parser string, selector string, values []string, types []string, out string, expErr error) func(t *testing.T) {
|
||||
func putObjectTest(in string, parser string, selector string, values []string, types []string, out string, expErr error, additionalArgs ...string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
cmd := command.NewRootCMD()
|
||||
outputBuffer := bytes.NewBuffer([]byte{})
|
||||
|
||||
args := []string{
|
||||
"put", "object", "-p", parser, "-o", "stdout", selector,
|
||||
"put", "object", "-p", parser, "-o", "stdout",
|
||||
}
|
||||
for _, t := range types {
|
||||
args = append(args, "-t", t)
|
||||
}
|
||||
args = append(args, additionalArgs...)
|
||||
args = append(args, selector)
|
||||
args = append(args, values...)
|
||||
|
||||
cmd.SetOut(outputBuffer)
|
||||
@@ -316,14 +460,14 @@ func putObjectTest(in string, parser string, selector string, values []string, t
|
||||
}
|
||||
}
|
||||
|
||||
func putStringTest(in string, parser string, selector string, value string, out string, expErr error) func(t *testing.T) {
|
||||
return putTest(in, "string", parser, selector, value, out, expErr)
|
||||
func putStringTest(in string, parser string, selector string, value string, out string, expErr error, additionalArgs ...string) func(t *testing.T) {
|
||||
return putTest(in, "string", parser, selector, value, out, expErr, additionalArgs...)
|
||||
}
|
||||
|
||||
func putIntTest(in string, parser string, selector string, value string, out string, expErr error) func(t *testing.T) {
|
||||
return putTest(in, "int", parser, selector, value, out, expErr)
|
||||
func putIntTest(in string, parser string, selector string, value string, out string, expErr error, additionalArgs ...string) func(t *testing.T) {
|
||||
return putTest(in, "int", parser, selector, value, out, expErr, additionalArgs...)
|
||||
}
|
||||
|
||||
func putBoolTest(in string, parser string, selector string, value string, out string, expErr error) func(t *testing.T) {
|
||||
return putTest(in, "bool", parser, selector, value, out, expErr)
|
||||
func putBoolTest(in string, parser string, selector string, value string, out string, expErr error, additionalArgs ...string) func(t *testing.T) {
|
||||
return putTest(in, "bool", parser, selector, value, out, expErr, additionalArgs...)
|
||||
}
|
||||
|
||||
@@ -121,16 +121,31 @@ func TestRootCMD_Select(t *testing.T) {
|
||||
`"Tom"
|
||||
`,
|
||||
))
|
||||
|
||||
t.Run("InvalidSingleSelector", expectErrFromInput(
|
||||
`{"name": "Tom"}`,
|
||||
[]string{"select", "-p", "json", "-s", "[-]"},
|
||||
"selector is not supported here: [-]",
|
||||
))
|
||||
t.Run("InvalidMultiSelector", expectErrFromInput(
|
||||
`{"name": "Tom"}`,
|
||||
[]string{"select", "-p", "json", "-m", "-s", "[-]"},
|
||||
"selector is not supported here: [-]",
|
||||
))
|
||||
}
|
||||
|
||||
func selectTest(in string, parser string, selector string, out string, expErr error) func(t *testing.T) {
|
||||
func selectTest(in string, parser string, selector string, out string, expErr error, additionalArgs ...string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
cmd := command.NewRootCMD()
|
||||
outputBuffer := bytes.NewBuffer([]byte{})
|
||||
|
||||
args := []string{
|
||||
"select", "-p", parser, selector,
|
||||
"select", "-p", parser,
|
||||
}
|
||||
if additionalArgs != nil {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
args = append(args, selector)
|
||||
|
||||
cmd.SetOut(outputBuffer)
|
||||
cmd.SetIn(strings.NewReader(in))
|
||||
@@ -214,6 +229,13 @@ 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"
|
||||
"34 Another Street"`), nil, "-m"))
|
||||
|
||||
t.Run("MultiRoot", selectTest(jsonDataSingle, "json", ".", newline(`{
|
||||
"x": "asd"
|
||||
}`), nil, "-m"))
|
||||
|
||||
t.Run("SubSelector", selectTest(`{
|
||||
"users": [
|
||||
{
|
||||
|
||||
@@ -15,12 +15,12 @@ func TestChangeDefaultCommand(t *testing.T) {
|
||||
os.Args = cachedArgs
|
||||
}()
|
||||
|
||||
testArgs := func(in []string, exp []string) func(t *testing.T) {
|
||||
testArgs := func(in []string, exp []string, blacklistedArgs ...string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
os.Args = in
|
||||
|
||||
cmd := command.NewRootCMD()
|
||||
command.ChangeDefaultCommand(cmd, "select")
|
||||
command.ChangeDefaultCommand(cmd, "select", blacklistedArgs...)
|
||||
|
||||
got := os.Args
|
||||
if !reflect.DeepEqual(exp, got) {
|
||||
@@ -43,6 +43,18 @@ func TestChangeDefaultCommand(t *testing.T) {
|
||||
[]string{"dasel", "put", "-p", "json", "-t", "string", "name=Tom"},
|
||||
[]string{"dasel", "put", "-p", "json", "-t", "string", "name=Tom"},
|
||||
))
|
||||
|
||||
t.Run("IgnoreBlacklisted", testArgs(
|
||||
[]string{"dasel", "-v"},
|
||||
[]string{"dasel", "-v"},
|
||||
"-v",
|
||||
))
|
||||
|
||||
t.Run("IgnoreBlacklisted", testArgs(
|
||||
[]string{"dasel", "select", "-v"},
|
||||
[]string{"dasel", "select", "-v"},
|
||||
"-v",
|
||||
))
|
||||
}
|
||||
|
||||
func expectErr(args []string, expErr string) func(t *testing.T) {
|
||||
@@ -62,6 +74,24 @@ func expectErr(args []string, expErr string) func(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func expectErrFromInput(in string, args []string, expErr string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
cmd := command.NewRootCMD()
|
||||
outputBuffer := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd.SetIn(bytes.NewReader([]byte(in)))
|
||||
cmd.SetOut(outputBuffer)
|
||||
cmd.SetArgs(args)
|
||||
|
||||
err := cmd.Execute()
|
||||
|
||||
if err == nil || !strings.Contains(err.Error(), expErr) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expectOutput(in string, args []string, exp string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
cmd := command.NewRootCMD()
|
||||
|
||||
@@ -14,6 +14,7 @@ type selectOptions struct {
|
||||
Reader io.Reader
|
||||
Writer io.Writer
|
||||
Plain bool
|
||||
Multi bool
|
||||
}
|
||||
|
||||
func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
|
||||
@@ -30,7 +31,34 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Writer == nil {
|
||||
opts.Writer = cmd.OutOrStdout()
|
||||
}
|
||||
|
||||
if opts.Multi {
|
||||
var results []*dasel.Node
|
||||
if opts.Selector == "." {
|
||||
results = []*dasel.Node{rootNode}
|
||||
} else {
|
||||
results, err = rootNode.QueryMultiple(opts.Selector)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not query multiple node: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeNodesToOutput(writeNodesToOutputOpts{
|
||||
Nodes: results,
|
||||
Parser: parser,
|
||||
Writer: opts.Writer,
|
||||
Plain: opts.Plain,
|
||||
}, cmd); err != nil {
|
||||
return fmt.Errorf("could not write output: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var res *dasel.Node
|
||||
|
||||
if opts.Selector == "." {
|
||||
res = rootNode
|
||||
} else {
|
||||
@@ -40,10 +68,6 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Writer == nil {
|
||||
opts.Writer = cmd.OutOrStdout()
|
||||
}
|
||||
|
||||
if err := writeNodeToOutput(writeNodeToOutputOpts{
|
||||
Node: res,
|
||||
Parser: parser,
|
||||
@@ -58,7 +82,7 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
|
||||
|
||||
func selectCommand() *cobra.Command {
|
||||
var fileFlag, selectorFlag, parserFlag string
|
||||
var plainFlag bool
|
||||
var plainFlag, multiFlag bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "select -f <file> -p <json,yaml> -s <selector>",
|
||||
@@ -73,6 +97,7 @@ func selectCommand() *cobra.Command {
|
||||
Parser: parserFlag,
|
||||
Selector: selectorFlag,
|
||||
Plain: plainFlag,
|
||||
Multi: multiFlag,
|
||||
}, cmd)
|
||||
},
|
||||
}
|
||||
@@ -81,6 +106,7 @@ func selectCommand() *cobra.Command {
|
||||
cmd.Flags().StringVarP(&selectorFlag, "selector", "s", "", "The selector to use when querying the data structure.")
|
||||
cmd.Flags().StringVarP(&parserFlag, "parser", "p", "", "The parser to use with the given file.")
|
||||
cmd.Flags().BoolVar(&plainFlag, "plain", false, "Do not format output to the output data format.")
|
||||
cmd.Flags().BoolVarP(&multiFlag, "multiple", "m", false, "Select multiple results.")
|
||||
|
||||
_ = cmd.MarkFlagFilename("file")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -64,6 +65,15 @@ var tomlData = `id = "1111"
|
||||
postcode = "YYY YYY"
|
||||
`
|
||||
|
||||
var errFailingWriterErr = errors.New("i am meant to fail at writing")
|
||||
|
||||
type failingWriter struct {
|
||||
}
|
||||
|
||||
func (fp *failingWriter) Write(_ []byte) (int, error) {
|
||||
return 0, errFailingWriterErr
|
||||
}
|
||||
|
||||
func selectTest(in string, parser string, selector string, out string, expErr error) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
outputBuffer := bytes.NewBuffer([]byte{})
|
||||
@@ -104,6 +114,36 @@ func newline(x string) string {
|
||||
return x + "\n"
|
||||
}
|
||||
|
||||
func TestSelect(t *testing.T) {
|
||||
t.Run("SingleFailingWriter", func(t *testing.T) {
|
||||
err := runSelectCommand(selectOptions{
|
||||
Parser: "json",
|
||||
Selector: ".",
|
||||
Reader: strings.NewReader(`{"name": "Tom"}`),
|
||||
Writer: &failingWriter{},
|
||||
}, nil)
|
||||
|
||||
if err == nil || !errors.Is(err, errFailingWriterErr) {
|
||||
t.Errorf("expected error %v, got %v", errFailingWriterErr, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("MultiFailingWriter", func(t *testing.T) {
|
||||
err := runSelectCommand(selectOptions{
|
||||
Parser: "json",
|
||||
Selector: ".",
|
||||
Reader: strings.NewReader(`{"name": "Tom"}`),
|
||||
Writer: &failingWriter{},
|
||||
Multi: true,
|
||||
}, nil)
|
||||
|
||||
if err == nil || !errors.Is(err, errFailingWriterErr) {
|
||||
t.Errorf("expected error %v, got %v", errFailingWriterErr, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelect_JSON(t *testing.T) {
|
||||
t.Run("SingleProperty", selectTest(jsonData, "json", ".id", newline(`"1111"`), nil))
|
||||
t.Run("ObjectProperty", selectTest(jsonData, "json", ".details.name", newline(`"Tom"`), nil))
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
type CSVParser struct {
|
||||
}
|
||||
|
||||
// YAMLSingleDocument represents a CSV file.
|
||||
// CSVDocument represents a CSV file.
|
||||
// This is required to keep headers in the expected order.
|
||||
type CSVDocument struct {
|
||||
originalRequired
|
||||
|
||||
@@ -61,6 +61,7 @@ func TestNewParserFromFilename(t *testing.T) {
|
||||
{In: "a.yml", Out: &storage.YAMLParser{}},
|
||||
{In: "a.toml", Out: &storage.TOMLParser{}},
|
||||
{In: "a.xml", Out: &storage.XMLParser{}},
|
||||
{In: "a.csv", Out: &storage.CSVParser{}},
|
||||
{In: "a.txt", Out: nil, Err: &storage.UnknownParserErr{Parser: ".txt"}},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tomwright/dasel/internal/storage"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -17,21 +16,6 @@ var xmlMap = map[string]interface{}{
|
||||
},
|
||||
}
|
||||
|
||||
func TestXMLParser_FromBytes2(t *testing.T) {
|
||||
got, err := (&storage.XMLParser{}).FromBytes([]byte(`<data>
|
||||
<names>
|
||||
<name>Tom</name>
|
||||
<name>Jim</name>
|
||||
</names>
|
||||
</data>`))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(got)
|
||||
}
|
||||
|
||||
func TestXMLParser_FromBytes(t *testing.T) {
|
||||
got, err := (&storage.XMLParser{}).FromBytes(xmlBytes)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tomwright/dasel/internal/storage"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -21,7 +20,6 @@ func TestYAMLParser_FromBytes(t *testing.T) {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%T", got)
|
||||
exp := &storage.YAMLSingleDocument{Value: yamlMap}
|
||||
if !reflect.DeepEqual(exp, got) {
|
||||
t.Errorf("expected %v, got %v", exp, got)
|
||||
@@ -51,13 +49,22 @@ name: Jim
|
||||
}
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
d, err := (&storage.YAMLParser{}).FromBytes([]byte(`{1:asd`))
|
||||
fmt.Println(d)
|
||||
_, err := (&storage.YAMLParser{}).FromBytes([]byte(`{1:asd`))
|
||||
if err == nil || !strings.Contains(err.Error(), "could not unmarshal config data") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
got, err := (&storage.YAMLParser{}).FromBytes([]byte(``))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(nil, got) {
|
||||
t.Errorf("expected %v, got %v", nil, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestYAMLParser_ToBytes(t *testing.T) {
|
||||
|
||||
17
node.go
17
node.go
@@ -31,8 +31,13 @@ type Selector struct {
|
||||
type Node struct {
|
||||
// Previous is the previous node in the chain.
|
||||
Previous *Node `json:"-"`
|
||||
// Next is the next node in the chain.
|
||||
// Next contains the next node in the chain.
|
||||
// This is used with Query and Put requests.
|
||||
Next *Node `json:"next,omitempty"`
|
||||
// NextMultiple contains the next nodes in the chain.
|
||||
// This is used with QueryMultiple and PutMultiple requests.
|
||||
// When a major version change occurs this will completely replace Next.
|
||||
NextMultiple []*Node `json:"nextMultiple,omitempty"`
|
||||
// OriginalValue is the value returned from the parser.
|
||||
// In most cases this is the same as Value, but is different for thr YAML parser
|
||||
// as it contains information on the original document.
|
||||
@@ -52,7 +57,7 @@ func (n *Node) InterfaceValue() interface{} {
|
||||
|
||||
const (
|
||||
propertySelector = `(?P<property>[a-zA-Z\-_]+)`
|
||||
indexSelector = `\[(?P<index>[0-9a-zA-Z]*?)\]`
|
||||
indexSelector = `\[(?P<index>[0-9a-zA-Z\*]*?)\]`
|
||||
dynamicSelector = `(?P<name>.+)(?P<comparison>=|<|>)(?P<value>.+)`
|
||||
)
|
||||
|
||||
@@ -105,9 +110,12 @@ func ParseSelector(selector string) (Selector, error) {
|
||||
sel.Property = match[1]
|
||||
} else if match := indexRegexp.FindStringSubmatch(selector); len(match) != 0 {
|
||||
sel.Current = match[0]
|
||||
if match[1] == "" {
|
||||
switch match[1] {
|
||||
case "":
|
||||
sel.Type = "NEXT_AVAILABLE_INDEX"
|
||||
} else {
|
||||
case "*":
|
||||
sel.Type = "INDEX_ANY"
|
||||
default:
|
||||
sel.Type = "INDEX"
|
||||
var err error
|
||||
index, err := strconv.ParseInt(match[1], 10, 32)
|
||||
@@ -161,6 +169,7 @@ func New(value interface{}) *Node {
|
||||
rootNode := &Node{
|
||||
Previous: nil,
|
||||
Next: nil,
|
||||
NextMultiple: nil,
|
||||
OriginalValue: value,
|
||||
Value: baseValue,
|
||||
Selector: Selector{
|
||||
|
||||
58
node_put.go
58
node_put.go
@@ -25,6 +25,29 @@ func (n *Node) Put(selector string, newValue interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutMultiple all applicable nodes for the given selector and updates all of their values to the given value.
|
||||
// It then attempts to propagate the value back up the chain to the root element.
|
||||
func (n *Node) PutMultiple(selector string, newValue interface{}) error {
|
||||
n.Selector.Remaining = selector
|
||||
|
||||
if err := buildPutMultipleChain(n); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
final := lastNodes(n)
|
||||
|
||||
val := reflect.ValueOf(newValue)
|
||||
|
||||
for _, n := range final {
|
||||
n.Value = val
|
||||
if err := propagate(n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildPutChain(n *Node) error {
|
||||
if n.Selector.Remaining == "" {
|
||||
// We've reached the end
|
||||
@@ -47,8 +70,41 @@ func buildPutChain(n *Node) error {
|
||||
// Populate the value for the new node.
|
||||
nextNode.Value, err = findValue(nextNode, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find value: %w", err)
|
||||
return fmt.Errorf("could not find put value: %w", err)
|
||||
}
|
||||
|
||||
return buildPutChain(nextNode)
|
||||
}
|
||||
|
||||
func buildPutMultipleChain(n *Node) error {
|
||||
if n.Selector.Remaining == "" {
|
||||
// We've reached the end
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Parse the selector.
|
||||
nextSelector, err := ParseSelector(n.Selector.Remaining)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse selector: %w", err)
|
||||
}
|
||||
|
||||
// Populate the value for the new node.
|
||||
n.NextMultiple, err = findNodes(nextSelector, n, true)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find put multiple value: %w", err)
|
||||
}
|
||||
|
||||
for _, next := range n.NextMultiple {
|
||||
// Add the back reference
|
||||
next.Previous = n
|
||||
|
||||
if err := buildPutMultipleChain(next); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,12 +17,15 @@ func (n *Node) Query(selector string) (*Node, error) {
|
||||
return lastNode(rootNode), nil
|
||||
}
|
||||
|
||||
// lastNode returns the last node in the chain.
|
||||
// If a node contains multiple next nodes, the first node is taken.
|
||||
func lastNode(n *Node) *Node {
|
||||
node := n
|
||||
for {
|
||||
if n.Next == nil {
|
||||
return n
|
||||
if node.Next == nil {
|
||||
return node
|
||||
}
|
||||
n = n.Next
|
||||
node = node.Next
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +75,7 @@ func findValueProperty(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
if createIfNotExists {
|
||||
return nilValue(), nil
|
||||
}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}
|
||||
}
|
||||
|
||||
return nilValue(), &UnsupportedTypeForSelector{Selector: n.Selector, Value: n.Previous.Value.Type().Kind()}
|
||||
@@ -95,7 +98,7 @@ func findValueIndex(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
if createIfNotExists {
|
||||
return nilValue(), nil
|
||||
}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}
|
||||
}
|
||||
|
||||
return nilValue(), &UnsupportedTypeForSelector{Selector: n.Selector, Value: value.Kind()}
|
||||
@@ -106,7 +109,7 @@ func findValueIndex(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
func findNextAvailableIndex(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
if !createIfNotExists {
|
||||
// Next available index isn't supported unless it's creating the item.
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}
|
||||
}
|
||||
return nilValue(), nil
|
||||
}
|
||||
@@ -158,29 +161,12 @@ func findValueDynamic(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
n.Selector.Type = "NEXT_AVAILABLE_INDEX"
|
||||
return nilValue(), nil
|
||||
}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}
|
||||
return nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}
|
||||
}
|
||||
|
||||
return nilValue(), &UnsupportedTypeForSelector{Selector: n.Selector, Value: value.Kind()}
|
||||
}
|
||||
|
||||
func initialiseEmptyValue(n *Node) {
|
||||
switch n.Selector.Type {
|
||||
case "PROPERTY":
|
||||
n.wasInitialised = true
|
||||
n.Previous.Value = reflect.ValueOf(map[interface{}]interface{}{})
|
||||
case "INDEX":
|
||||
n.wasInitialised = true
|
||||
n.Previous.Value = reflect.ValueOf([]interface{}{})
|
||||
case "NEXT_AVAILABLE_INDEX":
|
||||
n.wasInitialised = true
|
||||
n.Previous.Value = reflect.ValueOf([]interface{}{})
|
||||
case "DYNAMIC":
|
||||
n.wasInitialised = true
|
||||
n.Previous.Value = reflect.ValueOf([]interface{}{})
|
||||
}
|
||||
}
|
||||
|
||||
// findValue finds the value for the given node.
|
||||
// The value is essentially pulled from the previous node, using the (already parsed) selector
|
||||
// information stored on the current node.
|
||||
@@ -191,7 +177,7 @@ func findValue(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
}
|
||||
|
||||
if createIfNotExists && !isValid(n.Previous.Value) {
|
||||
initialiseEmptyValue(n)
|
||||
n.Previous.Value, n.wasInitialised = initialiseEmptyValue(n.Selector, n.Previous.Value)
|
||||
}
|
||||
|
||||
switch n.Selector.Type {
|
||||
@@ -204,6 +190,6 @@ func findValue(n *Node, createIfNotExists bool) (reflect.Value, error) {
|
||||
case "DYNAMIC":
|
||||
return findValueDynamic(n, createIfNotExists)
|
||||
default:
|
||||
return nilValue(), &UnsupportedSelector{Selector: n.Selector.Type}
|
||||
return nilValue(), &UnsupportedSelector{Selector: n.Selector.Raw}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestFindValueProperty(t *testing.T) {
|
||||
n := getNodeWithValue(map[string]interface{}{})
|
||||
n.Selector.Current = "x"
|
||||
got, err := findValueProperty(n, false)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}, got, err)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}, got, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestFindValueIndex(t *testing.T) {
|
||||
n.Selector.Current = "[0]"
|
||||
n.Selector.Index = 0
|
||||
got, err := findValueIndex(n, false)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}, got, err)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}, got, err)
|
||||
})
|
||||
t.Run("UnsupportedType", func(t *testing.T) {
|
||||
val := map[string]interface{}{}
|
||||
@@ -86,7 +86,7 @@ func TestFindValueNextAvailableIndex(t *testing.T) {
|
||||
n.Selector.Current = "[0]"
|
||||
n.Selector.Index = 0
|
||||
got, err := findNextAvailableIndex(n, false)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}, got, err)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}, got, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestFindValueDynamic(t *testing.T) {
|
||||
&EqualCondition{Key: "name", Value: "x"},
|
||||
}
|
||||
got, err := findValueDynamic(n, false)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, Node: n}, got, err)
|
||||
assertQueryResult(t, nilValue(), &ValueNotFound{Selector: n.Selector.Current, PreviousValue: n.Previous.Value}, got, err)
|
||||
})
|
||||
t.Run("NotFoundWithCreate", func(t *testing.T) {
|
||||
n := getNodeWithValue([]interface{}{})
|
||||
@@ -154,7 +154,7 @@ func TestFindValue(t *testing.T) {
|
||||
})
|
||||
t.Run("UnsupportedSelector", func(t *testing.T) {
|
||||
n := getNodeWithValue([]interface{}{})
|
||||
n.Selector.Type = "BAD"
|
||||
n.Selector.Raw = "BAD"
|
||||
got, err := findValue(n, false)
|
||||
assertQueryResult(t, nilValue(), &UnsupportedSelector{Selector: "BAD"}, got, err)
|
||||
})
|
||||
|
||||
279
node_query_multiple.go
Normal file
279
node_query_multiple.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package dasel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// QueryMultiple uses the given selector to query the current node for every match
|
||||
// possible and returns all of the end nodes.
|
||||
func (n *Node) QueryMultiple(selector string) ([]*Node, error) {
|
||||
n.Selector.Remaining = selector
|
||||
|
||||
if err := buildFindMultipleChain(n); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return lastNodes(n), nil
|
||||
}
|
||||
|
||||
// lastNodes returns a list of all of the last nodes.
|
||||
func lastNodes(n *Node) []*Node {
|
||||
if len(n.NextMultiple) == 0 {
|
||||
return []*Node{n}
|
||||
}
|
||||
var res []*Node
|
||||
for _, nextNode := range n.NextMultiple {
|
||||
res = append(res, lastNodes(nextNode)...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func buildFindMultipleChain(n *Node) error {
|
||||
if n.Selector.Remaining == "" {
|
||||
// We've reached the end
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Parse the selector.
|
||||
nextSelector, err := ParseSelector(n.Selector.Remaining)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse selector: %w", err)
|
||||
}
|
||||
|
||||
// Populate the value for the new node.
|
||||
n.NextMultiple, err = findNodes(nextSelector, n, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find multiple value: %w", err)
|
||||
}
|
||||
|
||||
for _, next := range n.NextMultiple {
|
||||
// Add the back reference
|
||||
next.Previous = n
|
||||
|
||||
if err := buildFindMultipleChain(next); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findNodesProperty finds the value for the given node using the property selector
|
||||
// information.
|
||||
func findNodesProperty(selector Selector, previousValue reflect.Value, createIfNotExists bool) ([]*Node, error) {
|
||||
if !isValid(previousValue) {
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: selector.Raw}
|
||||
}
|
||||
|
||||
value := unwrapValue(previousValue)
|
||||
|
||||
if value.Kind() == reflect.Map {
|
||||
node := &Node{
|
||||
Value: nilValue(),
|
||||
Selector: selector,
|
||||
}
|
||||
for _, key := range value.MapKeys() {
|
||||
if fmt.Sprint(key.Interface()) == selector.Property {
|
||||
node.Value = value.MapIndex(key)
|
||||
return []*Node{node}, nil
|
||||
}
|
||||
}
|
||||
if createIfNotExists {
|
||||
return []*Node{node}, nil
|
||||
}
|
||||
return nil, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}
|
||||
}
|
||||
|
||||
return nil, &UnsupportedTypeForSelector{Selector: selector, Value: previousValue.Type().Kind()}
|
||||
}
|
||||
|
||||
// findNodesIndex finds the value for the given node using the index selector
|
||||
// information.
|
||||
func findNodesIndex(selector Selector, previousValue reflect.Value, createIfNotExists bool) ([]*Node, error) {
|
||||
if !isValid(previousValue) {
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: selector.Raw}
|
||||
}
|
||||
|
||||
value := unwrapValue(previousValue)
|
||||
|
||||
if value.Kind() == reflect.Slice {
|
||||
node := &Node{
|
||||
Value: nilValue(),
|
||||
Selector: selector,
|
||||
}
|
||||
valueLen := value.Len()
|
||||
if selector.Index >= 0 && selector.Index < valueLen {
|
||||
node.Value = value.Index(selector.Index)
|
||||
return []*Node{node}, nil
|
||||
}
|
||||
if createIfNotExists {
|
||||
return []*Node{node}, nil
|
||||
}
|
||||
return nil, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}
|
||||
}
|
||||
|
||||
return nil, &UnsupportedTypeForSelector{Selector: selector, Value: value.Kind()}
|
||||
}
|
||||
|
||||
// findNextAvailableIndexNodes finds the value for the given node using the index selector
|
||||
// information.
|
||||
func findNextAvailableIndexNodes(selector Selector, previousValue reflect.Value, createIfNotExists bool) ([]*Node, error) {
|
||||
if !createIfNotExists {
|
||||
// Next available index isn't supported unless it's creating the item.
|
||||
return nil, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}
|
||||
}
|
||||
return []*Node{
|
||||
{
|
||||
Value: nilValue(),
|
||||
Selector: selector,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// processFindDynamicItems is used by findNodesDynamic.
|
||||
func processFindDynamicItems(selector Selector, object reflect.Value) (bool, error) {
|
||||
// Loop through each condition.
|
||||
allConditionsMatched := true
|
||||
for _, c := range selector.Conditions {
|
||||
// If the object doesn't match any checks, return a ValueNotFound.
|
||||
found, err := c.Check(object)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !found {
|
||||
allConditionsMatched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allConditionsMatched {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// findNodesDynamic finds the value for the given node using the dynamic selector
|
||||
// information.
|
||||
func findNodesDynamic(selector Selector, previousValue reflect.Value, createIfNotExists bool) ([]*Node, error) {
|
||||
if !isValid(previousValue) {
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: selector.Raw}
|
||||
}
|
||||
value := unwrapValue(previousValue)
|
||||
|
||||
if value.Kind() == reflect.Slice {
|
||||
results := make([]*Node, 0)
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
object := value.Index(i)
|
||||
found, err := processFindDynamicItems(selector, object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if found {
|
||||
selector.Type = "INDEX"
|
||||
selector.Index = i
|
||||
results = append(results, &Node{
|
||||
Value: object,
|
||||
Selector: selector,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(results) > 0 {
|
||||
return results, nil
|
||||
}
|
||||
if createIfNotExists {
|
||||
selector.Type = "NEXT_AVAILABLE_INDEX"
|
||||
return []*Node{
|
||||
{
|
||||
Value: nilValue(),
|
||||
Selector: selector,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}
|
||||
}
|
||||
|
||||
return nil, &UnsupportedTypeForSelector{Selector: selector, Value: value.Kind()}
|
||||
}
|
||||
|
||||
// findNodesAnyIndex returns a node for every value in the previous value list.
|
||||
func findNodesAnyIndex(selector Selector, previousValue reflect.Value) ([]*Node, error) {
|
||||
if !isValid(previousValue) {
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: selector.Raw}
|
||||
}
|
||||
value := unwrapValue(previousValue)
|
||||
|
||||
if value.Kind() == reflect.Slice {
|
||||
results := make([]*Node, 0)
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
object := value.Index(i)
|
||||
selector.Type = "INDEX"
|
||||
selector.Index = i
|
||||
results = append(results, &Node{
|
||||
Value: object,
|
||||
Selector: selector,
|
||||
})
|
||||
}
|
||||
if len(results) > 0 {
|
||||
return results, nil
|
||||
}
|
||||
return nil, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}
|
||||
}
|
||||
|
||||
return nil, &UnsupportedTypeForSelector{Selector: selector, Value: value.Kind()}
|
||||
}
|
||||
|
||||
func initialiseEmptyValue(selector Selector, previousValue reflect.Value) (reflect.Value, bool) {
|
||||
switch selector.Type {
|
||||
case "PROPERTY":
|
||||
return reflect.ValueOf(map[interface{}]interface{}{}), true
|
||||
case "INDEX":
|
||||
return reflect.ValueOf([]interface{}{}), true
|
||||
case "NEXT_AVAILABLE_INDEX":
|
||||
return reflect.ValueOf([]interface{}{}), true
|
||||
case "INDEX_ANY":
|
||||
return reflect.ValueOf([]interface{}{}), true
|
||||
case "DYNAMIC":
|
||||
return reflect.ValueOf([]interface{}{}), true
|
||||
}
|
||||
return previousValue, false
|
||||
}
|
||||
|
||||
// findNodes returns all of the nodes from the previous value that match the given selector.
|
||||
func findNodes(selector Selector, previousNode *Node, createIfNotExists bool) ([]*Node, error) {
|
||||
initialised := false
|
||||
if createIfNotExists && !isValid(previousNode.Value) {
|
||||
previousNode.Value, initialised = initialiseEmptyValue(selector, previousNode.Value)
|
||||
}
|
||||
|
||||
var res []*Node
|
||||
var err error
|
||||
|
||||
switch selector.Type {
|
||||
case "PROPERTY":
|
||||
res, err = findNodesProperty(selector, previousNode.Value, createIfNotExists)
|
||||
case "INDEX":
|
||||
res, err = findNodesIndex(selector, previousNode.Value, createIfNotExists)
|
||||
case "NEXT_AVAILABLE_INDEX":
|
||||
res, err = findNextAvailableIndexNodes(selector, previousNode.Value, createIfNotExists)
|
||||
case "INDEX_ANY":
|
||||
res, err = findNodesAnyIndex(selector, previousNode.Value)
|
||||
case "DYNAMIC":
|
||||
res, err = findNodesDynamic(selector, previousNode.Value, createIfNotExists)
|
||||
default:
|
||||
err = &UnsupportedSelector{Selector: selector.Raw}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if initialised && res != nil && len(res) > 0 {
|
||||
for _, n := range res {
|
||||
n.wasInitialised = initialised
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
137
node_query_multiple_internal_test.go
Normal file
137
node_query_multiple_internal_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package dasel
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertQueryMultipleResult(t *testing.T, exp []reflect.Value, expErr error, got []*Node, gotErr error) bool {
|
||||
if !assertErrResult(t, expErr, gotErr) {
|
||||
return false
|
||||
}
|
||||
gotVals := make([]reflect.Value, len(got))
|
||||
if len(got) > 0 {
|
||||
for i, n := range got {
|
||||
gotVals[i] = n.Value
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(exp, gotVals) {
|
||||
t.Errorf("expected result %v, got %v", exp, gotVals)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestFindNodesProperty(t *testing.T) {
|
||||
t.Run("NilValue", func(t *testing.T) {
|
||||
selector := Selector{Current: ".", Raw: "."}
|
||||
got, err := findNodesProperty(selector, nilValue(), false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnexpectedPreviousNilValue{Selector: "."}, got, err)
|
||||
})
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf(map[string]interface{}{})
|
||||
selector := Selector{Current: "x"}
|
||||
got, err := findNodesProperty(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}, got, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindNodesIndex(t *testing.T) {
|
||||
t.Run("NilValue", func(t *testing.T) {
|
||||
selector := Selector{Current: ".", Raw: "."}
|
||||
got, err := findNodesIndex(selector, nilValue(), false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnexpectedPreviousNilValue{Selector: "."}, got, err)
|
||||
})
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
selector := Selector{Current: "[0]", Index: 0, Raw: ".[0]"}
|
||||
previousValue := reflect.ValueOf([]interface{}{})
|
||||
got, err := findNodesIndex(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &ValueNotFound{Selector: "[0]", PreviousValue: previousValue}, got, err)
|
||||
})
|
||||
t.Run("UnsupportedType", func(t *testing.T) {
|
||||
selector := Selector{Current: "[0]", Index: 0, Raw: ".[0]"}
|
||||
previousValue := reflect.ValueOf(map[string]interface{}{})
|
||||
got, err := findNodesIndex(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnsupportedTypeForSelector{Selector: selector, Value: previousValue.Kind()}, got, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindNextAvailableIndexNodes(t *testing.T) {
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf([]interface{}{})
|
||||
selector := Selector{Current: "[0]", Index: 0}
|
||||
got, err := findNextAvailableIndexNodes(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}, got, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindNodesDynamic(t *testing.T) {
|
||||
t.Run("NilValue", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf(nil)
|
||||
selector := Selector{Raw: "."}
|
||||
got, err := findNodesDynamic(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnexpectedPreviousNilValue{Selector: "."}, got, err)
|
||||
})
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf([]interface{}{})
|
||||
selector := Selector{
|
||||
Current: "(name=x)",
|
||||
Conditions: []Condition{
|
||||
&EqualCondition{Key: "name", Value: "x"},
|
||||
},
|
||||
}
|
||||
got, err := findNodesDynamic(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &ValueNotFound{Selector: selector.Current, PreviousValue: previousValue}, got, err)
|
||||
})
|
||||
t.Run("NotFoundWithCreate", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf([]interface{}{})
|
||||
selector := Selector{
|
||||
Type: "NEXT_AVAILABLE_INDEX",
|
||||
Current: "(name=x)",
|
||||
Conditions: []Condition{
|
||||
&EqualCondition{Key: "name", Value: "x"},
|
||||
},
|
||||
}
|
||||
got, err := findNodesDynamic(selector, previousValue, true)
|
||||
if !assertQueryMultipleResult(t, []reflect.Value{nilValue()}, nil, got, err) {
|
||||
return
|
||||
}
|
||||
if exp, got := "NEXT_AVAILABLE_INDEX", selector.Type; exp != got {
|
||||
t.Errorf("expected type of %s, got %s", exp, got)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("UnsupportedCheckType", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf([]interface{}{
|
||||
1,
|
||||
})
|
||||
selector := Selector{
|
||||
Current: "(name=x)",
|
||||
Conditions: []Condition{
|
||||
&EqualCondition{Key: "name", Value: "x"},
|
||||
},
|
||||
}
|
||||
got, err := findNodesDynamic(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnhandledCheckType{Value: previousValue.Kind().String()}, got, err)
|
||||
})
|
||||
t.Run("UnsupportedType", func(t *testing.T) {
|
||||
previousValue := reflect.ValueOf(map[string]interface{}{})
|
||||
selector := Selector{
|
||||
Current: "(name=x)",
|
||||
Conditions: []Condition{
|
||||
&EqualCondition{Key: "name", Value: "x"},
|
||||
},
|
||||
}
|
||||
got, err := findNodesDynamic(selector, previousValue, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnsupportedTypeForSelector{Selector: selector, Value: previousValue.Kind()}, got, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindNodes(t *testing.T) {
|
||||
t.Run("UnsupportedSelector", func(t *testing.T) {
|
||||
previousNode := &Node{Value: reflect.ValueOf([]interface{}{})}
|
||||
selector := Selector{Raw: "BAD"}
|
||||
got, err := findNodes(selector, previousNode, false)
|
||||
assertQueryMultipleResult(t, []reflect.Value{}, &UnsupportedSelector{Selector: "BAD"}, got, err)
|
||||
})
|
||||
}
|
||||
357
node_test.go
357
node_test.go
@@ -1,6 +1,7 @@
|
||||
package dasel_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/tomwright/dasel"
|
||||
"github.com/tomwright/dasel/internal/storage"
|
||||
@@ -41,6 +42,13 @@ func TestParseSelector(t *testing.T) {
|
||||
t.Errorf("expected error %v, got %v", exp, err)
|
||||
}
|
||||
})
|
||||
t.Run("InvalidDynamicBracketCount", func(t *testing.T) {
|
||||
_, err := dasel.ParseSelector(".((name=x)")
|
||||
exp := dasel.ErrDynamicSelectorBracketMismatch
|
||||
if err == nil || !errors.Is(err, exp) {
|
||||
t.Errorf("expected error %v, got %v", exp, err)
|
||||
}
|
||||
})
|
||||
t.Run("InvalidDynamicComparison", func(t *testing.T) {
|
||||
_, err := dasel.ParseSelector(".(x<2)")
|
||||
exp := &dasel.UnknownComparisonOperatorErr{Operator: "<"}
|
||||
@@ -50,6 +58,260 @@ func TestParseSelector(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNode_QueryMultiple(t *testing.T) {
|
||||
value := []map[string]interface{}{
|
||||
{
|
||||
"name": "Tom",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
extractValues := func(nodes []*dasel.Node) []interface{} {
|
||||
gotValues := make([]interface{}, len(nodes))
|
||||
for i, n := range nodes {
|
||||
gotValues[i] = n.InterfaceValue()
|
||||
}
|
||||
return gotValues
|
||||
}
|
||||
t.Run("SingleResult", func(t *testing.T) {
|
||||
nodes, err := dasel.New(value).QueryMultiple(".[0].name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
gotValues := extractValues(nodes)
|
||||
expValues := []interface{}{
|
||||
"Tom",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expValues, gotValues) {
|
||||
t.Errorf("expected %v, got %v", expValues, gotValues)
|
||||
}
|
||||
})
|
||||
t.Run("SingleResultDynamic", func(t *testing.T) {
|
||||
nodes, err := dasel.New(value).QueryMultiple(".(age=25).name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
gotValues := extractValues(nodes)
|
||||
expValues := []interface{}{
|
||||
"Amelia",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expValues, gotValues) {
|
||||
t.Errorf("expected %v, got %v", expValues, gotValues)
|
||||
}
|
||||
})
|
||||
t.Run("MultipleResultDynamic", func(t *testing.T) {
|
||||
nodes, err := dasel.New(value).QueryMultiple(".(age=27).name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
gotValues := extractValues(nodes)
|
||||
expValues := []interface{}{
|
||||
"Tom",
|
||||
"Jim",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expValues, gotValues) {
|
||||
t.Errorf("expected %v, got %v", expValues, gotValues)
|
||||
}
|
||||
})
|
||||
t.Run("MultipleResultAnyIndex", func(t *testing.T) {
|
||||
nodes, err := dasel.New(value).QueryMultiple(".[*].name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
gotValues := extractValues(nodes)
|
||||
expValues := []interface{}{
|
||||
"Tom",
|
||||
"Jim",
|
||||
"Amelia",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expValues, gotValues) {
|
||||
t.Errorf("expected %v, got %v", expValues, gotValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNode_PutMultiple(t *testing.T) {
|
||||
t.Run("SingleResult", func(t *testing.T) {
|
||||
value := []map[string]interface{}{
|
||||
{
|
||||
"name": "Tom",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
err := dasel.New(value).PutMultiple(".[0].name", "Frank")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
exp := []map[string]interface{}{
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(exp, value) {
|
||||
t.Errorf("expected %v, got %v", exp, value)
|
||||
}
|
||||
})
|
||||
t.Run("SingleResultDynamic", func(t *testing.T) {
|
||||
value := []map[string]interface{}{
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
err := dasel.New(value).PutMultiple(".(age=25).name", "Frank")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
exp := []map[string]interface{}{
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(exp, value) {
|
||||
t.Errorf("expected %v, got %v", exp, value)
|
||||
}
|
||||
})
|
||||
t.Run("MultipleResultDynamic", func(t *testing.T) {
|
||||
value := []map[string]interface{}{
|
||||
{
|
||||
"name": "Tom",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
err := dasel.New(value).PutMultiple(".(age=27).name", "Frank")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
exp := []map[string]interface{}{
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(exp, value) {
|
||||
t.Errorf("expected %v, got %v", exp, value)
|
||||
}
|
||||
})
|
||||
t.Run("MultipleResultAnyIndex", func(t *testing.T) {
|
||||
value := []map[string]interface{}{
|
||||
{
|
||||
"name": "Tom",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Jim",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Amelia",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
err := dasel.New(value).PutMultiple(".[*].name", "Frank")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
exp := []map[string]interface{}{
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "27",
|
||||
},
|
||||
{
|
||||
"name": "Frank",
|
||||
"age": "25",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(exp, value) {
|
||||
t.Errorf("expected %v, got %v", exp, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNode_Query(t *testing.T) {
|
||||
parser, err := storage.NewParserFromFilename("./tests/assets/example.json")
|
||||
if err != nil {
|
||||
@@ -231,7 +493,29 @@ func putQueryTest(rootNode *dasel.Node, putSelector string, newValue interface{}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNode_Put(t *testing.T) {
|
||||
func putQueryMultipleTest(rootNode *dasel.Node, putSelector string, newValue interface{}, querySelector string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
err := rootNode.PutMultiple(putSelector, newValue)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected put error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
got, err := rootNode.QueryMultiple(querySelector)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, n := range got {
|
||||
if !reflect.DeepEqual(newValue, n.InterfaceValue()) {
|
||||
t.Errorf("expected %v, got %v", newValue, n.InterfaceValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNode_Put_Query(t *testing.T) {
|
||||
data := map[string]interface{}{
|
||||
"id": "123",
|
||||
"people": []map[string]interface{}{
|
||||
@@ -301,3 +585,74 @@ func TestNode_Put(t *testing.T) {
|
||||
t.Run("NilChainToListNextAvailableIndex", putQueryTest(dasel.New(nil), "my.favourite.people.[]", "Tom", "my.favourite.people.[0]"))
|
||||
t.Run("NilChainToDynamic", putQueryTest(dasel.New(nil), "(name=Jim).name", "Tom", "[0].name"))
|
||||
}
|
||||
|
||||
func TestNode_PutMultiple_Query(t *testing.T) {
|
||||
data := map[string]interface{}{
|
||||
"id": "123",
|
||||
"people": []map[string]interface{}{
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Tom",
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Jim",
|
||||
},
|
||||
},
|
||||
"names": []string{
|
||||
"Tom",
|
||||
"Jim",
|
||||
},
|
||||
}
|
||||
rootNode := dasel.New(data)
|
||||
|
||||
t.Run("InvalidSelector", func(t *testing.T) {
|
||||
err := rootNode.PutMultiple("people.[a].name", "Thomas")
|
||||
expErr := fmt.Errorf("failed to parse selector: %w", &dasel.InvalidIndexErr{Index: "a"})
|
||||
if err == nil {
|
||||
t.Errorf("expected err %v, got %v", expErr, err)
|
||||
return
|
||||
}
|
||||
if err.Error() != expErr.Error() {
|
||||
t.Errorf("expected err %v, got %v", expErr, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("ExistingSingleString", putQueryMultipleTest(rootNode, "id", "456", "id"))
|
||||
t.Run("ExistingStringValue", putQueryMultipleTest(rootNode, "people.[0].name", "Thomas", "people.(id=1).name"))
|
||||
t.Run("ExistingIntValue", putQueryMultipleTest(rootNode, "people.(id=1).id", 3, "people.(id=3).id"))
|
||||
t.Run("NewPropertyOnExistingObject", putQueryMultipleTest(rootNode, "people.(id=3).age", 27, "people.[0].age"))
|
||||
t.Run("AppendObjectToList", func(t *testing.T) {
|
||||
err := rootNode.PutMultiple("people.[]", map[string]interface{}{
|
||||
"id": 1,
|
||||
"name": "Bob",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected put error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
got, err := rootNode.Query("people.[2].id")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query [1] error: %v", err)
|
||||
return
|
||||
}
|
||||
if exp, got := 1, got.InterfaceValue().(int); exp != got {
|
||||
t.Errorf("expected %d, got %d", exp, got)
|
||||
}
|
||||
got, err = rootNode.Query("people.[2].name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected query [2] error: %v", err)
|
||||
return
|
||||
}
|
||||
if exp, got := "Bob", got.InterfaceValue().(string); exp != got {
|
||||
t.Errorf("expected %s, got %s", exp, got)
|
||||
}
|
||||
})
|
||||
t.Run("AppendStringToList", putQueryMultipleTest(rootNode, "names.[]", "Bob", "names.[2]"))
|
||||
t.Run("NilRootNode", putQueryMultipleTest(dasel.New(nil), "name", "Thomas", "name"))
|
||||
t.Run("NilChain", putQueryMultipleTest(dasel.New(nil), "my.name", "Thomas", "my.name"))
|
||||
t.Run("NilChainToListIndex", putQueryMultipleTest(dasel.New(nil), "my.favourite.people.[0]", "Tom", "my.favourite.people.[0]"))
|
||||
t.Run("NilChainToListNextAvailableIndex", putQueryMultipleTest(dasel.New(nil), "my.favourite.people.[]", "Tom", "my.favourite.people.[0]"))
|
||||
t.Run("NilChainToDynamic", putQueryMultipleTest(dasel.New(nil), "(name=Jim).name", "Tom", "[0].name"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user