yq/pkg/yqlib/navigation_strategy.go

165 lines
4.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
2020-02-12 04:40:21 +00:00
Head interface{}
Tail []interface{}
2019-12-28 07:19:37 +00:00
PathStack []interface{}
}
2020-02-12 04:40:21 +00:00
func NewNodeContext(node *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) NodeContext {
newTail := make([]interface{}, len(tail))
2019-12-29 22:21:21 +00:00
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
2020-02-07 05:32:39 +00:00
ShouldDeeplyTraverse(nodeContext NodeContext) bool
2020-06-10 23:40:59 +00:00
// when deeply traversing, should we visit all matching nodes, or just leaves?
ShouldOnlyDeeplyVisitLeaves(NodeContext) bool
2019-12-28 07:19:37 +00:00
GetVisitedNodes() []*NodeContext
2019-12-29 22:21:21 +00:00
DebugVisitedNodes()
GetPathParser() PathParser
2019-12-28 07:19:37 +00:00
}
type NavigationStrategyImpl struct {
2020-06-10 23:40:59 +00:00
followAlias func(nodeContext NodeContext) bool
autoCreateMap func(nodeContext NodeContext) bool
visit func(nodeContext NodeContext) error
shouldVisitExtraFn func(nodeContext NodeContext) bool
shouldDeeplyTraverse func(nodeContext NodeContext) bool
shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool
visitedNodes []*NodeContext
pathParser PathParser
}
func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
return ns.pathParser
2019-12-28 07:19:37 +00:00
}
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)
}
2020-02-07 05:32:39 +00:00
func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext) bool {
return ns.shouldDeeplyTraverse(nodeContext)
}
2020-06-10 23:40:59 +00:00
func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool {
return ns.shouldOnlyDeeplyVisitLeaves(nodeContext)
}
2019-12-28 07:19:37 +00:00
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
}
return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))
2019-12-28 07:19:37 +00:00
}
func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
pathStack := nodeContext.PathStack
if len(pathStack) == 0 {
return true
}
2019-12-31 02:21:39 +00:00
log.Debug("tail len %v", len(nodeContext.Tail))
2019-12-28 07:19:37 +00:00
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])
2019-12-31 02:21:39 +00:00
log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head)
2019-12-28 07:19:37 +00:00
// only visit aliases if its an exact match
2020-01-11 07:52:15 +00:00
return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
2019-12-28 07:19:37 +00:00
}
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
2020-01-09 10:18:24 +00:00
log.Debug("Visit?, %v, %v", nodeContext.Head, pathStackToString(nodeContext.PathStack))
2019-12-29 22:21:21 +00:00
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 {
2020-01-09 10:18:24 +00:00
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 {
2020-01-09 10:18:24 +00:00
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 {
2020-01-09 10:18:24 +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
}