From 949bf1c1d7afb5ddc4d211ef726cd42bc180cfe9 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 22 Dec 2019 15:15:15 +1100 Subject: [PATCH] Merge anchors - wip --- commands_test.go | 45 +++++++++++ .../{merge-anchor.yml => merge-anchor.yaml} | 8 +- pkg/yqlib/data_navigator.go | 77 +++++++++++++++---- 3 files changed, 110 insertions(+), 20 deletions(-) rename examples/{merge-anchor.yml => merge-anchor.yaml} (69%) diff --git a/commands_test.go b/commands_test.go index fa497bf..9f1349e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -121,6 +121,51 @@ func TestReadAnchorsCmd(t *testing.T) { test.AssertResult(t, "1\n", result.Output) } +func TestReadMergeAnchorsOriginalCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "original\n", result.Output) +} + +func TestReadMergeAnchorsOverrideCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.thing") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "ice\n", result.Output) +} + +func TestReadMergeAnchorsListOriginalCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "original\n", result.Output) +} + +func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.thing") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "coconut\n", result.Output) +} + +func TestReadMergeAnchorsListOverrideCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.c") + if result.Error != nil { + t.Error(result.Error) + } + test.AssertResult(t, "newbar\n", result.Output) +} + func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") diff --git a/examples/merge-anchor.yml b/examples/merge-anchor.yaml similarity index 69% rename from examples/merge-anchor.yml rename to examples/merge-anchor.yaml index cbfcc0e..2459c9a 100644 --- a/examples/merge-anchor.yml +++ b/examples/merge-anchor.yaml @@ -4,12 +4,12 @@ foo: &foo bar: &bar b: 2 + thing: coconut + c: oldbar - -overrideA: +foobarList: <<: [*foo,*bar] - a: vanilla - c: 3 + c: newbar foobar: <<: *foo diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index d852474..abbf052 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -254,24 +254,11 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi // value.Content is a concatenated array of key, value, // so keys are in the even indexes, values in odd. // merge aliases are defined first, but we only want to traverse them - // if we dont find a match on this node first. - for index := len(contents) - 2; index >= 0; index = index - 2 { + // if we don't find a match on this node first. + for index := 0; index < len(contents); index = index + 2 { content := contents[index] n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag) - // only visit aliases if we didn't find a match in this object. - if n.followAliases && !visited && contents[index+1].Kind == yaml.AliasNode { - valueNode := contents[index+1] - - n.log.Debug("need to visit the alias too") - n.DebugNode(valueNode) - visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) - if errorInAlias != nil { - return false, errorInAlias - } - visited = visited || visitedAlias - } - if n.matchesKey(key, content.Value) { n.log.Debug("found a match! %v", content.Value) errorVisiting := visit(contents, index) @@ -281,7 +268,65 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi visited = true } } - return visited, nil + + if visited == true || n.followAliases == false { + return visited, nil + } + + // didnt find a match, lets check the aliases. + // merge aliases are defined first, but we only want to traverse them + // if we don't find a match on this node first. + // traverse them backwards so that the last alias overrides the preceding. + + n.log.Debug("no entry in the map, checking for aliases") + + for index := len(contents) - 2; index >= 0; index = index - 2 { + // content := contents[index] + n.log.Debug("looking for %v", yaml.AliasNode) + + n.log.Debug("searching for aliases key %v kind %v", contents[index].Value, contents[index].Kind) + n.log.Debug("searching for aliases value %v kind %v", contents[index+1].Value, contents[index+1].Kind) + + // only visit aliases if we didn't find a match in this object. + + // NEED TO HANDLE A SEQUENCE OF ALIASES, and search each one. + // probably stop searching after we find a match, because overrides. + + if contents[index+1].Kind == yaml.AliasNode { + valueNode := contents[index+1] + + n.log.Debug("found an alias") + n.DebugNode(contents[index]) + n.DebugNode(valueNode) + visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit) + if errorInAlias != nil { + return false, errorInAlias + } + if visitedAlias == true { + return true, nil + } + } else if contents[index+1].Kind == yaml.SequenceNode { + // could be an array of aliases...need to search this backwards too! + possibleAliasArray := contents[index+1].Content + for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 { + child := possibleAliasArray[aliasIndex] + if child.Kind == yaml.AliasNode { + n.log.Debug("found an alias") + n.DebugNode(child) + visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit) + if errorInAlias != nil { + return false, errorInAlias + } + if visitedAlias == true { + return true, nil + } + } + } + } + + } + n.log.Debug("no aliases") + return false, nil } func (n *navigator) matchesKey(key string, actual string) bool {