diff --git a/commands_test.go b/commands_test.go index 33aeb4a..9c07897 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1077,7 +1077,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } -func TestDeleteYaml(t *testing.T) { +func TestDeleteYamlCmd(t *testing.T) { content := `a: 2 b: c: things diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 84bac23..364985e 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -82,13 +82,12 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat log.Debug("should I traverse? %v", head) DebugNode(value) - newPath := append(pathStack, contents[indexInMap].Value) - - if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPath) == true { + newPathStack := append(pathStack, contents[indexInMap].Value) + if n.navigationSettings.ShouldTraverse(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)) - return n.doTraverse(contents[indexInMap+1], head, tail, newPath) + return n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) } else { log.Debug("nope not traversing") } diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go index dd2aabf..b4392b4 100644 --- a/pkg/yqlib/delete_navigation_settings.go +++ b/pkg/yqlib/delete_navigation_settings.go @@ -7,6 +7,7 @@ import ( ) func DeleteNavigationSettings(lastBit string) NavigationSettings { + parser := NewPathParser() return &NavigationSettingsImpl{ visitedNodes: []*VisitedNode{}, followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool { @@ -16,7 +17,7 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings { return true }, visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error { - log.Debug("need to find %v in here", lastBit) + log.Debug("need to find and delete %v in here", lastBit) DebugNode(node) if node.Kind == yaml.SequenceNode { newContent, errorDeleting := deleteFromArray(node.Content, lastBit) @@ -25,31 +26,33 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings { } node.Content = newContent } else if node.Kind == yaml.MappingNode { - // need to delete in reverse - otherwise the matching indexes - // become incorrect. - // matchingIndices := make([]int, 0) - // _, errorVisiting := n.visitMatchingEntries(node, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error { - // matchingIndices = append(matchingIndices, indexInMap) - // log.Debug("matchingIndices %v", indexInMap) - // return nil - // }) - // log.Debug("delete matching indices now") - // log.Debug("%v", matchingIndices) - // if errorVisiting != nil { - // return errorVisiting - // } - // for i := len(matchingIndices) - 1; i >= 0; i-- { - // indexToDelete := matchingIndices[i] - // log.Debug("deleting index %v, %v", indexToDelete, node.Content[indexToDelete].Value) - // node.Content = append(node.Content[:indexToDelete], node.Content[indexToDelete+2:]...) - // } + node.Content = deleteFromMap(parser, node.Content, pathStack, lastBit) } return nil }, } } +func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, lastBit 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 { + log.Debug("adding node %v", keyNode.Value) + newContents = append(newContents, keyNode, valueNode) + } else { + log.Debug("skipping node %v", keyNode.Value) + } + } + return newContents +} func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) { + + if lastBit == "*" { + return make([]*yaml.Node, 0), nil + } + var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint if err != nil { return content, err diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go index 72d1ae3..f2da767 100644 --- a/pkg/yqlib/navigation_settings.go +++ b/pkg/yqlib/navigation_settings.go @@ -2,7 +2,6 @@ package yqlib import ( "fmt" - "strings" yaml "gopkg.in/yaml.v3" ) @@ -18,7 +17,7 @@ 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 + ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool GetVisitedNodes() []*VisitedNode } @@ -29,16 +28,6 @@ type NavigationSettingsImpl struct { 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 } @@ -51,16 +40,7 @@ func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, ta 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 { +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 @@ -70,9 +50,10 @@ func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, t return false } - lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + parser := NewPathParser() - return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && ns.matchesNextPath(head, lastBit)) + 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 { @@ -86,8 +67,11 @@ func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail } lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + parser := NewPathParser() + // only visit aliases if its an exact match - return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && ns.matchesNextPath(head, lastBit)) + return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && + parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit)) } diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 8315705..8ae73cc 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -1,7 +1,14 @@ 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 } type pathParser struct{} @@ -10,6 +17,22 @@ func NewPathParser() PathParser { return &pathParser{} } +/** + * node: node that we may traverse/visit + * 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. + */ +func (p *pathParser) MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool { + var prefixMatch = strings.TrimSuffix(head, "*") + if prefixMatch != head { + log.Debug("prefix match, %v", strings.HasPrefix(lastBit, prefixMatch)) + return strings.HasPrefix(lastBit, prefixMatch) + } + return lastBit == head +} + func (p *pathParser) ParsePath(path string) []string { if path == "" { return []string{}