mirror of
https://github.com/mikefarah/yq.git
synced 2025-01-24 06:35:40 +00:00
Added SETPATH operator
This commit is contained in:
parent
7f4c8e1c78
commit
da3f3b93b4
@ -98,3 +98,59 @@ will output
|
||||
value: frog
|
||||
```
|
||||
|
||||
## Set path
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'setpath(["a", "b"]; "things")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
b: things
|
||||
```
|
||||
|
||||
## Set on empty document
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'setpath(["a", "b"]; "things")'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
b: things
|
||||
```
|
||||
|
||||
## Set array path
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- cat
|
||||
- frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'setpath(["a", 0]; "things")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
- things
|
||||
- frog
|
||||
```
|
||||
|
||||
## Set array path empty
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'setpath(["a", 0]; "things")'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
- things
|
||||
```
|
||||
|
||||
|
@ -128,6 +128,7 @@ var participleYqRules = []*participleYqRule{
|
||||
simpleOp("file_?name|fileName", getFilenameOpType),
|
||||
simpleOp("file_?index|fileIndex|fi", getFileIndexOpType),
|
||||
simpleOp("path", getPathOpType),
|
||||
simpleOp("set_?path", setPathOpType),
|
||||
|
||||
simpleOp("to_?entries|toEntries", toEntriesOpType),
|
||||
simpleOp("from_?entries|fromEntries", fromEntriesOpType),
|
||||
|
@ -130,7 +130,9 @@ var getAliasOpType = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 5
|
||||
var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator}
|
||||
var getFilenameOpType = &operationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: getFilenameOperator}
|
||||
var getFileIndexOpType = &operationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: getFileIndexOperator}
|
||||
|
||||
var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
|
||||
var setPathOpType = &operationType{Type: "SET_PATH", NumArgs: 1, Precedence: 50, Handler: setPathOperator}
|
||||
|
||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator}
|
||||
|
@ -16,6 +16,90 @@ func createPathNodeFor(pathElement interface{}) *yaml.Node {
|
||||
}
|
||||
}
|
||||
|
||||
func getPathArrayFromExp(d *dataTreeNavigator, context Context, pathExp *ExpressionNode) ([]interface{}, error) {
|
||||
lhsPathContext, err := d.GetMatchingNodes(context.ReadOnlyClone(), pathExp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lhsPathContext.MatchingNodes.Len() != 1 {
|
||||
return nil, fmt.Errorf("expected single path but found %v results instead", lhsPathContext.MatchingNodes.Len())
|
||||
}
|
||||
lhsValue := lhsPathContext.MatchingNodes.Front().Value.(*CandidateNode)
|
||||
if lhsValue.Node.Kind != yaml.SequenceNode {
|
||||
return nil, fmt.Errorf("expected path array, but got %v instead", lhsValue.Node.Tag)
|
||||
}
|
||||
|
||||
path := make([]interface{}, len(lhsValue.Node.Content))
|
||||
|
||||
for i, childNode := range lhsValue.Node.Content {
|
||||
if childNode.Tag == "!!str" {
|
||||
path[i] = childNode.Value
|
||||
} else if childNode.Tag == "!!int" {
|
||||
number, err := parseInt(childNode.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse %v as an int: %w", childNode.Value, err)
|
||||
}
|
||||
path[i] = number
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected either a !!str or !!int in the path, found %v instead", childNode.Tag)
|
||||
}
|
||||
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// SETPATH(pathArray; value)
|
||||
func setPathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("SetPath")
|
||||
|
||||
if expressionNode.RHS.Operation.OperationType != blockOpType {
|
||||
return Context{}, fmt.Errorf("SETPATH must be given a block (;), got %v instead", expressionNode.RHS.Operation.OperationType.Type)
|
||||
}
|
||||
|
||||
lhsPath, err := getPathArrayFromExp(d, context, expressionNode.RHS.LHS)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
lhsTraversalTree := createTraversalTree(lhsPath, traversePreferences{}, false)
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
|
||||
//TODO if context is empty, create a new one
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
targetContextValue, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.RHS.RHS)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
if targetContextValue.MatchingNodes.Len() != 1 {
|
||||
return Context{}, fmt.Errorf("Expected single value on RHS but found %v", targetContextValue.MatchingNodes.Len())
|
||||
}
|
||||
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: targetContextValue.MatchingNodes.Front().Value.(*CandidateNode)}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
LHS: lhsTraversalTree,
|
||||
RHS: &ExpressionNode{Operation: rhsOp},
|
||||
}
|
||||
|
||||
_, err = d.GetMatchingNodes(context.SingleChildContext(candidate), assignmentOpNode)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("GetPath")
|
||||
|
||||
|
@ -63,6 +63,36 @@ var pathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a 2], (!!seq)::- path:\n - a\n - 2\n value: frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set path",
|
||||
document: `{a: {b: cat}}`,
|
||||
expression: `setpath(["a", "b"]; "things")`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: things}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set on empty document",
|
||||
expression: `setpath(["a", "b"]; "things")`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a:\n b: things\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set array path",
|
||||
document: `a: [cat, frog]`,
|
||||
expression: `setpath(["a", 0]; "things")`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: [things, frog]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set array path empty",
|
||||
expression: `setpath(["a", 0]; "things")`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a:\n - things\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPathOperatorsScenarios(t *testing.T) {
|
||||
|
@ -7,7 +7,7 @@ func withOperator(d *dataTreeNavigator, context Context, expressionNode *Express
|
||||
// with(path, exp)
|
||||
|
||||
if expressionNode.RHS.Operation.OperationType != blockOpType {
|
||||
return Context{}, fmt.Errorf("with must be given a block, got %v instead", expressionNode.RHS.Operation.OperationType.Type)
|
||||
return Context{}, fmt.Errorf("with must be given a block (;), got %v instead", expressionNode.RHS.Operation.OperationType.Type)
|
||||
}
|
||||
|
||||
pathExp := expressionNode.RHS.LHS
|
||||
|
Loading…
Reference in New Issue
Block a user