This commit is contained in:
Mike Farah 2023-05-05 14:13:18 +10:00
parent cde32c156b
commit 68df67f550
20 changed files with 820 additions and 566 deletions

View File

@ -180,16 +180,55 @@ func (n *CandidateNode) AsList() *list.List {
func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) { func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) {
key := rawKey.unwrapDocument().Copy() key := rawKey.unwrapDocument().Copy()
key.Parent = n key.SetParent(n)
key.IsMapKey = true key.IsMapKey = true
value := rawValue.unwrapDocument().Copy() value := rawValue.unwrapDocument().Copy()
value.Parent = n value.SetParent(n)
value.Key = key value.Key = key
n.Content = append(n.Content, key, value) n.Content = append(n.Content, key, value)
} }
func (n *CandidateNode) SetParent(parent *CandidateNode) {
n.Parent = parent
n.Document = parent.Document
n.Filename = parent.Filename
n.FileIndex = parent.FileIndex
}
func (n *CandidateNode) AddChildren(children []*CandidateNode) {
if n.Kind == MappingNode {
for i := 0; i < len(children); i += 2 {
key := children[i]
value := children[i+1]
keyClone := key.Copy()
keyClone.SetParent(n)
valueClone := value.Copy()
valueClone.SetParent(n)
valueClone.Key = keyClone
n.Content = append(n.Content, keyClone, valueClone)
}
} else {
for _, rawChild := range children {
value := rawChild.unwrapDocument().Copy()
value.SetParent(n)
if value.Key != nil {
value.Key.SetParent(n)
} else {
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
}
n.Content = append(n.Content, value)
}
}
}
func (n *CandidateNode) GetValueRep() (interface{}, error) { func (n *CandidateNode) GetValueRep() (interface{}, error) {
log.Debugf("GetValueRep for %v value: %v", n.GetNicePath(), n.Value) log.Debugf("GetValueRep for %v value: %v", n.GetNicePath(), n.Value)
realTag := n.guessTagFromCustomType() realTag := n.guessTagFromCustomType()
@ -262,14 +301,6 @@ func (n *CandidateNode) CreateReplacementWithDocWrappers(kind Kind, tag string,
return replacement return replacement
} }
func (n *CandidateNode) CopyChildren() []*CandidateNode {
clonedKids := make([]*CandidateNode, len(n.Content))
for i, child := range n.Content {
clonedKids[i] = child.Copy()
}
return clonedKids
}
func (n *CandidateNode) Copy() *CandidateNode { func (n *CandidateNode) Copy() *CandidateNode {
return n.doCopy(true) return n.doCopy(true)
} }
@ -280,9 +311,6 @@ func (n *CandidateNode) CopyWithoutContent() *CandidateNode {
func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode { func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
var content []*CandidateNode var content []*CandidateNode
if cloneContent {
content = n.CopyChildren()
}
var copyKey *CandidateNode var copyKey *CandidateNode
if n.Key != nil { if n.Key != nil {
@ -323,8 +351,8 @@ func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
IsMapKey: n.IsMapKey, IsMapKey: n.IsMapKey,
} }
for _, newChild := range content { if cloneContent {
newChild.Parent = clone clone.AddChildren(n.Content)
} }
return clone return clone
@ -340,7 +368,9 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences
n.Style = other.Style n.Style = other.Style
} }
n.Content = other.CopyChildren() n.Content = make([]*CandidateNode, 0)
n.AddChildren(other.Content)
n.Kind = other.Kind n.Kind = other.Kind
n.Value = other.Value n.Value = other.Value

View File

@ -5,6 +5,286 @@ Use the `alias` and `anchor` operators to read and write yaml aliases and anchor
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag. `yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
## Merge one map
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<: *CENTER
r: 10
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
x: 1
y: 2
r: 10
```
## Merge multiple maps
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *CENTER
- *BIG
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Override
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *BIG
- *LEFT
- *SMALL
x: 1
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Get anchor
Given a sample.yml file of:
```yaml
a: &billyBob cat
```
then
```bash
yq '.a | anchor' sample.yml
```
will output
```yaml
billyBob
```
## Set anchor
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a anchor = "foobar"' sample.yml
```
will output
```yaml
a: &foobar cat
```
## Set anchor relatively using assign-update
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq '.a anchor |= .b' sample.yml
```
will output
```yaml
a: &cat
b: cat
```
## Get alias
Given a sample.yml file of:
```yaml
{b: &billyBob meow, a: *billyBob}
```
then
```bash
yq '.a | alias' sample.yml
```
will output
```yaml
billyBob
```
## Set alias
Given a sample.yml file of:
```yaml
{b: &meow purr, a: cat}
```
then
```bash
yq '.a alias = "meow"' sample.yml
```
will output
```yaml
{b: &meow purr, a: *meow}
```
## Set alias to blank does nothing
Given a sample.yml file of:
```yaml
{b: &meow purr, a: cat}
```
then
```bash
yq '.a alias = ""' sample.yml
```
will output
```yaml
{b: &meow purr, a: cat}
```
## Set alias relatively using assign-update
Given a sample.yml file of:
```yaml
{b: &meow purr, a: {f: meow}}
```
then
```bash
yq '.a alias |= .f' sample.yml
```
will output
```yaml
{b: &meow purr, a: *meow}
```
## Explode alias and anchor
Given a sample.yml file of:
```yaml
{f: {a: &a cat, b: *a}}
```
then
```bash
yq 'explode(.f)' sample.yml
```
will output
```yaml
{f: {a: cat, b: cat}}
```
## Explode with no aliases or anchors
Given a sample.yml file of:
```yaml
a: mike
```
then
```bash
yq 'explode(.a)' sample.yml
```
will output
```yaml
a: mike
```
## Explode with alias keys
Given a sample.yml file of:
```yaml
{f: {a: &a cat, *a: b}}
```
then
```bash
yq 'explode(.f)' sample.yml
```
will output
```yaml
{f: {a: cat, cat: b}}
```
## Explode with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
thing: foo_thing
c: foobarList_c
a: foo_a
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```
## Dereference and update a field ## Dereference and update a field
Use explode with multiply to dereference an object Use explode with multiply to dereference an object

View File

@ -95,10 +95,7 @@ The env variable can be any valid yq expression.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: [{name: dog}, {name: cat}]}}
b:
- name: dog
- name: cat
``` ```
then then
```bash ```bash
@ -106,17 +103,13 @@ pathEnv=".a.b[0].name" valueEnv="moo" yq 'eval(strenv(pathEnv)) = strenv(valueE
``` ```
will output will output
```yaml ```yaml
a: {a: {b: [{name: moo}, {name: cat}]}}
b:
- name: moo
- name: cat
``` ```
## Dynamic key lookup with environment variable ## Dynamic key lookup with environment variable
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
cat: meow {cat: meow, dog: woof}
dog: woof
``` ```
then then
```bash ```bash
@ -220,7 +213,7 @@ Error: variable ${myEmptyEnv} set but empty
## Replace string environment variable in document ## Replace string environment variable in document
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
v: ${myenv} {v: '${myenv}'}
``` ```
then then
```bash ```bash
@ -228,7 +221,7 @@ myenv="cat meow" yq '.v |= envsubst' sample.yml
``` ```
will output will output
```yaml ```yaml
v: cat meow {v: 'cat meow'}
``` ```
## (Default) Return all envsubst errors ## (Default) Return all envsubst errors

View File

@ -21,113 +21,3 @@ The not equals `!=` operator returns `false` if the LHS is equal to the RHS.
- select operator [here](https://mikefarah.gitbook.io/yq/operators/select) - select operator [here](https://mikefarah.gitbook.io/yq/operators/select)
## Match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq '.[] | (. == "*at")' sample.yml
```
will output
```yaml
true
true
false
```
## Don't match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq '.[] | (. != "*at")' sample.yml
```
will output
```yaml
false
false
true
```
## Match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq '.[] | (. == 4)' sample.yml
```
will output
```yaml
false
true
false
```
## Don't match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq '.[] | (. != 4)' sample.yml
```
will output
```yaml
true
false
true
```
## Match nulls
Running
```bash
yq --null-input 'null == ~'
```
will output
```yaml
true
```
## Non existent key doesn't equal a value
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq 'select(.b != "thing")' sample.yml
```
will output
```yaml
a: frog
```
## Two non existent keys are equal
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq 'select(.b == .c)' sample.yml
```
will output
```yaml
a: frog
```

View File

@ -9,11 +9,7 @@ Tip: This can be a useful way to parameterise complex scripts.
## Dynamically evaluate a path ## Dynamically evaluate a path
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
pathExp: .a.b[] | select(.name == "cat") {pathExp: '.a.b[] | select(.name == "cat")', a: {b: [{name: dog}, {name: cat}]}}
a:
b:
- name: dog
- name: cat
``` ```
then then
```bash ```bash
@ -21,7 +17,7 @@ yq 'eval(.pathExp)' sample.yml
``` ```
will output will output
```yaml ```yaml
name: cat {name: cat}
``` ```
## Dynamically update a path from an environment variable ## Dynamically update a path from an environment variable
@ -29,10 +25,7 @@ The env variable can be any valid yq expression.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: [{name: dog}, {name: cat}]}}
b:
- name: dog
- name: cat
``` ```
then then
```bash ```bash
@ -40,9 +33,6 @@ pathEnv=".a.b[0].name" valueEnv="moo" yq 'eval(strenv(pathEnv)) = strenv(valueE
``` ```
will output will output
```yaml ```yaml
a: {a: {b: [{name: moo}, {name: cat}]}}
b:
- name: moo
- name: cat
``` ```

View File

@ -13,7 +13,7 @@ yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file
## Get filename ## Get filename
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat}
``` ```
then then
```bash ```bash
@ -27,7 +27,7 @@ sample.yml
## Get file index ## Get file index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat}
``` ```
then then
```bash ```bash
@ -41,11 +41,11 @@ will output
## Get file indices of multiple documents ## Get file indices of multiple documents
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat}
``` ```
And another sample another.yml file of: And another sample another.yml file of:
```yaml ```yaml
a: cat {a: cat}
``` ```
then then
```bash ```bash
@ -61,7 +61,7 @@ will output
## Get file index alias ## Get file index alias
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat}
``` ```
then then
```bash ```bash

View File

@ -5,8 +5,7 @@ This is the simplest (and perhaps most used) operator. It is used to navigate de
## Simple map navigation ## Simple map navigation
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: apple}}
b: apple
``` ```
then then
```bash ```bash
@ -14,7 +13,7 @@ yq '.a' sample.yml
``` ```
will output will output
```yaml ```yaml
b: apple {b: apple}
``` ```
## Splat ## Splat
@ -22,8 +21,7 @@ Often used to pipe children into other operators
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- b: apple [{b: apple}, {c: banana}]
- c: banana
``` ```
then then
```bash ```bash
@ -31,8 +29,8 @@ yq '.[]' sample.yml
``` ```
will output will output
```yaml ```yaml
b: apple {b: apple}
c: banana {c: banana}
``` ```
## Optional Splat ## Optional Splat
@ -40,7 +38,7 @@ Just like splat, but won't error if you run it against scalars
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
cat "cat"
``` ```
then then
```bash ```bash
@ -55,7 +53,7 @@ Use quotes with square brackets around path elements with special characters
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
"{}": frog {"{}": frog}
``` ```
then then
```bash ```bash
@ -87,7 +85,7 @@ Use quotes with square brackets around path elements with special characters
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
"red rabbit": frog {"red rabbit": frog}
``` ```
then then
```bash ```bash
@ -103,9 +101,7 @@ Expressions within [] can be used to dynamically lookup / calculate keys
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
b: apple {b: apple, apple: crispy yum, banana: soft yum}
apple: crispy yum
banana: soft yum
``` ```
then then
```bash ```bash
@ -121,7 +117,7 @@ Nodes are added dynamically while traversing
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
c: banana {c: banana}
``` ```
then then
```bash ```bash
@ -137,9 +133,7 @@ Like jq, does not output an error when the yaml is not an array or object as exp
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 1 [1, 2, 3]
- 2
- 3
``` ```
then then
```bash ```bash
@ -152,9 +146,7 @@ will output
## Wildcard matching ## Wildcard matching
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {cat: apple, mad: things}}
cat: apple
mad: things
``` ```
then then
```bash ```bash
@ -169,9 +161,7 @@ things
## Aliases ## Aliases
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: &cat {a: &cat {c: frog}, b: *cat}
c: frog
b: *cat
``` ```
then then
```bash ```bash
@ -185,9 +175,7 @@ will output
## Traversing aliases with splat ## Traversing aliases with splat
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: &cat {a: &cat {c: frog}, b: *cat}
c: frog
b: *cat
``` ```
then then
```bash ```bash
@ -201,9 +189,7 @@ frog
## Traversing aliases explicitly ## Traversing aliases explicitly
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: &cat {a: &cat {c: frog}, b: *cat}
c: frog
b: *cat
``` ```
then then
```bash ```bash
@ -217,9 +203,7 @@ frog
## Traversing arrays by index ## Traversing arrays by index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 1 [1, 2, 3]
- 2
- 3
``` ```
then then
```bash ```bash
@ -247,7 +231,7 @@ cat
## Maps with numeric keys ## Maps with numeric keys
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
2: cat {2: cat}
``` ```
then then
```bash ```bash
@ -261,7 +245,7 @@ cat
## Maps with non existing numeric keys ## Maps with non existing numeric keys
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: b {a: b}
``` ```
then then
```bash ```bash
@ -468,10 +452,7 @@ foobarList_c
## Select multiple indices ## Select multiple indices
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: [a, b, c]}
- a
- b
- c
``` ```
then then
```bash ```bash

View File

@ -170,7 +170,8 @@ func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode)
return err return err
} }
target.Content = append(lhs.CopyChildren(), extraNodes...) target.AddChildren(lhs.Content)
target.AddChildren(extraNodes)
return nil return nil
} }

View File

@ -11,7 +11,7 @@ var alternativeOperatorScenarios = []expressionScenario{
expression: `.b // .c`, expression: `.b // .c`,
document: `a: bridge`, document: `a: bridge`,
expected: []string{ expected: []string{
"D0, P[], (!!null)::null\n", "D0, P[c], (!!null)::null\n",
}, },
}, },
{ {

View File

@ -199,7 +199,7 @@ func explodeNode(node *CandidateNode, context Context) error {
node.Kind = node.Alias.Kind node.Kind = node.Alias.Kind
node.Style = node.Alias.Style node.Style = node.Alias.Style
node.Tag = node.Alias.Tag node.Tag = node.Alias.Tag
node.Content = node.Alias.CopyChildren() node.AddChildren(node.Alias.Content)
node.Value = node.Alias.Value node.Value = node.Alias.Value
node.Alias = nil node.Alias = nil
} }

View File

@ -34,198 +34,227 @@ thingTwo:
!!merge <<: *item_value !!merge <<: *item_value
` `
var explodeMergeAnchorsExpected = `D0, P[], (doc)::foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
thing: foo_thing
c: foobarList_c
a: foo_a
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
`
var anchorOperatorScenarios = []expressionScenario{ var anchorOperatorScenarios = []expressionScenario{
{
skipDoc: true,
description: "merge anchor not map",
document: "a: &a\n - 0\nc:\n <<: [*a]\n",
expectedError: "merge anchor only supports maps, got !!seq instead",
expression: "explode(.)",
},
{
description: "Merge one map",
subdescription: "see https://yaml.org/type/merge.html",
document: specDocument + "- << : *CENTER\n r: 10\n",
expression: ".[4] | explode(.)",
expected: []string{expectedSpecResult},
},
{
description: "Merge multiple maps",
subdescription: "see https://yaml.org/type/merge.html",
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
},
{
description: "Override",
subdescription: "see https://yaml.org/type/merge.html",
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
},
{
description: "Get anchor",
document: `a: &billyBob cat`,
expression: `.a | anchor`,
expected: []string{
"D0, P[a], (!!str)::billyBob\n",
},
},
{
description: "Set anchor",
document: `a: cat`,
expression: `.a anchor = "foobar"`,
expected: []string{
"D0, P[], (doc)::a: &foobar cat\n",
},
},
{
description: "Set anchor relatively using assign-update",
document: `a: {b: cat}`,
expression: `.a anchor |= .b`,
expected: []string{
"D0, P[], (doc)::a: &cat {b: cat}\n",
},
},
{
skipDoc: true,
document: `a: {c: cat}`,
expression: `.a anchor |= .b`,
expected: []string{
"D0, P[], (doc)::a: {c: cat}\n",
},
},
{
skipDoc: true,
document: `a: {c: cat}`,
expression: `.a anchor = .b`,
expected: []string{
"D0, P[], (doc)::a: {c: cat}\n",
},
},
{
description: "Get alias",
document: `{b: &billyBob meow, a: *billyBob}`,
expression: `.a | alias`,
expected: []string{
"D0, P[a], (!!str)::billyBob\n",
},
},
{
description: "Set alias",
document: `{b: &meow purr, a: cat}`,
expression: `.a alias = "meow"`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
},
},
{
description: "Set alias to blank does nothing",
document: `{b: &meow purr, a: cat}`,
expression: `.a alias = ""`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: cat}\n",
},
},
{
skipDoc: true,
document: `{b: &meow purr, a: cat}`,
expression: `.a alias = .c`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: cat}\n",
},
},
{
skipDoc: true,
document: `{b: &meow purr, a: cat}`,
expression: `.a alias |= .c`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: cat}\n",
},
},
{
description: "Set alias relatively using assign-update",
document: `{b: &meow purr, a: {f: meow}}`,
expression: `.a alias |= .f`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
},
},
// { // {
// description: "Dont explode alias and anchor - check alias parent",
// skipDoc: true, // skipDoc: true,
// description: "merge anchor not map", // document: `{a: &a [1], b: *a}`,
// document: "a: &a\n - 0\nc:\n <<: [*a]\n", // expression: `.b[]`,
// expectedError: "merge anchor only supports maps, got !!seq instead",
// expression: "explode(.)",
// },
// {
// description: "Merge one map",
// subdescription: "see https://yaml.org/type/merge.html",
// document: specDocument + "- << : *CENTER\n r: 10\n",
// expression: ".[4] | explode(.)",
// expected: []string{expectedSpecResult},
// },
// {
// description: "Merge multiple maps",
// subdescription: "see https://yaml.org/type/merge.html",
// document: specDocument + "- << : [ *CENTER, *BIG ]\n",
// expression: ".[4] | explode(.)",
// expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
// },
// {
// description: "Override",
// subdescription: "see https://yaml.org/type/merge.html",
// document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
// expression: ".[4] | explode(.)",
// expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
// },
// {
// description: "Get anchor",
// document: `a: &billyBob cat`,
// expression: `.a | anchor`,
// expected: []string{ // expected: []string{
// "D0, P[a], (!!str)::billyBob\n", // "D0, P[b], (!!str)::cat\n",
// },
// },
// {
// description: "Set anchor",
// document: `a: cat`,
// expression: `.a anchor = "foobar"`,
// expected: []string{
// "D0, P[], (doc)::a: &foobar cat\n",
// },
// },
// {
// description: "Set anchor relatively using assign-update",
// document: `a: {b: cat}`,
// expression: `.a anchor |= .b`,
// expected: []string{
// "D0, P[], (doc)::a: &cat {b: cat}\n",
// },
// },
// {
// skipDoc: true,
// document: `a: {c: cat}`,
// expression: `.a anchor |= .b`,
// expected: []string{
// "D0, P[], (doc)::a: {c: cat}\n",
// },
// },
// {
// skipDoc: true,
// document: `a: {c: cat}`,
// expression: `.a anchor = .b`,
// expected: []string{
// "D0, P[], (doc)::a: {c: cat}\n",
// },
// },
// {
// description: "Get alias",
// document: `{b: &billyBob meow, a: *billyBob}`,
// expression: `.a | alias`,
// expected: []string{
// "D0, P[a], (!!str)::billyBob\n",
// },
// },
// {
// description: "Set alias",
// document: `{b: &meow purr, a: cat}`,
// expression: `.a alias = "meow"`,
// expected: []string{
// "D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
// },
// },
// {
// description: "Set alias to blank does nothing",
// document: `{b: &meow purr, a: cat}`,
// expression: `.a alias = ""`,
// expected: []string{
// "D0, P[], (doc)::{b: &meow purr, a: cat}\n",
// },
// },
// {
// skipDoc: true,
// document: `{b: &meow purr, a: cat}`,
// expression: `.a alias = .c`,
// expected: []string{
// "D0, P[], (doc)::{b: &meow purr, a: cat}\n",
// },
// },
// {
// skipDoc: true,
// document: `{b: &meow purr, a: cat}`,
// expression: `.a alias |= .c`,
// expected: []string{
// "D0, P[], (doc)::{b: &meow purr, a: cat}\n",
// },
// },
// {
// description: "Set alias relatively using assign-update",
// document: `{b: &meow purr, a: {f: meow}}`,
// expression: `.a alias |= .f`,
// expected: []string{
// "D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
// },
// },
// {
// description: "Explode alias and anchor",
// document: `{f : {a: &a cat, b: *a}}`,
// expression: `explode(.f)`,
// expected: []string{
// "D0, P[], (doc)::{f: {a: cat, b: cat}}\n",
// },
// },
// {
// description: "Explode with no aliases or anchors",
// document: `a: mike`,
// expression: `explode(.a)`,
// expected: []string{
// "D0, P[], (doc)::a: mike\n",
// },
// },
// {
// description: "Explode with alias keys",
// document: `{f : {a: &a cat, *a: b}}`,
// expression: `explode(.f)`,
// expected: []string{
// "D0, P[], (doc)::{f: {a: cat, cat: b}}\n",
// },
// },
// {
// description: "Explode with merge anchors",
// document: mergeDocSample,
// expression: `explode(.)`,
// expected: []string{`D0, P[], (doc)::foo:
// a: foo_a
// thing: foo_thing
// c: foo_c
// bar:
// b: bar_b
// thing: bar_thing
// c: bar_c
// foobarList:
// b: bar_b
// thing: foo_thing
// c: foobarList_c
// a: foo_a
// foobar:
// c: foo_c
// a: foo_a
// thing: foobar_thing
// `},
// },
// {
// skipDoc: true,
// document: mergeDocSample,
// expression: `.foo* | explode(.) | (. style="flow")`,
// expected: []string{
// "D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
// "D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
// "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
// },
// },
// {
// skipDoc: true,
// document: mergeDocSample,
// expression: `.foo* | explode(explode(.)) | (. style="flow")`,
// expected: []string{
// "D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
// "D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
// "D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
// },
// },
// {
// skipDoc: true,
// document: `{f : {a: &a cat, b: &b {foo: *a}, *a: *b}}`,
// expression: `explode(.f)`,
// expected: []string{
// "D0, P[], (doc)::{f: {a: cat, b: {foo: cat}, cat: {foo: cat}}}\n",
// }, // },
// }, // },
{
description: "Explode alias and anchor - check alias parent",
skipDoc: true,
document: `{a: &a cat, b: *a}`,
expression: `explode(.) | .b`,
expected: []string{
"D0, P[b], (!!str)::cat\n",
},
},
{
description: "Explode alias and anchor - check original parent",
skipDoc: true,
document: `{a: &a cat, b: *a}`,
expression: `explode(.) | .a`,
expected: []string{
"D0, P[a], (!!str)::cat\n",
},
},
{
description: "Explode alias and anchor",
document: `{f : {a: &a cat, b: *a}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (doc)::{f: {a: cat, b: cat}}\n",
},
},
{
description: "Explode with no aliases or anchors",
document: `a: mike`,
expression: `explode(.a)`,
expected: []string{
"D0, P[], (doc)::a: mike\n",
},
},
{
description: "Explode with alias keys",
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (doc)::{f: {a: cat, cat: b}}\n",
},
},
{
description: "Explode with merge anchors",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsExpected},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: `{f : {a: &a cat, b: &b {foo: *a}, *a: *b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (doc)::{f: {a: cat, b: {foo: cat}, cat: {foo: cat}}}\n",
},
},
{ {
description: "Dereference and update a field", description: "Dereference and update a field",
subdescription: "Use explode with multiply to dereference an object", subdescription: "Use explode with multiply to dereference an object",

View File

@ -15,7 +15,8 @@ func collectTogether(d *dataTreeNavigator, context Context, expressionNode *Expr
for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() { for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() {
resultC := result.Value.(*CandidateNode) resultC := result.Value.(*CandidateNode)
log.Debugf("found this: %v", NodeToString(resultC)) log.Debugf("found this: %v", NodeToString(resultC))
collectedNode.Content = append(collectedNode.Content, resultC.unwrapDocument()) collectedNode.AddChildren([]*CandidateNode{resultC})
// collectedNode.Content = append(collectedNode.Content, resultC.unwrapDocument())
} }
} }
return collectedNode, nil return collectedNode, nil
@ -64,7 +65,8 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() { for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() {
resultC := result.Value.(*CandidateNode) resultC := result.Value.(*CandidateNode)
log.Debugf("found this: %v", NodeToString(resultC)) log.Debugf("found this: %v", NodeToString(resultC))
collectCandidate.Content = append(collectCandidate.Content, resultC.unwrapDocument()) collectCandidate.AddChildren([]*CandidateNode{resultC})
// collectCandidate.Content = append(collectCandidate.Content, resultC.unwrapDocument())
} }
log.Debugf("done collect rhs: %v", expressionNode.RHS.Operation.toString()) log.Debugf("done collect rhs: %v", expressionNode.RHS.Operation.toString())

View File

@ -5,6 +5,20 @@ import (
) )
var collectObjectOperatorScenarios = []expressionScenario{ var collectObjectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
expression: `{"name": "mike"} | .name`,
expected: []string{
"D0, P[name], (!!str)::mike\n",
},
},
{
skipDoc: true,
expression: `{"person": {"names": ["mike"]}} | .person.names[0]`,
expected: []string{
"D0, P[person names 0], (!!str)::mike\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `[{name: cat}, {name: dog}]`, document: `[{name: cat}, {name: dog}]`,

View File

@ -5,6 +5,13 @@ import (
) )
var collectOperatorScenarios = []expressionScenario{ var collectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
expression: `["x", "y"] | .[1]`,
expected: []string{
"D0, P[1], (!!str)::y\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: ``, document: ``,

View File

@ -4,6 +4,7 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"os" "os"
"strings"
parse "github.com/a8m/envsubst/parse" parse "github.com/a8m/envsubst/parse"
) )
@ -31,9 +32,10 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
Value: rawValue, Value: rawValue,
} }
} else if rawValue == "" { } else if rawValue == "" {
return Context{}, fmt.Errorf("Value for env variable '%v' not provided in env()", envName) return Context{}, fmt.Errorf("value for env variable '%v' not provided in env()", envName)
} else { } else {
decoder := NewYamlDecoder(ConfiguredYamlPreferences) decoder := NewYamlDecoder(ConfiguredYamlPreferences)
decoder.Init(strings.NewReader(rawValue))
result, err := decoder.Decode() result, err := decoder.Decode()
if err != nil { if err != nil {

View File

@ -157,7 +157,7 @@ var envOperatorScenarios = []expressionScenario{
document: "# abc\n{v: \"${myenv}\"}\n# xyz\n", document: "# abc\n{v: \"${myenv}\"}\n# xyz\n",
expression: `(.. | select(tag == "!!str")) |= envsubst`, expression: `(.. | select(tag == "!!str")) |= envsubst`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::# abc\n{v: \"cat meow\"}\n# xyz\n", "D0, P[], (doc)::# abc\n{v: \"cat meow\"}\n# xyz\n",
}, },
}, },
} }

View File

@ -10,6 +10,7 @@ func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *Candid
value := false value := false
log.Debugf("-- isEquals cross function") log.Debugf("-- isEquals cross function")
if lhs == nil && rhs == nil { if lhs == nil && rhs == nil {
log.Debugf("-- both are nil")
owner := &CandidateNode{} owner := &CandidateNode{}
return createBooleanCandidate(owner, !flip), nil return createBooleanCandidate(owner, !flip), nil
} else if lhs == nil { } else if lhs == nil {

View File

@ -12,181 +12,181 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[a], (!!bool)::true\n", "D0, P[a], (!!bool)::true\n",
}, },
}, },
{ // {
expression: `(.k | length) == 0`, // expression: `(.k | length) == 0`,
skipDoc: true, // skipDoc: true,
expected: []string{ // expected: []string{
"D0, P[k], (!!bool)::true\n", // "D0, P[k], (!!bool)::true\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: `a: cat`, // document: `a: cat`,
expression: ".a == .b", // expression: ".a == .b",
expected: []string{ // expected: []string{
"D0, P[a], (!!bool)::false\n", // "D0, P[a], (!!bool)::false\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: `a: cat`, // document: `a: cat`,
expression: ".b == .a", // expression: ".b == .a",
expected: []string{ // expected: []string{
"D0, P[b], (!!bool)::false\n", // "D0, P[b], (!!bool)::false\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "cat", // document: "cat",
document2: "dog", // document2: "dog",
expression: "select(fi==0) == select(fi==1)", // expression: "select(fi==0) == select(fi==1)",
expected: []string{ // expected: []string{
"D0, P[], (!!bool)::false\n", // "D0, P[], (!!bool)::false\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{}", // document: "{}",
expression: "(.a == .b) as $x | .", // expression: "(.a == .b) as $x | .",
expected: []string{ // expected: []string{
"D0, P[], (doc)::{}\n", // "D0, P[], (doc)::{}\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{}", // document: "{}",
expression: ".a == .b", // expression: ".a == .b",
expected: []string{ // expected: []string{
"D0, P[a], (!!bool)::true\n", // "D0, P[a], (!!bool)::true\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{}", // document: "{}",
expression: "(.a != .b) as $x | .", // expression: "(.a != .b) as $x | .",
expected: []string{ // expected: []string{
"D0, P[], (doc)::{}\n", // "D0, P[], (doc)::{}\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{}", // document: "{}",
expression: ".a != .b", // expression: ".a != .b",
expected: []string{ // expected: []string{
"D0, P[], (!!bool)::false\n", // "D0, P[], (!!bool)::false\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{a: {b: 10}}", // document: "{a: {b: 10}}",
expression: "select(.c != null)", // expression: "select(.c != null)",
expected: []string{}, // expected: []string{},
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{a: {b: 10}}", // document: "{a: {b: 10}}",
expression: "select(.d == .c)", // expression: "select(.d == .c)",
expected: []string{ // expected: []string{
"D0, P[], (doc)::{a: {b: 10}}\n", // "D0, P[], (doc)::{a: {b: 10}}\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{a: {b: 10}}", // document: "{a: {b: 10}}",
expression: "select(null == .c)", // expression: "select(null == .c)",
expected: []string{ // expected: []string{
"D0, P[], (doc)::{a: {b: 10}}\n", // "D0, P[], (doc)::{a: {b: 10}}\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: "{a: { b: {things: \"\"}, f: [1], g: [] }}", // document: "{a: { b: {things: \"\"}, f: [1], g: [] }}",
expression: ".. | select(. == \"\")", // expression: ".. | select(. == \"\")",
expected: []string{ // expected: []string{
"D0, P[a b things], (!!str)::\n", // "D0, P[a b things], (!!str)::\n",
}, // },
}, // },
{ // {
description: "Match string", // description: "Match string",
document: `[cat,goat,dog]`, // document: `[cat,goat,dog]`,
expression: `.[] | (. == "*at")`, // expression: `.[] | (. == "*at")`,
expected: []string{ // expected: []string{
"D0, P[0], (!!bool)::true\n", // "D0, P[0], (!!bool)::true\n",
"D0, P[1], (!!bool)::true\n", // "D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n", // "D0, P[2], (!!bool)::false\n",
}, // },
}, // },
{ // {
description: "Don't match string", // description: "Don't match string",
document: `[cat,goat,dog]`, // document: `[cat,goat,dog]`,
expression: `.[] | (. != "*at")`, // expression: `.[] | (. != "*at")`,
expected: []string{ // expected: []string{
"D0, P[0], (!!bool)::false\n", // "D0, P[0], (!!bool)::false\n",
"D0, P[1], (!!bool)::false\n", // "D0, P[1], (!!bool)::false\n",
"D0, P[2], (!!bool)::true\n", // "D0, P[2], (!!bool)::true\n",
}, // },
}, // },
{ // {
description: "Match number", // description: "Match number",
document: `[3, 4, 5]`, // document: `[3, 4, 5]`,
expression: `.[] | (. == 4)`, // expression: `.[] | (. == 4)`,
expected: []string{ // expected: []string{
"D0, P[0], (!!bool)::false\n", // "D0, P[0], (!!bool)::false\n",
"D0, P[1], (!!bool)::true\n", // "D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n", // "D0, P[2], (!!bool)::false\n",
}, // },
}, // },
{ // {
description: "Don't match number", // description: "Don't match number",
document: `[3, 4, 5]`, // document: `[3, 4, 5]`,
expression: `.[] | (. != 4)`, // expression: `.[] | (. != 4)`,
expected: []string{ // expected: []string{
"D0, P[0], (!!bool)::true\n", // "D0, P[0], (!!bool)::true\n",
"D0, P[1], (!!bool)::false\n", // "D0, P[1], (!!bool)::false\n",
"D0, P[2], (!!bool)::true\n", // "D0, P[2], (!!bool)::true\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, // document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
expression: `.a | (.[].b == "apple")`, // expression: `.a | (.[].b == "apple")`,
expected: []string{ // expected: []string{
"D0, P[a cat b], (!!bool)::true\n", // "D0, P[a cat b], (!!bool)::true\n",
"D0, P[a pat b], (!!bool)::false\n", // "D0, P[a pat b], (!!bool)::false\n",
}, // },
}, // },
{ // {
skipDoc: true, // skipDoc: true,
document: ``, // document: ``,
expression: `null == null`, // expression: `null == null`,
expected: []string{ // expected: []string{
"D0, P[], (!!bool)::true\n", // "D0, P[], (!!bool)::true\n",
}, // },
}, // },
{ // {
description: "Match nulls", // description: "Match nulls",
document: ``, // document: ``,
expression: `null == ~`, // expression: `null == ~`,
expected: []string{ // expected: []string{
"D0, P[], (!!bool)::true\n", // "D0, P[], (!!bool)::true\n",
}, // },
}, // },
{ // {
description: "Non existent key doesn't equal a value", // description: "Non existent key doesn't equal a value",
document: "a: frog", // document: "a: frog",
expression: `select(.b != "thing")`, // expression: `select(.b != "thing")`,
expected: []string{ // expected: []string{
"D0, P[], (doc)::a: frog\n", // "D0, P[], (doc)::a: frog\n",
}, // },
}, // },
{ // {
description: "Two non existent keys are equal", // description: "Two non existent keys are equal",
document: "a: frog", // document: "a: frog",
expression: `select(.b == .c)`, // expression: `select(.b == .c)`,
expected: []string{ // expected: []string{
"D0, P[], (doc)::a: frog\n", // "D0, P[], (doc)::a: frog\n",
}, // },
}, // },
} }
func TestEqualOperatorScenarios(t *testing.T) { func TestEqualOperatorScenarios(t *testing.T) {

View File

@ -203,7 +203,13 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*CandidateNode
node.Style = 0 node.Style = 0
} }
node.Content = append(node.Content, &CandidateNode{Tag: "!!null", Kind: ScalarNode, Value: "null"}) valueNode := node.CreateChild()
valueNode.Kind = ScalarNode
valueNode.Tag = "!!null"
valueNode.Value = "null"
valueNode.Key = createScalarNode(contentLength, fmt.Sprintf("%v", contentLength))
node.Content = append(node.Content, valueNode)
contentLength = len(node.Content) contentLength = len(node.Content)
} }
@ -235,7 +241,11 @@ func traverseMap(context Context, matchingNode *CandidateNode, keyNode *Candidat
if !splat && !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 { if !splat && !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 {
log.Debugf("no matches, creating one") log.Debugf("no matches, creating one")
//no matches, create one automagically //no matches, create one automagically
valueNode := &CandidateNode{Tag: "!!null", Kind: ScalarNode, Value: "null"} valueNode := matchingNode.CreateChild()
valueNode.Kind = ScalarNode
valueNode.Tag = "!!null"
valueNode.Value = "null"
valueNode.Key = keyNode
if len(matchingNode.Content) == 0 { if len(matchingNode.Content) == 0 {
matchingNode.Style = 0 matchingNode.Style = 0

View File

@ -35,6 +35,30 @@ steps:
` `
var traversePathOperatorScenarios = []expressionScenario{ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
description: "dynamically set parent and key",
expression: `.a.b.c = 3 | .a.b.c`,
expected: []string{
"D0, P[a b c], (!!int)::3\n",
},
},
{
skipDoc: true,
description: "dynamically set parent and key in array",
expression: `.a.b[0] = 3 | .a.b[0]`,
expected: []string{
"D0, P[a b 0], (!!int)::3\n",
},
},
{
skipDoc: true,
description: "dynamically set parent and key",
expression: `.a.b = ["x","y"] | .a.b[1]`,
expected: []string{
"D0, P[a b 1], (!!str)::y\n",
},
},
{ {
skipDoc: true, skipDoc: true,
description: "splat empty map", description: "splat empty map",