yq/pkg/yqlib/navigation_settings.go
2019-12-27 19:06:08 +11:00

139 lines
4.1 KiB
Go

package yqlib
import (
"fmt"
"strings"
yaml "gopkg.in/yaml.v3"
)
type VisitedNode struct {
Node *yaml.Node
Head string
Tail []string
PathStack []interface{}
}
type NavigationSettings interface {
FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
GetVisitedNodes() []*VisitedNode
}
type NavigationSettingsImpl struct {
followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
visitedNodes []*VisitedNode
}
func matches(node *yaml.Node, head string) bool {
var prefixMatch = strings.TrimSuffix(head, "*")
if prefixMatch != head {
log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
return strings.HasPrefix(node.Value, prefixMatch)
}
log.Debug("equals match, %v", node.Value == head)
return node.Value == head
}
func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode {
return ns.visitedNodes
}
func (ns *NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return ns.followAlias(node, head, tail, pathStack)
}
func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return ns.autoCreateMap(node, head, tail, pathStack)
}
func (ns *NavigationSettingsImpl) matchesNextPath(path string, candidate string) bool {
var prefixMatch = strings.TrimSuffix(path, "*")
if prefixMatch != path {
log.Debug("prefix match, %v", strings.HasPrefix(candidate, prefixMatch))
return strings.HasPrefix(candidate, prefixMatch)
}
return candidate == path
}
func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
// we should traverse aliases (if enabled), but not visit them :/
if len(pathStack) == 0 {
return true
}
if ns.alreadyVisited(pathStack) {
return false
}
lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && ns.matchesNextPath(head, lastBit))
}
func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
// we should traverse aliases (if enabled), but not visit them :/
if len(pathStack) == 0 {
return true
}
if ns.alreadyVisited(pathStack) {
return false
}
lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
// only visit aliases if its an exact match
return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && ns.matchesNextPath(head, lastBit))
}
func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
if ns.shouldVisit(node, head, tail, pathStack) {
ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack})
log.Debug("adding to visited nodes, %v", head)
return ns.visit(node, head, tail, pathStack)
}
return nil
}
func (ns *NavigationSettingsImpl) alreadyVisited(pathStack []interface{}) bool {
log.Debug("looking for pathStack")
for _, val := range pathStack {
log.Debug("\t %v", val)
}
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 {
log.Debug("checking against path")
for _, val := range path1 {
log.Debug("\t %v", val)
}
if len(path1) != len(path2) {
return false
}
for index, p1Value := range path1 {
p2Value := path2[index]
if p1Value != p2Value {
return false
}
}
return true
}