1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Mike Farah
af283315f2 Increment version 2021-05-21 14:27:24 +10:00
Mike Farah
d18a6963f6 Fixes nested array indexing #824 2021-05-21 14:18:24 +10:00
Mike Farah
77edbb9f5c Fixing readonly ops not to modify context when paths dont exist 2021-05-16 15:02:31 +10:00
Mike Farah
179c44aacc Fixing readonly ops not to modify context when paths dont exist 2021-05-16 14:36:13 +10:00
Mike Farah
bc70c1fb16 Added blank alias example 2021-05-16 14:18:18 +10:00
Mike Farah
0b71a40797 Fixing readonly ops not to modify context when paths dont exist 2021-05-16 14:17:13 +10:00
Mike Farah
3f51a44596 Fixing readonly ops not to modify context when paths dont exist 2021-05-16 14:00:30 +10:00
41 changed files with 368 additions and 51 deletions

View File

@@ -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.9.1" Version = "4.9.2"
// 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

View File

@@ -1,4 +1,4 @@
FROM mikefarah/yq:4.9.1 FROM mikefarah/yq:4.9.2
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

View File

@@ -12,6 +12,14 @@ type Context struct {
DontAutoCreate bool DontAutoCreate bool
} }
func (n *Context) SingleReadonlyChildContext(candidate *CandidateNode) Context {
list := list.New()
list.PushBack(candidate)
newContext := n.ChildContext(list)
newContext.DontAutoCreate = true
return newContext
}
func (n *Context) SingleChildContext(candidate *CandidateNode) Context { func (n *Context) SingleChildContext(candidate *CandidateNode) Context {
list := list.New() list := list.New()
list.PushBack(candidate) list.PushBack(candidate)
@@ -52,3 +60,9 @@ func (n *Context) Clone() Context {
} }
return clone return clone
} }
func (n *Context) ReadOnlyClone() Context {
clone := n.Clone()
clone.DontAutoCreate = true
return clone
}

View File

@@ -169,6 +169,22 @@ b: &meow purr
a: *meow a: *meow
``` ```
## Set alias to blank does nothing
Given a sample.yml file of:
```yaml
b: &meow purr
a: cat
```
then
```bash
yq eval '.a alias = ""' sample.yml
```
will output
```yaml
b: &meow purr
a: cat
```
## Set alias relatively using assign-update ## Set alias relatively using assign-update
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -54,7 +54,7 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
opStack = opStack[0 : len(opStack)-1] opStack = opStack[0 : len(opStack)-1]
log.Debugf("deleteing open bracket from opstack") log.Debugf("deleteing open bracket from opstack")
//and append a collect to the opStack //and append a collect to the result
// hack - see if there's the optional traverse flag // hack - see if there's the optional traverse flag
// on the close op - move it to the collect op. // on the close op - move it to the collect op.
// allows for .["cat"]? // allows for .["cat"]?
@@ -68,6 +68,12 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
result = append(result, &Operation{OperationType: shortPipeOpType}) result = append(result, &Operation{OperationType: shortPipeOpType})
log.Debugf("put shortpipe onto the result") log.Debugf("put shortpipe onto the result")
//traverseArrayCollect is a sneaky op that needs to be included too
//when closing a []
if len(opStack) > 0 && opStack[len(opStack)-1].Operation != nil && opStack[len(opStack)-1].Operation.OperationType == traverseArrayOpType {
opStack, result = popOpToResult(opStack, result)
}
case closeBracket: case closeBracket:
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket { for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
opStack, result = popOpToResult(opStack, result) opStack, result = popOpToResult(opStack, result)

View File

@@ -12,6 +12,16 @@ var pathTests = []struct {
expectedTokens []interface{} expectedTokens []interface{}
expectedPostFix []interface{} expectedPostFix []interface{}
}{ }{
{
`.[0]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "SELF", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.[0][1]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "0 (int64)", "]", "TRAVERSE_ARRAY", "[", "1 (int64)", "]"),
append(make([]interface{}, 0), "SELF", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "1 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{ {
`"\""`, `"\""`,
append(make([]interface{}, 0), "\" (string)"), append(make([]interface{}, 0), "\" (string)"),

View File

@@ -411,16 +411,19 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
skipNextToken = false skipNextToken = false
currentToken := tokens[index] currentToken := tokens[index]
log.Debug("processing %v", currentToken.toString(true))
if currentToken.TokenType == traverseArrayCollect { if currentToken.TokenType == traverseArrayCollect {
//need to put a traverse array then a collect currentToken //need to put a traverse array then a collect currentToken
// do this by adding traverse then converting currentToken to collect // do this by adding traverse then converting currentToken to collect
if index == 0 || tokens[index-1].TokenType != operationToken || if index == 0 || tokens[index-1].TokenType != operationToken ||
tokens[index-1].Operation.OperationType != traversePathOpType { tokens[index-1].Operation.OperationType != traversePathOpType {
log.Debug(" adding self")
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"} op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
} }
log.Debug(" adding traverse array")
op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"} op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
@@ -431,17 +434,19 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
if index != len(tokens)-1 && currentToken.AssignOperation != nil && if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
tokens[index+1].TokenType == operationToken && tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == assignOpType { tokens[index+1].Operation.OperationType == assignOpType {
log.Debug(" its an update assign")
currentToken.Operation = currentToken.AssignOperation currentToken.Operation = currentToken.AssignOperation
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true skipNextToken = true
} }
log.Debug(" adding token to the fixed list")
postProcessedTokens = append(postProcessedTokens, currentToken) postProcessedTokens = append(postProcessedTokens, currentToken)
if index != len(tokens)-1 && if index != len(tokens)-1 &&
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) || ((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) { (currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
log.Debug(" adding empty")
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"} op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
} }
@@ -449,12 +454,19 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
if index != len(tokens)-1 && currentToken.CheckForPostTraverse && if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == operationToken && tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == traversePathOpType { tokens[index+1].Operation.OperationType == traversePathOpType {
log.Debug(" adding pipe because the next thing is traverse")
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"} op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
} }
if index != len(tokens)-1 && currentToken.CheckForPostTraverse && if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == openCollect { tokens[index+1].TokenType == openCollect {
// if tokens[index].TokenType == closeCollect {
// log.Debug(" adding pipe because next is opencollect")
// op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
// postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
// }
log.Debug(" adding traverArray because next is opencollect")
op := &Operation{OperationType: traverseArrayOpType} op := &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
} }

View File

@@ -20,9 +20,6 @@ type operationType struct {
Handler operatorHandler Handler operatorHandler
} }
// operators TODO:
// - mergeEmpty (sets only if the document is empty, do I do that now?)
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator} var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator} var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator} var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}

View File

@@ -39,7 +39,7 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("Add operator") log.Debugf("Add operator")
return crossFunction(d, context, expressionNode, add, false) return crossFunction(d, context.ReadOnlyClone(), expressionNode, add, false)
} }
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {

View File

@@ -14,6 +14,22 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[1 a], (!!int)::3\n", "D0, P[1 a], (!!int)::3\n",
}, },
}, },
{
skipDoc: true,
document: `{}`,
expression: "(.a + .b) as $x",
expected: []string{
"D0, P[], (doc)::{}\n",
},
},
{
skipDoc: true,
document: `a: 0`,
expression: ".a += .b.c",
expected: []string{
"D0, P[], (doc)::a: 0\n",
},
},
{ {
description: "Concatenate and assign arrays", description: "Concatenate and assign arrays",
document: `{a: {val: thing, b: [cat,dog]}}`, document: `{a: {val: thing, b: [cat,dog]}}`,

View File

@@ -2,7 +2,7 @@ package yqlib
func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- alternative") log.Debugf("-- alternative")
return crossFunction(d, context, expressionNode, alternativeFunc, true) return crossFunction(d, context.ReadOnlyClone(), expressionNode, alternativeFunc, true)
} }
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {

View File

@@ -5,6 +5,14 @@ import (
) )
var alternativeOperatorScenarios = []expressionScenario{ var alternativeOperatorScenarios = []expressionScenario{
{
skipDoc: true,
expression: `(.b // "hello") as $x`,
document: `a: bridge`,
expected: []string{
"D0, P[], (doc)::a: bridge\n",
},
},
{ {
description: "LHS is defined", description: "LHS is defined",
expression: `.a // "hello"`, expression: `.a // "hello"`,

View File

@@ -12,7 +12,7 @@ func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *
aliasName := "" aliasName := ""
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -32,7 +32,7 @@ func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *
log.Debugf("Setting aliasName : %v", candidate.GetKey()) log.Debugf("Setting aliasName : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign { if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -41,8 +41,10 @@ func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *
} }
} }
candidate.Node.Kind = yaml.AliasNode if aliasName != "" {
candidate.Node.Value = aliasName candidate.Node.Kind = yaml.AliasNode
candidate.Node.Value = aliasName
}
} }
return context, nil return context, nil
} }
@@ -66,7 +68,7 @@ func assignAnchorOperator(d *dataTreeNavigator, context Context, expressionNode
anchorName := "" anchorName := ""
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -87,7 +89,7 @@ func assignAnchorOperator(d *dataTreeNavigator, context Context, expressionNode
log.Debugf("Setting anchorName of : %v", candidate.GetKey()) log.Debugf("Setting anchorName of : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign { if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@@ -58,6 +58,22 @@ var anchorOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: &cat {b: cat}\n", "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", description: "Get alias",
document: `{b: &billyBob meow, a: *billyBob}`, document: `{b: &billyBob meow, a: *billyBob}`,
@@ -74,6 +90,30 @@ var anchorOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n", "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", description: "Set alias relatively using assign-update",
document: `{b: &meow purr, a: {f: meow}}`, document: `{b: &meow purr, a: {f: meow}}`,
@@ -159,7 +199,7 @@ foobar:
}, },
} }
func TestAnchorAliaseOperatorScenarios(t *testing.T) { func TestAnchorAliasOperatorScenarios(t *testing.T) {
for _, tt := range anchorOperatorScenarios { for _, tt := range anchorOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }

View File

@@ -7,7 +7,7 @@ func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode
} }
var rhs Context var rhs Context
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err = d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
@@ -44,7 +44,7 @@ func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionN
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err

View File

@@ -12,6 +12,22 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], ()::a:\n b: cat\nx: frog\n", "D0, P[], ()::a:\n b: cat\nx: frog\n",
}, },
}, },
{
skipDoc: true,
document: "{}",
expression: `.a |= .b`,
expected: []string{
"D0, P[], (doc)::{a: null}\n",
},
},
{
skipDoc: true,
document: "{}",
expression: `.a = .b`,
expected: []string{
"D0, P[], (doc)::{a: null}\n",
},
},
{ {
description: "Update node to be the child value", description: "Update node to be the child value",
document: `{a: {b: {g: foof}}}`, document: `{a: {b: {g: foof}}}`,

View File

@@ -71,7 +71,7 @@ func findBoolean(wantBool bool, d *dataTreeNavigator, context Context, expressio
if expressionNode != nil { if expressionNode != nil {
//need to evaluate the expression against the node //need to evaluate the expression against the node
candidate := &CandidateNode{Node: node} candidate := &CandidateNode{Node: node}
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -134,7 +134,7 @@ func anyOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- orOp") log.Debugf("-- orOp")
return crossFunction(d, context, expressionNode, performBoolOp( return crossFunction(d, context.ReadOnlyClone(), expressionNode, performBoolOp(
func(b1 bool, b2 bool) bool { func(b1 bool, b2 bool) bool {
log.Debugf("-- peformingOrOp with %v and %v", b1, b2) log.Debugf("-- peformingOrOp with %v and %v", b1, b2)
return b1 || b2 return b1 || b2
@@ -143,7 +143,7 @@ func orOperator(d *dataTreeNavigator, context Context, expressionNode *Expressio
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- AndOp") log.Debugf("-- AndOp")
return crossFunction(d, context, expressionNode, performBoolOp( return crossFunction(d, context.ReadOnlyClone(), expressionNode, performBoolOp(
func(b1 bool, b2 bool) bool { func(b1 bool, b2 bool) bool {
return b1 && b2 return b1 && b2
}), true) }), true)

View File

@@ -67,6 +67,22 @@ var booleanOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: true\nb: false\n", "D0, P[], (doc)::a: true\nb: false\n",
}, },
}, },
{
skipDoc: true,
document: `[{pet: cat}]`,
expression: `any_c(.name == "harry") as $c`,
expected: []string{
"D0, P[], (doc)::[{pet: cat}]\n",
},
},
{
skipDoc: true,
document: `[{pet: cat}]`,
expression: `all_c(.name == "harry") as $c`,
expected: []string{
"D0, P[], (doc)::[{pet: cat}]\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `[false, false]`, document: `[false, false]`,
@@ -125,6 +141,22 @@ var booleanOperatorScenarios = []expressionScenario{
"D0, P[b], (!!bool)::true\n", "D0, P[b], (!!bool)::true\n",
}, },
}, },
{
skipDoc: true,
document: `{}`,
expression: `(.a.b or .c) as $x`,
expected: []string{
"D0, P[], (doc)::{}\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `(.a.b and .c) as $x`,
expected: []string{
"D0, P[], (doc)::{}\n",
},
},
{ {
description: "Not true is false", description: "Not true is false",
expression: `true | not`, expression: `true | not`,

View File

@@ -27,7 +27,7 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
comment := "" comment := ""
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -41,7 +41,7 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign { if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@@ -62,6 +62,22 @@ var commentOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: cat\n\n# cat\n", "D0, P[], (doc)::a: cat\n\n# cat\n",
}, },
}, },
{
skipDoc: true,
document: `a: cat`,
expression: `. footComment=.b.d`,
expected: []string{
"D0, P[], (doc)::a: cat\n",
},
},
{
skipDoc: true,
document: `a: cat`,
expression: `. footComment|=.b.d`,
expected: []string{
"D0, P[], (doc)::a: cat\n",
},
},
{ {
description: "Remove comment", description: "Remove comment",
document: "a: cat # comment\nb: dog # leave this", document: "a: cat # comment\nb: dog # leave this",

View File

@@ -7,9 +7,7 @@ import (
) )
func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
contextToUse := context.Clone() nodesToDelete, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
contextToUse.DontAutoCreate = true
nodesToDelete, err := d.GetMatchingNodes(contextToUse, expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err

View File

@@ -4,7 +4,7 @@ import "gopkg.in/yaml.v3"
func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- equalsOperation") log.Debugf("-- equalsOperation")
return crossFunction(d, context, expressionNode, isEquals(false), true) return crossFunction(d, context.ReadOnlyClone(), expressionNode, isEquals(false), true)
} }
func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
@@ -50,5 +50,5 @@ func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *Candid
func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- notEqualsOperator") log.Debugf("-- notEqualsOperator")
return crossFunction(d, context, expressionNode, isEquals(true), true) return crossFunction(d, context.ReadOnlyClone(), expressionNode, isEquals(true), true)
} }

View File

@@ -14,6 +14,38 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[], (!!bool)::false\n", "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[], (!!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, skipDoc: true,
document: "{a: {b: 10}}", document: "{a: {b: 10}}",

View File

@@ -12,14 +12,19 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
log.Debugf("-- hasOperation") log.Debugf("-- hasOperation")
var results = list.New() var results = list.New()
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
wanted := rhs.MatchingNodes.Front().Value.(*CandidateNode).Node
wantedKey := wanted.Value
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
wantedKey := "null"
wanted := &yaml.Node{Tag: "!!null"}
if rhs.MatchingNodes.Len() != 0 {
wanted = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node
wantedKey = wanted.Value
}
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)

View File

@@ -13,6 +13,22 @@ var hasOperatorScenarios = []expressionScenario{
"D0, P[], (!!bool)::true\n", "D0, P[], (!!bool)::true\n",
}, },
}, },
{
skipDoc: true,
document: `a: hello`,
expression: `has(.b) as $c`,
expected: []string{
"D0, P[], (doc)::a: hello\n",
},
},
{
skipDoc: true,
document: `a: hello`,
expression: `has(.b)`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{ {
description: "Has map key", description: "Has map key",
document: `- a: "yes" document: `- a: "yes"

View File

@@ -11,9 +11,7 @@ func selectOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
childContext := context.SingleChildContext(candidate) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
childContext.DontAutoCreate = true
rhs, err := d.GetMatchingNodes(childContext, expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err

View File

@@ -10,7 +10,7 @@ func sortKeysOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@@ -13,6 +13,14 @@ var sortKeysOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: blah, b: bing, c: frog}\n", "D0, P[], (doc)::{a: blah, b: bing, c: frog}\n",
}, },
}, },
{
skipDoc: true,
document: `{c: frog}`,
expression: `sortKeys(.d)`,
expected: []string{
"D0, P[], (doc)::{c: frog}\n",
},
},
{ {
description: "Sort keys recursively", description: "Sort keys recursively",
subdescription: "Note the array elements are left unsorted, but maps inside arrays are sorted", subdescription: "Note the array elements are left unsorted, but maps inside arrays are sorted",

View File

@@ -13,7 +13,7 @@ func getSubstituteParameters(d *dataTreeNavigator, block *ExpressionNode, contex
regEx := "" regEx := ""
replacementText := "" replacementText := ""
regExNodes, err := d.GetMatchingNodes(context, block.Lhs) regExNodes, err := d.GetMatchingNodes(context.ReadOnlyClone(), block.Lhs)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@@ -78,7 +78,7 @@ func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *E
log.Debugf("-- joinStringOperator") log.Debugf("-- joinStringOperator")
joinStr := "" joinStr := ""
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -119,7 +119,7 @@ func splitStringOperator(d *dataTreeNavigator, context Context, expressionNode *
log.Debugf("-- splitStringOperator") log.Debugf("-- splitStringOperator")
splitStr := "" splitStr := ""
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@@ -31,7 +31,7 @@ func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *
log.Debugf("AssignStyleOperator: %v") log.Debugf("AssignStyleOperator: %v")
var style yaml.Style var style yaml.Style
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -54,7 +54,7 @@ func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting style of : %v", candidate.GetKey()) log.Debugf("Setting style of : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign { if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@@ -25,7 +25,7 @@ func subtractAssignOperator(d *dataTreeNavigator, context Context, expressionNod
func subtractOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func subtractOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("Subtract operator") log.Debugf("Subtract operator")
return crossFunction(d, context, expressionNode, subtract, false) return crossFunction(d, context.ReadOnlyClone(), expressionNode, subtract, false)
} }
func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {

View File

@@ -5,6 +5,14 @@ import (
) )
var subtractOperatorScenarios = []expressionScenario{ var subtractOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{}`,
expression: "(.a - .b) as $x",
expected: []string{
"D0, P[], (doc)::{}\n",
},
},
{ {
description: "Number subtraction - float", description: "Number subtraction - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.", subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",

View File

@@ -12,7 +12,7 @@ func assignTagOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
tag := "" tag := ""
if !expressionNode.Operation.UpdateAssign { if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@@ -32,7 +32,7 @@ func assignTagOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey()) log.Debugf("Setting tag of : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign { if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@@ -89,7 +89,7 @@ func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode
// rhs is a collect expression that will yield indexes to retreive of the arrays // rhs is a collect expression that will yield indexes to retreive of the arrays
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err

View File

@@ -27,6 +27,22 @@ foobar:
` `
var traversePathOperatorScenarios = []expressionScenario{ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `[[1]]`,
expression: `.[0][0]`,
expected: []string{
"D0, P[0 0], (!!int)::1\n",
},
},
{
skipDoc: true,
document: `[[[1]]]`,
expression: `.[0][0][0]`,
expected: []string{
"D0, P[0 0 0], (!!int)::1\n",
},
},
{ {
description: "Simple map navigation", description: "Simple map navigation",
document: `{a: {b: apple}}`, document: `{a: {b: apple}}`,
@@ -78,6 +94,14 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[flying fox], (!!str)::frog\n", "D0, P[flying fox], (!!str)::frog\n",
}, },
}, },
{
skipDoc: true,
document: `c: dog`,
expression: `.[.a.b] as $x`,
expected: []string{
"D0, P[], (doc)::c: dog\n",
},
},
{ {
description: "Dynamic keys", description: "Dynamic keys",
subdescription: `Expressions within [] can be used to dynamically lookup / calculate keys`, subdescription: `Expressions within [] can be used to dynamically lookup / calculate keys`,

View File

@@ -5,6 +5,20 @@ import (
) )
var unionOperatorScenarios = []expressionScenario{ var unionOperatorScenarios = []expressionScenario{
// {
// skipDoc: true,
// document: "{}",
// expression: `(.a, .b.c) as $x`,
// expected: []string{
// "D0, P[], (doc)::{}\n",
// },
// },
// {
// skipDoc: true,
// document: "{}",
// expression: `(.a, .b.c)`,
// expected: []string{},
// },
{ {
description: "Combine scalars", description: "Combine scalars",
expression: `1, true, "cat"`, expression: `1, true, "cat"`,

View File

@@ -31,15 +31,20 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN
var newMatches = orderedmap.NewOrderedMap() var newMatches = orderedmap.NewOrderedMap()
for _, node := range candidateNode.Content { for _, node := range candidateNode.Content {
child := &CandidateNode{Node: node} child := &CandidateNode{Node: node}
rhs, err := d.GetMatchingNodes(context.SingleChildContext(child), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(child), expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
first := rhs.MatchingNodes.Front() keyValue := "null"
keyCandidate := first.Value.(*CandidateNode)
keyValue := keyCandidate.Node.Value if rhs.MatchingNodes.Len() > 0 {
first := rhs.MatchingNodes.Front()
keyCandidate := first.Value.(*CandidateNode)
keyValue = keyCandidate.Node.Value
}
_, exists := newMatches.Get(keyValue) _, exists := newMatches.Get(keyValue)
if !exists { if !exists {

View File

@@ -39,6 +39,22 @@ var uniqueOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::- {name: harry, pet: cat}\n- {name: billy, pet: dog}\n", "D0, P[], (!!seq)::- {name: harry, pet: cat}\n- {name: billy, pet: dog}\n",
}, },
}, },
{
skipDoc: true,
document: `[{name: harry, pet: cat}, {pet: fish}, {name: harry, pet: dog}]`,
expression: `unique_by(.name)`,
expected: []string{
"D0, P[], (!!seq)::- {name: harry, pet: cat}\n- {pet: fish}\n",
},
},
{
skipDoc: true,
document: `[{name: harry, pet: cat}, {pet: fish}, {name: harry, pet: dog}]`,
expression: `unique_by(.cat.dog)`,
expected: []string{
"D0, P[], (!!seq)::- {name: harry, pet: cat}\n",
},
},
} }
func TestUniqueOperatorScenarios(t *testing.T) { func TestUniqueOperatorScenarios(t *testing.T) {

View File

@@ -16,7 +16,7 @@ func getVariableOperator(d *dataTreeNavigator, context Context, expressionNode *
} }
func assignVariableOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func assignVariableOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Lhs)
if err != nil { if err != nil {
return Context{}, nil return Context{}, nil
} }

View File

@@ -5,6 +5,14 @@ import (
) )
var variableOperatorScenarios = []expressionScenario{ var variableOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{}`,
expression: `.a.b as $foo`,
expected: []string{
"D0, P[], (doc)::{}\n",
},
},
{ {
description: "Single value variable", description: "Single value variable",
document: `a: cat`, document: `a: cat`,

View File

@@ -1,5 +1,5 @@
name: yq name: yq
version: '4.9.1' version: '4.9.2'
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.