2019-12-28 07:19:37 +00:00
|
|
|
package yqlib
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type NodeContext struct {
|
|
|
|
Node *yaml.Node
|
|
|
|
Head string
|
|
|
|
Tail []string
|
|
|
|
PathStack []interface{}
|
|
|
|
}
|
|
|
|
|
2019-12-29 22:21:21 +00:00
|
|
|
func NewNodeContext(node *yaml.Node, head string, tail []string, pathStack []interface{}) NodeContext {
|
|
|
|
newTail := make([]string, len(tail))
|
|
|
|
copy(newTail, tail)
|
|
|
|
|
|
|
|
newPathStack := make([]interface{}, len(pathStack))
|
|
|
|
copy(newPathStack, pathStack)
|
|
|
|
return NodeContext{
|
|
|
|
Node: node,
|
|
|
|
Head: head,
|
|
|
|
Tail: newTail,
|
|
|
|
PathStack: newPathStack,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-28 07:19:37 +00:00
|
|
|
type NavigationStrategy interface {
|
|
|
|
FollowAlias(nodeContext NodeContext) bool
|
|
|
|
AutoCreateMap(nodeContext NodeContext) bool
|
|
|
|
Visit(nodeContext NodeContext) error
|
|
|
|
// node key is the string value of the last element in the path stack
|
|
|
|
// we use it to match against the pathExpression in head.
|
|
|
|
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
|
|
|
|
GetVisitedNodes() []*NodeContext
|
2019-12-29 22:21:21 +00:00
|
|
|
DebugVisitedNodes()
|
2019-12-28 07:19:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type NavigationStrategyImpl struct {
|
|
|
|
followAlias func(nodeContext NodeContext) bool
|
|
|
|
autoCreateMap func(nodeContext NodeContext) bool
|
|
|
|
visit func(nodeContext NodeContext) error
|
|
|
|
visitedNodes []*NodeContext
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
|
|
|
|
return ns.visitedNodes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool {
|
|
|
|
return ns.followAlias(nodeContext)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool {
|
|
|
|
return ns.autoCreateMap(nodeContext)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool {
|
|
|
|
// we should traverse aliases (if enabled), but not visit them :/
|
|
|
|
if len(nodeContext.PathStack) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if ns.alreadyVisited(nodeContext.PathStack) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
parser := NewPathParser()
|
|
|
|
|
|
|
|
return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
|
|
|
|
parser.MatchesNextPathElement(nodeContext, nodeKey))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
|
|
|
|
pathStack := nodeContext.PathStack
|
|
|
|
if len(pathStack) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-12-30 03:51:07 +00:00
|
|
|
if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 {
|
2019-12-28 07:19:37 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
|
|
|
|
parser := NewPathParser()
|
|
|
|
|
|
|
|
// only visit aliases if its an exact match
|
|
|
|
return (nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
|
|
|
|
parser.MatchesNextPathElement(nodeContext, nodeKey))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
|
2019-12-29 22:21:21 +00:00
|
|
|
log.Debug("Visit?, %v, %v", nodeContext.Head, PathStackToString(nodeContext.PathStack))
|
|
|
|
DebugNode(nodeContext.Node)
|
2019-12-28 07:19:37 +00:00
|
|
|
if ns.shouldVisit(nodeContext) {
|
2019-12-29 22:21:21 +00:00
|
|
|
log.Debug("yep, visiting")
|
|
|
|
// pathStack array must be
|
|
|
|
// copied, as append() may sometimes reuse and modify the array
|
2019-12-28 07:19:37 +00:00
|
|
|
ns.visitedNodes = append(ns.visitedNodes, &nodeContext)
|
2019-12-29 22:21:21 +00:00
|
|
|
ns.DebugVisitedNodes()
|
2019-12-28 07:19:37 +00:00
|
|
|
return ns.visit(nodeContext)
|
|
|
|
}
|
2019-12-29 22:21:21 +00:00
|
|
|
log.Debug("nope, skip it")
|
2019-12-28 07:19:37 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-29 22:21:21 +00:00
|
|
|
func (ns *NavigationStrategyImpl) DebugVisitedNodes() {
|
2019-12-30 03:51:07 +00:00
|
|
|
log.Debug("Visited Nodes:")
|
2019-12-29 22:21:21 +00:00
|
|
|
for _, candidate := range ns.visitedNodes {
|
|
|
|
log.Debug(" - %v", PathStackToString(candidate.PathStack))
|
2019-12-28 07:19:37 +00:00
|
|
|
}
|
2019-12-29 22:21:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
|
|
|
|
log.Debug("checking already visited pathStack: %v", PathStackToString(pathStack))
|
2019-12-28 07:19:37 +00:00
|
|
|
for _, candidate := range ns.visitedNodes {
|
|
|
|
candidatePathStack := candidate.PathStack
|
|
|
|
if patchStacksMatch(candidatePathStack, pathStack) {
|
|
|
|
log.Debug("paths match, already seen it")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
log.Debug("never seen it before!")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
|
2019-12-29 22:21:21 +00:00
|
|
|
log.Debug("checking against path: %v", PathStackToString(path1))
|
2019-12-28 07:19:37 +00:00
|
|
|
|
|
|
|
if len(path1) != len(path2) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for index, p1Value := range path1 {
|
|
|
|
|
|
|
|
p2Value := path2[index]
|
|
|
|
if p1Value != p2Value {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
|
|
|
|
}
|