yq/pkg/yqlib/navigation_strategy.go

146 lines
3.8 KiB
Go
Raw Normal View History

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
}