Refactored!

This commit is contained in:
Mike Farah 2019-12-28 20:19:37 +13:00
parent df52383ffb
commit 0652f67a91
9 changed files with 177 additions and 178 deletions

View File

@ -12,12 +12,12 @@ type DataNavigator interface {
}
type navigator struct {
navigationSettings NavigationSettings
navigationStrategy NavigationStrategy
}
func NewDataNavigator(navigationSettings NavigationSettings) DataNavigator {
func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
return &navigator{
navigationSettings: navigationSettings,
navigationStrategy: NavigationStrategy,
}
}
@ -38,7 +38,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat
return n.recurse(value, path[0], path[1:], pathStack)
}
log.Debug("should I visit?")
return n.navigationSettings.Visit(value, head, path, pathStack)
return n.navigationStrategy.Visit(NodeContext{value, head, path, pathStack})
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
@ -66,7 +66,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true {
if n.navigationStrategy.FollowAlias(NodeContext{value, head, tail, pathStack}) == true {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
@ -83,7 +83,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
log.Debug("should I traverse? %v", head)
DebugNode(value)
newPathStack := append(pathStack, contents[indexInMap].Value)
if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPathStack, contents[indexInMap].Value) == true {
if n.navigationStrategy.ShouldTraverse(NodeContext{contents[indexInMap+1], head, tail, newPathStack}, contents[indexInMap].Value) == true {
log.Debug("yep!")
traversedEntry = true
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
@ -98,7 +98,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
return errorVisiting
}
if traversedEntry == true || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false {
if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NodeContext{value, head, tail, pathStack}) == false {
return nil
}
@ -136,7 +136,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st
// if we don't find a match directly on this node first.
errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
if errorVisitedDirectEntries != nil || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false {
if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NodeContext{node, head, tail, pathStack}) == false {
return errorVisitedDirectEntries
}
return n.visitAliases(contents, head, tail, pathStack, visit)

View File

@ -6,38 +6,39 @@ import (
yaml "gopkg.in/yaml.v3"
)
func DeleteNavigationSettings(lastBit string) NavigationSettings {
func DeleteNavigationStrategy(pathElementToDelete string) NavigationStrategy {
parser := NewPathParser()
return &NavigationSettingsImpl{
visitedNodes: []*VisitedNode{},
followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
autoCreateMap: func(nodeContext NodeContext) bool {
return true
},
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
log.Debug("need to find and delete %v in here", lastBit)
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
log.Debug("need to find and delete %v in here", pathElementToDelete)
DebugNode(node)
if node.Kind == yaml.SequenceNode {
newContent, errorDeleting := deleteFromArray(node.Content, lastBit)
newContent, errorDeleting := deleteFromArray(node.Content, pathElementToDelete)
if errorDeleting != nil {
return errorDeleting
}
node.Content = newContent
} else if node.Kind == yaml.MappingNode {
node.Content = deleteFromMap(parser, node.Content, pathStack, lastBit)
node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
}
return nil
},
}
}
func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, lastBit string) []*yaml.Node {
func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete string) []*yaml.Node {
newContents := make([]*yaml.Node, 0)
for index := 0; index < len(contents); index = index + 2 {
keyNode := contents[index]
valueNode := contents[index+1]
if pathParser.MatchesNextPathElement(keyNode, lastBit, []string{}, pathStack, keyNode.Value) == false {
if pathParser.MatchesNextPathElement(NodeContext{keyNode, pathElementToDelete, []string{}, pathStack}, keyNode.Value) == false {
log.Debug("adding node %v", keyNode.Value)
newContents = append(newContents, keyNode, valueNode)
} else {
@ -47,13 +48,13 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int
return newContents
}
func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) {
func deleteFromArray(content []*yaml.Node, pathElementToDelete string) ([]*yaml.Node, error) {
if lastBit == "*" {
if pathElementToDelete == "*" {
return make([]*yaml.Node, 0), nil
}
var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
var index, err = strconv.ParseInt(pathElementToDelete, 10, 64) // nolint
if err != nil {
return content, err
}

View File

@ -57,7 +57,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind {
}
type YqLib interface {
Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error)
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
New(path string) yaml.Node
}
@ -73,12 +73,12 @@ func NewYqLib(l *logging.Logger) YqLib {
}
}
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) {
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
var paths = l.parser.ParsePath(path)
navigationSettings := ReadNavigationSettings()
navigator := NewDataNavigator(navigationSettings)
NavigationStrategy := ReadNavigationStrategy()
navigator := NewDataNavigator(NavigationStrategy)
error := navigator.Traverse(rootNode, paths)
return navigationSettings.GetVisitedNodes(), error
return NavigationStrategy.GetVisitedNodes(), error
}
@ -93,12 +93,12 @@ func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
switch updateCommand.Command {
case "update":
var paths = l.parser.ParsePath(updateCommand.Path)
navigator := NewDataNavigator(UpdateNavigationSettings(updateCommand.Value))
navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand.Value))
return navigator.Traverse(rootNode, paths)
case "delete":
var paths = l.parser.ParsePath(updateCommand.Path)
lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1]
navigator := NewDataNavigator(DeleteNavigationSettings(lastBit))
navigator := NewDataNavigator(DeleteNavigationStrategy(lastBit))
return navigator.Traverse(rootNode, newTail)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)

View File

@ -1,122 +0,0 @@
package yqlib
import (
"fmt"
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{}, lastBit string) 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 (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) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool {
// we should traverse aliases (if enabled), but not visit them :/
if len(pathStack) == 0 {
return true
}
if ns.alreadyVisited(pathStack) {
return false
}
parser := NewPathParser()
return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" &&
parser.MatchesNextPathElement(node, head, tail, pathStack, 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])
parser := NewPathParser()
// only visit aliases if its an exact match
return (lastBit == "<<" && head == "<<") || (lastBit != "<<" &&
parser.MatchesNextPathElement(node, head, tail, pathStack, 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
}

View File

@ -0,0 +1,124 @@
package yqlib
import (
"fmt"
yaml "gopkg.in/yaml.v3"
)
type NodeContext struct {
Node *yaml.Node
Head string
Tail []string
PathStack []interface{}
}
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
}
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 {
// we should traverse aliases (if enabled), but not visit them :/
pathStack := nodeContext.PathStack
if len(pathStack) == 0 {
return true
}
if ns.alreadyVisited(pathStack) {
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 {
if ns.shouldVisit(nodeContext) {
ns.visitedNodes = append(ns.visitedNodes, &nodeContext)
log.Debug("adding to visited nodes, %v", nodeContext.Head)
return ns.visit(nodeContext)
}
return nil
}
func (ns *NavigationStrategyImpl) 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
}

View File

@ -2,13 +2,11 @@ package yqlib
import (
"strings"
yaml "gopkg.in/yaml.v3"
)
type PathParser interface {
ParsePath(path string) []string
MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
}
type pathParser struct{}
@ -22,15 +20,16 @@ func NewPathParser() PathParser {
* head: path element expression to match against
* tail: remaining path element expressions
* pathStack: stack of actual paths we've matched to get to node
* lastBit: actual value of this nodes 'key' or index.
* nodeKey: actual value of this nodes 'key' or index.
*/
func (p *pathParser) MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool {
func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool {
head := nodeContext.Head
var prefixMatch = strings.TrimSuffix(head, "*")
if prefixMatch != head {
log.Debug("prefix match, %v", strings.HasPrefix(lastBit, prefixMatch))
return strings.HasPrefix(lastBit, prefixMatch)
log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))
return strings.HasPrefix(nodeKey, prefixMatch)
}
return lastBit == head
return nodeKey == head
}
func (p *pathParser) ParsePath(path string) []string {

View File

@ -1,19 +1,15 @@
package yqlib
import (
yaml "gopkg.in/yaml.v3"
)
func ReadNavigationSettings() NavigationSettings {
return &NavigationSettingsImpl{
visitedNodes: []*VisitedNode{},
followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
func ReadNavigationStrategy() NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
followAlias: func(nodeContext NodeContext) bool {
return true
},
autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
autoCreateMap: func(nodeContext NodeContext) bool {
return false
},
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
visit: func(nodeContext NodeContext) error {
return nil
},
}

View File

@ -4,16 +4,17 @@ import (
yaml "gopkg.in/yaml.v3"
)
func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings {
return &NavigationSettingsImpl{
visitedNodes: []*VisitedNode{},
followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
func UpdateNavigationStrategy(changesToApply *yaml.Node) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
autoCreateMap: func(nodeContext NodeContext) bool {
return true
},
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
log.Debug("going to update")
DebugNode(node)
log.Debug("with")

6
yq.go
View File

@ -257,7 +257,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorParsingDocIndex
}
var matchingNodes []*yqlib.VisitedNode
var matchingNodes []*yqlib.NodeContext
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
@ -292,7 +292,7 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
return nil
}
func appendDocument(originalMatchingNodes []*yqlib.VisitedNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.VisitedNode, error) {
func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) {
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
yqlib.DebugNode(&dataBucket)
if !updateAll && currentIndex != docIndexInt {
@ -337,7 +337,7 @@ func printValue(node *yaml.Node, cmd *cobra.Command) error {
return nil
}
func printResults(matchingNodes []*yqlib.VisitedNode, cmd *cobra.Command) error {
func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error {
if len(matchingNodes) == 0 {
log.Debug("no matching results, nothing to print")
return nil