mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d612897e89 | ||
|
|
806f906041 | ||
|
|
e8b2f6e383 | ||
|
|
e419b5a39d | ||
|
|
a5b6e08282 | ||
|
|
2417c0ee8f | ||
|
|
d3b2e37b66 | ||
|
|
54723c1a36 | ||
|
|
0318c80d33 | ||
|
|
8980846632 | ||
|
|
0fb62d3ae1 | ||
|
|
85398727ad | ||
|
|
0bf3d781f6 |
@@ -11,7 +11,7 @@ var (
|
|||||||
GitDescribe string
|
GitDescribe string
|
||||||
|
|
||||||
// Version is main version number that is being run at the moment.
|
// Version is main version number that is being run at the moment.
|
||||||
Version = "4.3.2"
|
Version = "4.4.0"
|
||||||
|
|
||||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mikefarah/yq:4.3.2
|
FROM mikefarah/yq:4.4.0
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
@@ -78,17 +78,22 @@ b: dog # leave this
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Remove all comments
|
## Remove all comments
|
||||||
|
Note the use of `...` to ensure key nodes are included.
|
||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a: cat # comment
|
a: cat # comment
|
||||||
|
# great
|
||||||
|
b: # key comment
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.. comments=""' sample.yml
|
yq eval '... comments=""' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
a: cat
|
a: cat
|
||||||
|
b:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get line comment
|
## Get line comment
|
||||||
|
|||||||
35
pkg/yqlib/doc/Keys.md
Normal file
35
pkg/yqlib/doc/Keys.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Keys
|
||||||
|
|
||||||
|
Use the `keys` operator to return map keys or array indices.
|
||||||
|
## Map keys
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
dog: woof
|
||||||
|
cat: meow
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'keys' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- dog
|
||||||
|
- cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Array keys
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- apple
|
||||||
|
- banana
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'keys' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
```
|
||||||
|
|
||||||
@@ -111,6 +111,26 @@ b:
|
|||||||
- 5
|
- 5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Merge, only existing fields
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
thing: one
|
||||||
|
cat: frog
|
||||||
|
b:
|
||||||
|
missing: two
|
||||||
|
thing: two
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a *? .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
thing: two
|
||||||
|
cat: frog
|
||||||
|
```
|
||||||
|
|
||||||
## Merge, appending arrays
|
## Merge, appending arrays
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -143,6 +163,33 @@ array:
|
|||||||
value: banana
|
value: banana
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Merge, only existing fields, appending arrays
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
thing:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
b:
|
||||||
|
thing:
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
another:
|
||||||
|
- 1
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a *?+ .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
thing:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
```
|
||||||
|
|
||||||
## Merge to prefix an element
|
## Merge to prefix an element
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
31
pkg/yqlib/doc/Split into Documents.md
Normal file
31
pkg/yqlib/doc/Split into Documents.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Split into Documents
|
||||||
|
|
||||||
|
This operator splits all matches into separate documents
|
||||||
|
|
||||||
|
## Split empty
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
yq eval --null-input 'splitDoc'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Split array
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: cat
|
||||||
|
- b: dog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.[] | splitDoc' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
---
|
||||||
|
b: dog
|
||||||
|
```
|
||||||
|
|
||||||
52
pkg/yqlib/doc/String Operators.md
Normal file
52
pkg/yqlib/doc/String Operators.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# String Operators
|
||||||
|
|
||||||
|
## Join strings
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- cat
|
||||||
|
- meow
|
||||||
|
- 1
|
||||||
|
- null
|
||||||
|
- true
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'join("; ")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
cat; meow; 1; ; true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Split strings
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
cat; meow; 1; ; true
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'split("; ")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- cat
|
||||||
|
- meow
|
||||||
|
- "1"
|
||||||
|
- ""
|
||||||
|
- "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Split strings one match
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
word
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'split("; ")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- word
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Operators
|
||||||
|
|
||||||
|
In `yq` expressions are made up of operators. Operators have 0-2 arguments and run against the current 'matching' nodes in the expression tree.
|
||||||
|
|
||||||
|
Lets look at a couple of examples.
|
||||||
|
|
||||||
|
The `length` operator take no arguments, and will simply return the length of _each_ matching node. So if there were 2 nodes, one string and one array, length will update the 'matching' nodes context to be two new numeric scalar nodes representing the lengths of the orignal 'matching' nodes.
|
||||||
|
|
||||||
|
The `=` operator takes two arguments, a `lhs` expression and `rhs` expression. It runs the 'matching' nodes context against the `lhs` expression to find the nodes to update, lets call it `lhsNodes`, and then runs the matching nodes against the `rhs` to find the new values, lets call that `rhsNodes`. It updates the `lhsNodes` values with the `rhsNodes` values and _returns the original matching nodes_. This is important, where length changed the matching nodes to be new nodes with the length values, `=` returns the original matching nodes, albeit with some of the nodes values updated. So `.a = 3` will still return the parent matching node, but with the matching child updated.
|
||||||
|
|
||||||
|
Please see the individual operator docs for more information and examples.
|
||||||
|
|
||||||
|
|||||||
3
pkg/yqlib/doc/headers/Keys.md
Normal file
3
pkg/yqlib/doc/headers/Keys.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Keys
|
||||||
|
|
||||||
|
Use the `keys` operator to return map keys or array indices.
|
||||||
3
pkg/yqlib/doc/headers/Split into Documents.md
Normal file
3
pkg/yqlib/doc/headers/Split into Documents.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Split into Documents
|
||||||
|
|
||||||
|
This operator splits all matches into separate documents
|
||||||
1
pkg/yqlib/doc/headers/String Operators.md
Normal file
1
pkg/yqlib/doc/headers/String Operators.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# String Operators
|
||||||
@@ -53,9 +53,19 @@ var pathTests = []struct {
|
|||||||
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`d0.a`,
|
`.key.array + .key.array2`,
|
||||||
append(make([]interface{}, 0), "d0", "SHORT_PIPE", "a"),
|
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"),
|
||||||
append(make([]interface{}, 0), "d0", "a", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ADD"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.key.array * .key.array2`,
|
||||||
|
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "MULTIPLY", "key", "SHORT_PIPE", "array2"),
|
||||||
|
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "MULTIPLY"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.key.array // .key.array2`,
|
||||||
|
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ALTERNATIVE", "key", "SHORT_PIPE", "array2"),
|
||||||
|
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ALTERNATIVE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a | .[].b == "apple"`,
|
`.a | .[].b == "apple"`,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
lex "github.com/timtadh/lexmachine"
|
lex "github.com/timtadh/lexmachine"
|
||||||
"github.com/timtadh/lexmachine/machines"
|
"github.com/timtadh/lexmachine/machines"
|
||||||
@@ -65,21 +66,7 @@ func pathToken(wrapped bool) lex.Action {
|
|||||||
value = unwrap(value)
|
value = unwrap(value)
|
||||||
}
|
}
|
||||||
log.Debug("PathToken %v", value)
|
log.Debug("PathToken %v", value)
|
||||||
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value}
|
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}}
|
||||||
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func documentToken() lex.Action {
|
|
||||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
|
||||||
var numberString = string(m.Bytes)
|
|
||||||
numberString = numberString[1:]
|
|
||||||
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
|
|
||||||
if errParsingInt != nil {
|
|
||||||
return nil, errParsingInt
|
|
||||||
}
|
|
||||||
log.Debug("documentToken %v", string(m.Bytes))
|
|
||||||
op := &Operation{OperationType: documentFilterOpType, Value: number, StringValue: numberString}
|
|
||||||
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
|
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,6 +88,21 @@ func assignOpToken(updateAssign bool) lex.Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func multiplyWithPrefs() lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
prefs := multiplyPreferences{}
|
||||||
|
options := string(m.Bytes)
|
||||||
|
if strings.Contains(options, "+") {
|
||||||
|
prefs.AppendArrays = true
|
||||||
|
}
|
||||||
|
if strings.Contains(options, "?") {
|
||||||
|
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
|
||||||
|
}
|
||||||
|
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
|
||||||
|
return &token{TokenType: operationToken, Operation: op}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action {
|
func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action {
|
||||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
|
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
|
||||||
@@ -123,7 +125,7 @@ func assignAllCommentsOp(updateAssign bool) lex.Action {
|
|||||||
Value: assignCommentOpType.Type,
|
Value: assignCommentOpType.Type,
|
||||||
StringValue: value,
|
StringValue: value,
|
||||||
UpdateAssign: updateAssign,
|
UpdateAssign: updateAssign,
|
||||||
Preferences: &commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
|
Preferences: commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
|
||||||
}
|
}
|
||||||
return &token{TokenType: operationToken, Operation: op}, nil
|
return &token{TokenType: operationToken, Operation: op}, nil
|
||||||
}
|
}
|
||||||
@@ -181,7 +183,7 @@ func stringValue(wrapped bool) lex.Action {
|
|||||||
func envOp(strenv bool) lex.Action {
|
func envOp(strenv bool) lex.Action {
|
||||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
value := string(m.Bytes)
|
value := string(m.Bytes)
|
||||||
preferences := &envOpPreferences{}
|
preferences := envOpPreferences{}
|
||||||
|
|
||||||
if strenv {
|
if strenv {
|
||||||
// strenv( )
|
// strenv( )
|
||||||
@@ -219,11 +221,11 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`\)`), literalToken(closeBracket, true))
|
lexer.Add([]byte(`\)`), literalToken(closeBracket, true))
|
||||||
|
|
||||||
lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false))
|
lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false))
|
||||||
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true,
|
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
|
||||||
TraversePreferences: &traversePreferences{FollowAlias: false, IncludeMapKeys: false}}))
|
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}}))
|
||||||
|
|
||||||
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true,
|
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
|
||||||
TraversePreferences: &traversePreferences{FollowAlias: false, IncludeMapKeys: true}}))
|
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}))
|
||||||
|
|
||||||
lexer.Add([]byte(`,`), opToken(unionOpType))
|
lexer.Add([]byte(`,`), opToken(unionOpType))
|
||||||
lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
|
lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
|
||||||
@@ -239,6 +241,11 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
|
|
||||||
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
|
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
|
||||||
lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType))
|
lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType))
|
||||||
|
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`join`), opToken(joinStringOpType))
|
||||||
|
lexer.Add([]byte(`split`), opToken(splitStringOpType))
|
||||||
|
lexer.Add([]byte(`keys`), opToken(keysOpType))
|
||||||
|
|
||||||
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))
|
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))
|
||||||
|
|
||||||
@@ -250,11 +257,11 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
|
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
|
||||||
lexer.Add([]byte(`path`), opToken(getPathOpType))
|
lexer.Add([]byte(`path`), opToken(getPathOpType))
|
||||||
|
|
||||||
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, &commentOpPreferences{LineComment: true}))
|
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true}))
|
||||||
|
|
||||||
lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, &commentOpPreferences{HeadComment: true}))
|
lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}))
|
||||||
|
|
||||||
lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, &commentOpPreferences{FootComment: true}))
|
lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}))
|
||||||
|
|
||||||
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
|
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
|
||||||
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
|
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
|
||||||
@@ -270,7 +277,6 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
|
|
||||||
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
||||||
|
|
||||||
lexer.Add([]byte(`d[0-9]+`), documentToken())
|
|
||||||
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
|
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
|
||||||
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
|
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
|
||||||
lexer.Add([]byte(`\.`), selfToken())
|
lexer.Add([]byte(`\.`), selfToken())
|
||||||
@@ -295,8 +301,7 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
|
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
|
||||||
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
|
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
|
||||||
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
|
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
|
||||||
lexer.Add([]byte(`\*`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: false}))
|
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
|
||||||
lexer.Add([]byte(`\*\+`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: true}))
|
|
||||||
lexer.Add([]byte(`\+`), opToken(addOpType))
|
lexer.Add([]byte(`\+`), opToken(addOpType))
|
||||||
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
|
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Pre
|
|||||||
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||||
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
|
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
|
||||||
|
|
||||||
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: multiplyOperator}
|
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
||||||
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: addOperator}
|
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
||||||
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: alternativeOperator}
|
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
||||||
|
|
||||||
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
||||||
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
|
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
|
||||||
@@ -51,6 +51,7 @@ var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence:
|
|||||||
|
|
||||||
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
|
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
|
||||||
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
|
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
|
||||||
|
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
|
||||||
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
|
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
|
||||||
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
|
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
|
||||||
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
||||||
@@ -63,12 +64,15 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50,
|
|||||||
|
|
||||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||||
|
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||||
|
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||||
|
|
||||||
|
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
||||||
|
|
||||||
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
|
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
|
||||||
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator}
|
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator}
|
||||||
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator}
|
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator}
|
||||||
|
|
||||||
var documentFilterOpType = &operationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: traversePathOperator}
|
|
||||||
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator}
|
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator}
|
||||||
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
||||||
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
||||||
@@ -120,8 +124,6 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
|
|||||||
func (p *Operation) toString() string {
|
func (p *Operation) toString() string {
|
||||||
if p.OperationType == traversePathOpType {
|
if p.OperationType == traversePathOpType {
|
||||||
return fmt.Sprintf("%v", p.Value)
|
return fmt.Sprintf("%v", p.Value)
|
||||||
} else if p.OperationType == documentFilterOpType {
|
|
||||||
return fmt.Sprintf("d%v", p.Value)
|
|
||||||
} else if p.OperationType == selfReferenceOpType {
|
} else if p.OperationType == selfReferenceOpType {
|
||||||
return "SELF"
|
return "SELF"
|
||||||
} else if p.OperationType == valueOpType {
|
} else if p.OperationType == valueOpType {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
|||||||
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
||||||
|
|
||||||
splatted, err := splat(d, nodeToMap(candidate),
|
splatted, err := splat(d, nodeToMap(candidate),
|
||||||
&traversePreferences{FollowAlias: false, IncludeMapKeys: false})
|
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
|
||||||
|
|
||||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||||
splatEl.Value.(*CandidateNode).Path = nil
|
splatEl.Value.(*CandidateNode).Path = nil
|
||||||
@@ -87,7 +87,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
|||||||
|
|
||||||
newCandidate.Path = nil
|
newCandidate.Path = nil
|
||||||
|
|
||||||
newCandidate, err = multiply(&multiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func assignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expr
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences := expressionNode.Operation.Preferences.(*commentOpPreferences)
|
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||||
|
|
||||||
comment := ""
|
comment := ""
|
||||||
if !expressionNode.Operation.UpdateAssign {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
@@ -67,7 +67,7 @@ func assignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
func getCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
preferences := expressionNode.Operation.Preferences.(*commentOpPreferences)
|
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||||
log.Debugf("GetComments operator!")
|
log.Debugf("GetComments operator!")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,12 @@ var commentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Remove all comments",
|
description: "Remove all comments",
|
||||||
document: "# hi\n\na: cat # comment\n\n# great\n",
|
subdescription: "Note the use of `...` to ensure key nodes are included.",
|
||||||
expression: `.. comments=""`,
|
document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment",
|
||||||
|
expression: `... comments=""`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::a: cat\n",
|
"D0, P[], (!!map)::a: cat\nb:\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func deleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, express
|
|||||||
|
|
||||||
deleteImmediateChildOpNode := &ExpressionNode{
|
deleteImmediateChildOpNode := &ExpressionNode{
|
||||||
Operation: deleteImmediateChildOp,
|
Operation: deleteImmediateChildOp,
|
||||||
Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
|
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func envOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *Expr
|
|||||||
|
|
||||||
rawValue := os.Getenv(envName)
|
rawValue := os.Getenv(envName)
|
||||||
|
|
||||||
preferences := expressionNode.Operation.Preferences.(*envOpPreferences)
|
preferences := expressionNode.Operation.Preferences.(envOpPreferences)
|
||||||
|
|
||||||
var node *yaml.Node
|
var node *yaml.Node
|
||||||
if preferences.StringValue {
|
if preferences.StringValue {
|
||||||
|
|||||||
54
pkg/yqlib/operator_keys.go
Normal file
54
pkg/yqlib/operator_keys.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func keysOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- keysOperator")
|
||||||
|
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := unwrapDoc(candidate.Node)
|
||||||
|
var targetNode *yaml.Node
|
||||||
|
if node.Kind == yaml.MappingNode {
|
||||||
|
targetNode = getMapKeys(node)
|
||||||
|
} else if node.Kind == yaml.SequenceNode {
|
||||||
|
targetNode = getIndicies(node)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", node.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := candidate.CreateChild(nil, targetNode)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMapKeys(node *yaml.Node) *yaml.Node {
|
||||||
|
contents := make([]*yaml.Node, 0)
|
||||||
|
for index := 0; index < len(node.Content); index = index + 2 {
|
||||||
|
contents = append(contents, node.Content[index])
|
||||||
|
}
|
||||||
|
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndicies(node *yaml.Node) *yaml.Node {
|
||||||
|
var contents = make([]*yaml.Node, len(node.Content))
|
||||||
|
|
||||||
|
for index := range node.Content {
|
||||||
|
contents[index] = &yaml.Node{
|
||||||
|
Kind: yaml.ScalarNode,
|
||||||
|
Tag: "!!int",
|
||||||
|
Value: fmt.Sprintf("%v", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
|
||||||
|
}
|
||||||
47
pkg/yqlib/operator_keys_test.go
Normal file
47
pkg/yqlib/operator_keys_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keysOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Map keys",
|
||||||
|
document: `{dog: woof, cat: meow}`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- dog\n- cat\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{}`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Array keys",
|
||||||
|
document: `[apple, banana]`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- 0\n- 1\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `[]`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range keysOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Keys", keysOperatorScenarios)
|
||||||
|
}
|
||||||
@@ -44,44 +44,44 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, expressionNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
type multiplyPreferences struct {
|
type multiplyPreferences struct {
|
||||||
AppendArrays bool
|
AppendArrays bool
|
||||||
|
TraversePrefs traversePreferences
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- MultiplyOperator")
|
log.Debugf("-- MultiplyOperator")
|
||||||
return crossFunction(d, matchingNodes, expressionNode, multiply(expressionNode.Operation.Preferences.(*multiplyPreferences)))
|
return crossFunction(d, matchingNodes, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiply(preferences *multiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = unwrapDoc(lhs.Node)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
rhs.Node = unwrapDoc(rhs.Node)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
||||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||||
|
|
||||||
shouldAppendArrays := preferences.AppendArrays
|
|
||||||
|
|
||||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||||
|
|
||||||
var newBlank = lhs.CreateChild(nil, &yaml.Node{})
|
var newBlank = lhs.CreateChild(nil, &yaml.Node{})
|
||||||
var newThing, err = mergeObjects(d, newBlank, lhs, false)
|
var newThing, err = mergeObjects(d, newBlank, lhs, multiplyPreferences{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
|
return mergeObjects(d, newThing, rhs, preferences)
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
|
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
|
||||||
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
// shouldn't recurse arrays if appending
|
// shouldn't recurse arrays if appending
|
||||||
prefs := &recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
||||||
TraversePreferences: &traversePreferences{FollowAlias: false}}
|
TraversePreferences: traversePreferences{DontFollowAlias: true}}
|
||||||
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -93,7 +93,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for el := results.Front(); el != nil; el = el.Next() {
|
for el := results.Front(); el != nil; el = el.Next() {
|
||||||
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
|
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), preferences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -101,8 +101,8 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
|||||||
return lhs, nil
|
return lhs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
|
||||||
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||||
|
|
||||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||||
@@ -116,7 +116,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
|
|||||||
}
|
}
|
||||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}
|
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}
|
||||||
|
|
||||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &ExpressionNode{Operation: rhsOp}}
|
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs), Rhs: &ExpressionNode{Operation: rhsOp}}
|
||||||
|
|
||||||
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
|
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,22 @@ b:
|
|||||||
"D0, P[a], (!!seq)::[1, 2]\n",
|
"D0, P[a], (!!seq)::[1, 2]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Merge, only existing fields",
|
||||||
|
document: `{a: {thing: one, cat: frog}, b: {missing: two, thing: two}}`,
|
||||||
|
expression: `.a *? .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!map)::{thing: two, cat: frog}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`,
|
||||||
|
expression: `.a *? .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!seq)::[{thing: two}]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Merge, appending arrays",
|
description: "Merge, appending arrays",
|
||||||
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
||||||
@@ -115,6 +131,14 @@ b:
|
|||||||
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
|
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Merge, only existing fields, appending arrays",
|
||||||
|
document: `{a: {thing: [1,2]}, b: {thing: [3,4], another: [1]}}`,
|
||||||
|
expression: `.a *?+ .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Merge to prefix an element",
|
description: "Merge to prefix an element",
|
||||||
document: `{a: cat, b: dog}`,
|
document: `{a: cat, b: dog}`,
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type recursiveDescentPreferences struct {
|
type recursiveDescentPreferences struct {
|
||||||
TraversePreferences *traversePreferences
|
TraversePreferences traversePreferences
|
||||||
RecurseArray bool
|
RecurseArray bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
preferences := expressionNode.Operation.Preferences.(*recursiveDescentPreferences)
|
preferences := expressionNode.Operation.Preferences.(recursiveDescentPreferences)
|
||||||
err := recursiveDecent(d, results, matchMap, preferences)
|
err := recursiveDecent(d, results, matchMap, preferences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -23,7 +23,7 @@ func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, express
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *recursiveDescentPreferences) error {
|
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences recursiveDescentPreferences) error {
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
|||||||
18
pkg/yqlib/operator_split_document.go
Normal file
18
pkg/yqlib/operator_split_document.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
func splitDocumentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- splitDocumentOperator")
|
||||||
|
|
||||||
|
var index uint = 0
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
candidate.Document = index
|
||||||
|
index = index + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchMap, nil
|
||||||
|
}
|
||||||
32
pkg/yqlib/operator_split_document_test.go
Normal file
32
pkg/yqlib/operator_split_document_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var splitDocOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Split empty",
|
||||||
|
document: ``,
|
||||||
|
expression: `splitDoc`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!null)::\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Split array",
|
||||||
|
document: `[{a: cat}, {b: dog}]`,
|
||||||
|
expression: `.[] | splitDoc`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0], (!!map)::{a: cat}\n",
|
||||||
|
"D1, P[1], (!!map)::{b: dog}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitDocOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range splitDocOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Split into Documents", splitDocOperatorScenarios)
|
||||||
|
}
|
||||||
96
pkg/yqlib/operator_strings.go
Normal file
96
pkg/yqlib/operator_strings.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func joinStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- joinStringOperator")
|
||||||
|
joinStr := ""
|
||||||
|
|
||||||
|
rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
joinStr = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := unwrapDoc(candidate.Node)
|
||||||
|
if node.Kind != yaml.SequenceNode {
|
||||||
|
return nil, fmt.Errorf("Cannot join with %v, can only join arrays of scalars", node.Tag)
|
||||||
|
}
|
||||||
|
targetNode := join(node.Content, joinStr)
|
||||||
|
result := candidate.CreateChild(nil, targetNode)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func join(content []*yaml.Node, joinStr string) *yaml.Node {
|
||||||
|
var stringsToJoin []string
|
||||||
|
for _, node := range content {
|
||||||
|
str := node.Value
|
||||||
|
if node.Tag == "!!null" {
|
||||||
|
str = ""
|
||||||
|
}
|
||||||
|
stringsToJoin = append(stringsToJoin, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &yaml.Node{Kind: yaml.ScalarNode, Value: strings.Join(stringsToJoin, joinStr), Tag: "!!str"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- splitStringOperator")
|
||||||
|
splitStr := ""
|
||||||
|
|
||||||
|
rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
splitStr = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := unwrapDoc(candidate.Node)
|
||||||
|
if node.Tag == "!!null" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if node.Tag != "!!str" {
|
||||||
|
return nil, fmt.Errorf("Cannot split %v, can only split strings", node.Tag)
|
||||||
|
}
|
||||||
|
targetNode := split(node.Value, splitStr)
|
||||||
|
result := candidate.CreateChild(nil, targetNode)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(value string, spltStr string) *yaml.Node {
|
||||||
|
var contents []*yaml.Node
|
||||||
|
|
||||||
|
if value != "" {
|
||||||
|
var newStrings = strings.Split(value, spltStr)
|
||||||
|
contents = make([]*yaml.Node, len(newStrings))
|
||||||
|
|
||||||
|
for index, str := range newStrings {
|
||||||
|
contents[index] = &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: str}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
|
||||||
|
}
|
||||||
52
pkg/yqlib/operator_strings_test.go
Normal file
52
pkg/yqlib/operator_strings_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var stringsOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Join strings",
|
||||||
|
document: `[cat, meow, 1, null, true]`,
|
||||||
|
expression: `join("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::cat; meow; 1; ; true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Split strings",
|
||||||
|
document: `"cat; meow; 1; ; true"`,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- cat\n- meow\n- \"1\"\n- \"\"\n- \"true\"\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Split strings one match",
|
||||||
|
document: `"word"`,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- word\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `""`,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n", // dont actually want this, just not to error
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringsOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range stringsOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "String Operators", stringsOperatorScenarios)
|
||||||
|
}
|
||||||
@@ -10,11 +10,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type traversePreferences struct {
|
type traversePreferences struct {
|
||||||
FollowAlias bool
|
DontFollowAlias bool
|
||||||
IncludeMapKeys bool
|
IncludeMapKeys bool
|
||||||
|
DontAutoCreate bool // by default, we automatically create entries on the fly.
|
||||||
}
|
}
|
||||||
|
|
||||||
func splat(d *dataTreeNavigator, matches *list.List, prefs *traversePreferences) (*list.List, error) {
|
func splat(d *dataTreeNavigator, matches *list.List, prefs traversePreferences) (*list.List, error) {
|
||||||
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
|
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +55,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
|
|||||||
switch value.Kind {
|
switch value.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||||
prefs := &traversePreferences{FollowAlias: true}
|
return traverseMap(matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false)
|
||||||
return traverseMap(matchingNode, operation.StringValue, prefs, false)
|
|
||||||
|
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
log.Debug("its a sequence of %v things!", len(value.Content))
|
log.Debug("its a sequence of %v things!", len(value.Content))
|
||||||
@@ -83,11 +83,10 @@ func traverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, expre
|
|||||||
}
|
}
|
||||||
|
|
||||||
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
|
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
|
||||||
prefs := &traversePreferences{FollowAlias: true}
|
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, traversePreferences{})
|
||||||
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) {
|
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) {
|
||||||
var matchingNodeMap = list.New()
|
var matchingNodeMap = list.New()
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
@@ -101,7 +100,7 @@ func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse [
|
|||||||
return matchingNodeMap, nil
|
return matchingNodeMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
|
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
|
||||||
node := matchingNode.Node
|
node := matchingNode.Node
|
||||||
if node.Tag == "!!null" {
|
if node.Tag == "!!null" {
|
||||||
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
|
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
|
||||||
@@ -124,7 +123,7 @@ func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml
|
|||||||
return list.New(), nil
|
return list.New(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *traversePreferences) (*list.List, error) {
|
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) {
|
||||||
if len(indices) == 0 {
|
if len(indices) == 0 {
|
||||||
return traverseMap(candidate, "", prefs, true)
|
return traverseMap(candidate, "", prefs, true)
|
||||||
}
|
}
|
||||||
@@ -188,7 +187,7 @@ func keyMatches(key *yaml.Node, wantedKey string) bool {
|
|||||||
return matchKey(key.Value, wantedKey)
|
return matchKey(key.Value, wantedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePreferences, splat bool) (*list.List, error) {
|
func traverseMap(matchingNode *CandidateNode, key string, prefs traversePreferences, splat bool) (*list.List, error) {
|
||||||
var newMatches = orderedmap.NewOrderedMap()
|
var newMatches = orderedmap.NewOrderedMap()
|
||||||
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
|
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
|
||||||
|
|
||||||
@@ -196,7 +195,7 @@ func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePrefere
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if newMatches.Len() == 0 {
|
if !prefs.DontAutoCreate && newMatches.Len() == 0 {
|
||||||
//no matches, create one automagically
|
//no matches, create one automagically
|
||||||
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
||||||
node := matchingNode.Node
|
node := matchingNode.Node
|
||||||
@@ -214,7 +213,7 @@ func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePrefere
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *traversePreferences, splat bool) error {
|
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error {
|
||||||
// value.Content is a concatenated array of key, value,
|
// value.Content is a concatenated array of key, value,
|
||||||
// so keys are in the even indexes, values in odd.
|
// so keys are in the even indexes, values in odd.
|
||||||
// merge aliases are defined first, but we only want to traverse them
|
// merge aliases are defined first, but we only want to traverse them
|
||||||
@@ -229,7 +228,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
|
|||||||
|
|
||||||
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
||||||
//skip the 'merge' tag, find a direct match first
|
//skip the 'merge' tag, find a direct match first
|
||||||
if key.Tag == "!!merge" && prefs.FollowAlias {
|
if key.Tag == "!!merge" && !prefs.DontFollowAlias {
|
||||||
log.Debug("Merge anchor")
|
log.Debug("Merge anchor")
|
||||||
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
|
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -249,7 +248,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *traversePreferences, splat bool) error {
|
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs traversePreferences, splat bool) error {
|
||||||
switch value.Kind {
|
switch value.Kind {
|
||||||
case yaml.AliasNode:
|
case yaml.AliasNode:
|
||||||
candidateNode := originalCandidate.CreateChild(nil, value.Alias)
|
candidateNode := originalCandidate.CreateChild(nil, value.Alias)
|
||||||
|
|||||||
@@ -35,14 +35,16 @@ func nodeToMap(candidate *CandidateNode) *list.List {
|
|||||||
return elMap
|
return elMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTraversalTree(path []interface{}) *ExpressionNode {
|
func createTraversalTree(path []interface{}, traversePrefs traversePreferences) *ExpressionNode {
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
|
return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
|
||||||
} else if len(path) == 1 {
|
} else if len(path) == 1 {
|
||||||
return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Preferences: traversePrefs, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ExpressionNode{
|
return &ExpressionNode{
|
||||||
Operation: &Operation{OperationType: shortPipeOpType},
|
Operation: &Operation{OperationType: shortPipeOpType},
|
||||||
Lhs: createTraversalTree(path[0:1]),
|
Lhs: createTraversalTree(path[0:1], traversePrefs),
|
||||||
Rhs: createTraversalTree(path[1:])}
|
Rhs: createTraversalTree(path[1:], traversePrefs),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
|||||||
candidateNode := &CandidateNode{
|
candidateNode := &CandidateNode{
|
||||||
Document: 0,
|
Document: 0,
|
||||||
Filename: "",
|
Filename: "",
|
||||||
Node: &yaml.Node{Tag: "!!null"},
|
Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
|
||||||
FileIndex: 0,
|
FileIndex: 0,
|
||||||
}
|
}
|
||||||
inputs.PushBack(candidateNode)
|
inputs.PushBack(candidateNode)
|
||||||
@@ -245,7 +245,7 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
|
|||||||
candidateNode := &CandidateNode{
|
candidateNode := &CandidateNode{
|
||||||
Document: 0,
|
Document: 0,
|
||||||
Filename: "",
|
Filename: "",
|
||||||
Node: &yaml.Node{Tag: "!!null"},
|
Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
|
||||||
FileIndex: 0,
|
FileIndex: 0,
|
||||||
}
|
}
|
||||||
inputs.PushBack(candidateNode)
|
inputs.PushBack(candidateNode)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error
|
|||||||
candidateNode := &CandidateNode{
|
candidateNode := &CandidateNode{
|
||||||
Document: 0,
|
Document: 0,
|
||||||
Filename: "",
|
Filename: "",
|
||||||
Node: &yaml.Node{Tag: "!!null"},
|
Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
|
||||||
FileIndex: 0,
|
FileIndex: 0,
|
||||||
}
|
}
|
||||||
inputList := list.New()
|
inputList := list.New()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: yq
|
name: yq
|
||||||
version: '4.3.2'
|
version: '4.4.0'
|
||||||
summary: A lightweight and portable command-line YAML processor
|
summary: A lightweight and portable command-line YAML processor
|
||||||
description: |
|
description: |
|
||||||
The aim of the project is to be the jq or sed of yaml files.
|
The aim of the project is to be the jq or sed of yaml files.
|
||||||
|
|||||||
Reference in New Issue
Block a user