2020-10-08 23:59:03 +00:00
|
|
|
package treeops
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
|
2020-10-10 11:42:09 +00:00
|
|
|
"github.com/elliotchance/orderedmap"
|
2020-10-08 23:59:03 +00:00
|
|
|
"gopkg.in/op/go-logging.v1"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
2020-10-10 04:00:39 +00:00
|
|
|
var log = logging.MustGetLogger("yq-treeops")
|
|
|
|
|
2020-10-11 23:44:33 +00:00
|
|
|
type PathElementType uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
PathKey PathElementType = 1 << iota
|
|
|
|
ArrayIndex
|
|
|
|
Operation
|
|
|
|
SelfReference
|
|
|
|
OpenBracket
|
|
|
|
CloseBracket
|
|
|
|
)
|
|
|
|
|
|
|
|
type OperationType struct {
|
|
|
|
Type string
|
|
|
|
NumArgs uint // number of arguments to the op
|
|
|
|
Precedence uint
|
|
|
|
Handler OperatorHandler
|
|
|
|
}
|
|
|
|
|
|
|
|
var None = &OperationType{Type: "NONE", NumArgs: 0, Precedence: 0}
|
2020-10-13 03:37:01 +00:00
|
|
|
var Traverse = &OperationType{Type: "TRAVERSE", NumArgs: 2, Precedence: 35, Handler: TraverseOperator}
|
2020-10-11 23:44:33 +00:00
|
|
|
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
|
|
|
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: IntersectionOperator}
|
|
|
|
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 30, Handler: EqualsOperator}
|
2020-10-13 03:37:01 +00:00
|
|
|
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignOperator}
|
2020-10-11 23:44:33 +00:00
|
|
|
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 30, Handler: DeleteChildOperator}
|
|
|
|
|
2020-10-13 03:37:01 +00:00
|
|
|
var Count = &OperationType{Type: "COUNT", NumArgs: 1, Precedence: 40, Handler: CountOperator}
|
|
|
|
var Collect = &OperationType{Type: "COLLECT", NumArgs: 1, Precedence: 40, Handler: CollectOperator}
|
2020-10-12 01:24:59 +00:00
|
|
|
|
|
|
|
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
|
|
|
|
// filters matches if they have the existing path
|
2020-10-11 23:44:33 +00:00
|
|
|
|
|
|
|
type PathElement struct {
|
|
|
|
PathElementType PathElementType
|
|
|
|
OperationType *OperationType
|
|
|
|
Value interface{}
|
|
|
|
StringValue string
|
|
|
|
}
|
|
|
|
|
|
|
|
// debugging purposes only
|
|
|
|
func (p *PathElement) toString() string {
|
|
|
|
var result string = ``
|
|
|
|
switch p.PathElementType {
|
|
|
|
case PathKey:
|
|
|
|
result = result + fmt.Sprintf("PathKey - '%v'\n", p.Value)
|
|
|
|
case ArrayIndex:
|
|
|
|
result = result + fmt.Sprintf("ArrayIndex - '%v'\n", p.Value)
|
|
|
|
case SelfReference:
|
|
|
|
result = result + fmt.Sprintf("SELF\n")
|
|
|
|
case Operation:
|
|
|
|
result = result + fmt.Sprintf("Operation - %v\n", p.OperationType.Type)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-10-10 04:00:39 +00:00
|
|
|
type YqTreeLib interface {
|
2020-10-13 01:51:37 +00:00
|
|
|
Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error)
|
2020-10-10 04:00:39 +00:00
|
|
|
// GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
|
|
|
|
// Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
|
|
|
|
// New(path string) yaml.Node
|
|
|
|
|
|
|
|
// PathStackToString(pathStack []interface{}) string
|
|
|
|
// MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
|
|
|
|
}
|
|
|
|
|
2020-10-13 01:51:37 +00:00
|
|
|
func NewYqTreeLib() YqTreeLib {
|
|
|
|
return &lib{treeCreator: NewPathTreeCreator()}
|
|
|
|
}
|
|
|
|
|
2020-10-10 04:00:39 +00:00
|
|
|
type lib struct {
|
|
|
|
treeCreator PathTreeCreator
|
|
|
|
}
|
2020-10-08 23:59:03 +00:00
|
|
|
|
2020-10-13 01:51:37 +00:00
|
|
|
func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) {
|
|
|
|
nodes := []*CandidateNode{&CandidateNode{Node: documentNode.Content[0], Document: 0}}
|
|
|
|
navigator := NewDataTreeNavigator(NavigationPrefs{})
|
|
|
|
pathNode, errPath := l.treeCreator.ParsePath(path)
|
|
|
|
if errPath != nil {
|
|
|
|
return nil, errPath
|
|
|
|
}
|
|
|
|
return navigator.GetMatchingNodes(nodes, pathNode)
|
|
|
|
}
|
|
|
|
|
2020-10-10 11:42:09 +00:00
|
|
|
//use for debugging only
|
|
|
|
func NodesToString(collection *orderedmap.OrderedMap) string {
|
|
|
|
if !log.IsEnabledFor(logging.DEBUG) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
result := ""
|
|
|
|
for el := collection.Front(); el != nil; el = el.Next() {
|
|
|
|
result = result + "\n" + NodeToString(el.Value.(*CandidateNode))
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-10-08 23:59:03 +00:00
|
|
|
func NodeToString(node *CandidateNode) string {
|
|
|
|
if !log.IsEnabledFor(logging.DEBUG) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
value := node.Node
|
|
|
|
if value == nil {
|
|
|
|
return "-- node is nil --"
|
|
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
encoder := yaml.NewEncoder(buf)
|
|
|
|
errorEncoding := encoder.Encode(value)
|
|
|
|
if errorEncoding != nil {
|
|
|
|
log.Error("Error debugging node, %v", errorEncoding.Error())
|
|
|
|
}
|
|
|
|
encoder.Close()
|
|
|
|
return fmt.Sprintf(`-- Node --
|
|
|
|
Document %v, path: %v
|
|
|
|
Tag: %v, Kind: %v, Anchor: %v
|
|
|
|
%v`, node.Document, node.Path, value.Tag, KindString(value.Kind), value.Anchor, buf.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func KindString(kind yaml.Kind) string {
|
|
|
|
switch kind {
|
|
|
|
case yaml.ScalarNode:
|
|
|
|
return "ScalarNode"
|
|
|
|
case yaml.SequenceNode:
|
|
|
|
return "SequenceNode"
|
|
|
|
case yaml.MappingNode:
|
|
|
|
return "MappingNode"
|
|
|
|
case yaml.DocumentNode:
|
|
|
|
return "DocumentNode"
|
|
|
|
case yaml.AliasNode:
|
|
|
|
return "AliasNode"
|
|
|
|
default:
|
|
|
|
return "unknown!"
|
|
|
|
}
|
|
|
|
}
|