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) {
key := rawKey.unwrapDocument().Copy()
key.Parent = n
key.SetParent(n)
key.IsMapKey = true
value := rawValue.unwrapDocument().Copy()
value.Parent = n
value.SetParent(n)
value.Key = key
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) {
log.Debugf("GetValueRep for %v value: %v", n.GetNicePath(), n.Value)
realTag := n.guessTagFromCustomType()
@ -262,14 +301,6 @@ func (n *CandidateNode) CreateReplacementWithDocWrappers(kind Kind, tag string,
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 {
return n.doCopy(true)
}
@ -280,9 +311,6 @@ func (n *CandidateNode) CopyWithoutContent() *CandidateNode {
func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
var content []*CandidateNode
if cloneContent {
content = n.CopyChildren()
}
var copyKey *CandidateNode
if n.Key != nil {
@ -323,8 +351,8 @@ func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
IsMapKey: n.IsMapKey,
}
for _, newChild := range content {
newChild.Parent = clone
if cloneContent {
clone.AddChildren(n.Content)
}
return clone
@ -340,7 +368,9 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences
n.Style = other.Style
}
n.Content = other.CopyChildren()
n.Content = make([]*CandidateNode, 0)
n.AddChildren(other.Content)
n.Kind = other.Kind
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.
## 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
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:
```yaml
a:
b:
- name: dog
- name: cat
{a: {b: [{name: dog}, {name: cat}]}}
```
then
```bash
@ -106,17 +103,13 @@ pathEnv=".a.b[0].name" valueEnv="moo" yq 'eval(strenv(pathEnv)) = strenv(valueE
```
will output
```yaml
a:
b:
- name: moo
- name: cat
{a: {b: [{name: moo}, {name: cat}]}}
```
## Dynamic key lookup with environment variable
Given a sample.yml file of:
```yaml
cat: meow
dog: woof
{cat: meow, dog: woof}
```
then
```bash
@ -220,7 +213,7 @@ Error: variable ${myEmptyEnv} set but empty
## Replace string environment variable in document
Given a sample.yml file of:
```yaml
v: ${myenv}
{v: '${myenv}'}
```
then
```bash
@ -228,7 +221,7 @@ myenv="cat meow" yq '.v |= envsubst' sample.yml
```
will output
```yaml
v: cat meow
{v: 'cat meow'}
```
## (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)
## 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
Given a sample.yml file of:
```yaml
pathExp: .a.b[] | select(.name == "cat")
a:
b:
- name: dog
- name: cat
{pathExp: '.a.b[] | select(.name == "cat")', a: {b: [{name: dog}, {name: cat}]}}
```
then
```bash
@ -21,7 +17,7 @@ yq 'eval(.pathExp)' sample.yml
```
will output
```yaml
name: cat
{name: cat}
```
## 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:
```yaml
a:
b:
- name: dog
- name: cat
{a: {b: [{name: dog}, {name: cat}]}}
```
then
```bash
@ -40,9 +33,6 @@ pathEnv=".a.b[0].name" valueEnv="moo" yq 'eval(strenv(pathEnv)) = strenv(valueE
```
will output
```yaml
a:
b:
- name: moo
- name: cat
{a: {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
Given a sample.yml file of:
```yaml
a: cat
{a: cat}
```
then
```bash
@ -27,7 +27,7 @@ sample.yml
## Get file index
Given a sample.yml file of:
```yaml
a: cat
{a: cat}
```
then
```bash
@ -41,11 +41,11 @@ will output
## Get file indices of multiple documents
Given a sample.yml file of:
```yaml
a: cat
{a: cat}
```
And another sample another.yml file of:
```yaml
a: cat
{a: cat}
```
then
```bash
@ -61,7 +61,7 @@ will output
## Get file index alias
Given a sample.yml file of:
```yaml
a: cat
{a: cat}
```
then
```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
Given a sample.yml file of:
```yaml
a:
b: apple
{a: {b: apple}}
```
then
```bash
@ -14,7 +13,7 @@ yq '.a' sample.yml
```
will output
```yaml
b: apple
{b: apple}
```
## Splat
@ -22,8 +21,7 @@ Often used to pipe children into other operators
Given a sample.yml file of:
```yaml
- b: apple
- c: banana
[{b: apple}, {c: banana}]
```
then
```bash
@ -31,8 +29,8 @@ yq '.[]' sample.yml
```
will output
```yaml
b: apple
c: banana
{b: apple}
{c: banana}
```
## 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:
```yaml
cat
"cat"
```
then
```bash
@ -55,7 +53,7 @@ Use quotes with square brackets around path elements with special characters
Given a sample.yml file of:
```yaml
"{}": frog
{"{}": frog}
```
then
```bash
@ -87,7 +85,7 @@ Use quotes with square brackets around path elements with special characters
Given a sample.yml file of:
```yaml
"red rabbit": frog
{"red rabbit": frog}
```
then
```bash
@ -103,9 +101,7 @@ Expressions within [] can be used to dynamically lookup / calculate keys
Given a sample.yml file of:
```yaml
b: apple
apple: crispy yum
banana: soft yum
{b: apple, apple: crispy yum, banana: soft yum}
```
then
```bash
@ -121,7 +117,7 @@ Nodes are added dynamically while traversing
Given a sample.yml file of:
```yaml
c: banana
{c: banana}
```
then
```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:
```yaml
- 1
- 2
- 3
[1, 2, 3]
```
then
```bash
@ -152,9 +146,7 @@ will output
## Wildcard matching
Given a sample.yml file of:
```yaml
a:
cat: apple
mad: things
{a: {cat: apple, mad: things}}
```
then
```bash
@ -169,9 +161,7 @@ things
## Aliases
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
{a: &cat {c: frog}, b: *cat}
```
then
```bash
@ -185,9 +175,7 @@ will output
## Traversing aliases with splat
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
{a: &cat {c: frog}, b: *cat}
```
then
```bash
@ -201,9 +189,7 @@ frog
## Traversing aliases explicitly
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
{a: &cat {c: frog}, b: *cat}
```
then
```bash
@ -217,9 +203,7 @@ frog
## Traversing arrays by index
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
[1, 2, 3]
```
then
```bash
@ -247,7 +231,7 @@ cat
## Maps with numeric keys
Given a sample.yml file of:
```yaml
2: cat
{2: cat}
```
then
```bash
@ -261,7 +245,7 @@ cat
## Maps with non existing numeric keys
Given a sample.yml file of:
```yaml
a: b
{a: b}
```
then
```bash
@ -468,10 +452,7 @@ foobarList_c
## Select multiple indices
Given a sample.yml file of:
```yaml
a:
- a
- b
- c
{a: [a, b, c]}
```
then
```bash

View File

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

View File

@ -11,7 +11,7 @@ var alternativeOperatorScenarios = []expressionScenario{
expression: `.b // .c`,
document: `a: bridge`,
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.Style = node.Alias.Style
node.Tag = node.Alias.Tag
node.Content = node.Alias.CopyChildren()
node.AddChildren(node.Alias.Content)
node.Value = node.Alias.Value
node.Alias = nil
}

View File

@ -34,198 +34,227 @@ thingTwo:
!!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{
// {
// 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: "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,
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",
},
},
// {
// skipDoc: true,
// document: `{f : {a: &a cat, b: &b {foo: *a}, *a: *b}}`,
// expression: `explode(.f)`,
// description: "Dont explode alias and anchor - check alias parent",
// skipDoc: true,
// document: `{a: &a [1], b: *a}`,
// expression: `.b[]`,
// expected: []string{
// "D0, P[], (doc)::{f: {a: cat, b: {foo: cat}, cat: {foo: cat}}}\n",
// "D0, P[b], (!!str)::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",
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() {
resultC := result.Value.(*CandidateNode)
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
@ -64,7 +65,8 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() {
resultC := result.Value.(*CandidateNode)
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())

View File

@ -5,6 +5,20 @@ import (
)
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,
document: `[{name: cat}, {name: dog}]`,

View File

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

View File

@ -4,6 +4,7 @@ import (
"container/list"
"fmt"
"os"
"strings"
parse "github.com/a8m/envsubst/parse"
)
@ -31,9 +32,10 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
Value: 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 {
decoder := NewYamlDecoder(ConfiguredYamlPreferences)
decoder.Init(strings.NewReader(rawValue))
result, err := decoder.Decode()
if err != nil {

View File

@ -157,7 +157,7 @@ var envOperatorScenarios = []expressionScenario{
document: "# abc\n{v: \"${myenv}\"}\n# xyz\n",
expression: `(.. | select(tag == "!!str")) |= envsubst`,
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
log.Debugf("-- isEquals cross function")
if lhs == nil && rhs == nil {
log.Debugf("-- both are nil")
owner := &CandidateNode{}
return createBooleanCandidate(owner, !flip), nil
} else if lhs == nil {

View File

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

View File

@ -203,7 +203,13 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*CandidateNode
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)
}
@ -235,7 +241,11 @@ func traverseMap(context Context, matchingNode *CandidateNode, keyNode *Candidat
if !splat && !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 {
log.Debugf("no matches, creating one")
//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 {
matchingNode.Style = 0

View File

@ -35,6 +35,30 @@ steps:
`
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,
description: "splat empty map",