mirror of
https://github.com/mikefarah/yq.git
synced 2024-12-19 20:19:04 +00:00
173 lines
5.2 KiB
Go
173 lines
5.2 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"container/list"
|
|
"fmt"
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func createPathNodeFor(pathElement interface{}) *yaml.Node {
|
|
switch pathElement := pathElement.(type) {
|
|
case string:
|
|
return &yaml.Node{Kind: yaml.ScalarNode, Value: pathElement, Tag: "!!str"}
|
|
default:
|
|
return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", pathElement), Tag: "!!int"}
|
|
}
|
|
}
|
|
|
|
func getPathArrayFromNode(funcName string, node *yaml.Node) ([]interface{}, error) {
|
|
if node.Kind != yaml.SequenceNode {
|
|
return nil, fmt.Errorf("%v: expected path array, but got %v instead", funcName, node.Tag)
|
|
}
|
|
|
|
path := make([]interface{}, len(node.Content))
|
|
|
|
for i, childNode := range 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("%v: could not parse %v as an int: %w", funcName, childNode.Value, err)
|
|
}
|
|
path[i] = number
|
|
} else {
|
|
return nil, fmt.Errorf("%v: expected either a !!str or !!int in the path, found %v instead", funcName, 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)
|
|
}
|
|
|
|
lhsPathContext, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.RHS.LHS)
|
|
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
|
|
if lhsPathContext.MatchingNodes.Len() != 1 {
|
|
return Context{}, fmt.Errorf("SETPATH: expected single path but found %v results instead", lhsPathContext.MatchingNodes.Len())
|
|
}
|
|
lhsValue := lhsPathContext.MatchingNodes.Front().Value.(*CandidateNode)
|
|
|
|
lhsPath, err := getPathArrayFromNode("SETPATH", lhsValue.Node)
|
|
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
|
|
lhsTraversalTree := createTraversalTree(lhsPath, traversePreferences{}, false)
|
|
|
|
assignmentOp := &Operation{OperationType: assignOpType}
|
|
|
|
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("SETPATH: 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 delPathsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
log.Debugf("delPaths")
|
|
// single RHS expression that returns an array of paths (array of arrays)
|
|
|
|
pathArraysContext, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.RHS)
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
if pathArraysContext.MatchingNodes.Len() != 1 {
|
|
return Context{}, fmt.Errorf("DELPATHS: expected single value but found %v", pathArraysContext.MatchingNodes.Len())
|
|
}
|
|
pathArraysNode := pathArraysContext.MatchingNodes.Front().Value.(*CandidateNode).Node
|
|
|
|
if pathArraysNode.Tag != "!!seq" {
|
|
return Context{}, fmt.Errorf("DELPATHS: expected a sequence of sequences, but found %v", pathArraysNode.Tag)
|
|
}
|
|
|
|
updatedContext := context
|
|
|
|
for i, child := range pathArraysNode.Content {
|
|
|
|
if child.Tag != "!!seq" {
|
|
return Context{}, fmt.Errorf("DELPATHS: expected entry [%v] to be a sequence, but its a %v. Note that delpaths takes an array of path arrays, e.g. [[\"a\", \"b\"]]", i, child.Tag)
|
|
}
|
|
childPath, err := getPathArrayFromNode("DELPATHS", child)
|
|
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
|
|
childTraversalExp := createTraversalTree(childPath, traversePreferences{}, false)
|
|
deleteChildOp := &Operation{OperationType: deleteChildOpType}
|
|
|
|
deleteChildOpNode := &ExpressionNode{
|
|
Operation: deleteChildOp,
|
|
RHS: childTraversalExp,
|
|
}
|
|
|
|
updatedContext, err = d.GetMatchingNodes(updatedContext, deleteChildOpNode)
|
|
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
|
|
}
|
|
|
|
return updatedContext, nil
|
|
|
|
}
|
|
|
|
func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
log.Debugf("GetPath")
|
|
|
|
var results = list.New()
|
|
|
|
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
|
candidate := el.Value.(*CandidateNode)
|
|
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
|
|
|
content := make([]*yaml.Node, len(candidate.Path))
|
|
for pathIndex := 0; pathIndex < len(candidate.Path); pathIndex++ {
|
|
path := candidate.Path[pathIndex]
|
|
content[pathIndex] = createPathNodeFor(path)
|
|
}
|
|
node.Content = content
|
|
result := candidate.CreateReplacement(node)
|
|
results.PushBack(result)
|
|
}
|
|
|
|
return context.ChildContext(results), nil
|
|
}
|