From 643f2467eeb8736bb7e4df5bcb9416be07ab337a Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Fri, 30 Oct 2020 10:56:45 +1100 Subject: [PATCH] simple anchors --- pkg/yqlib/treeops/candidate_node.go | 1 + pkg/yqlib/treeops/lib.go | 5 +- pkg/yqlib/treeops/operator_multilpy.go | 2 +- pkg/yqlib/treeops/operator_multiply_test.go | 14 ++++++ .../treeops/operator_recursive_descent.go | 19 +++++--- .../operator_recursive_descent_test.go | 10 ++++ pkg/yqlib/treeops/operator_traverse_path.go | 48 ++++++++++--------- .../treeops/operator_traverse_path_test.go | 21 ++++++++ 8 files changed, 87 insertions(+), 33 deletions(-) diff --git a/pkg/yqlib/treeops/candidate_node.go b/pkg/yqlib/treeops/candidate_node.go index 39336b8c..c8d40eea 100644 --- a/pkg/yqlib/treeops/candidate_node.go +++ b/pkg/yqlib/treeops/candidate_node.go @@ -31,6 +31,7 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode) { n.UpdateAttributesFrom(other) n.Node.Content = other.Node.Content n.Node.Value = other.Node.Value + n.Node.Alias = other.Node.Alias } func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { diff --git a/pkg/yqlib/treeops/lib.go b/pkg/yqlib/treeops/lib.go index 5efdaf4f..35fbdbc1 100644 --- a/pkg/yqlib/treeops/lib.go +++ b/pkg/yqlib/treeops/lib.go @@ -49,8 +49,6 @@ var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator} -// var Splat = &OperationType{Type: "SPLAT", NumArgs: 0, Precedence: 40, Handler: SplatOperator} - // var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35} // filters matches if they have the existing path @@ -59,6 +57,7 @@ type Operation struct { Value interface{} StringValue string CandidateNode *CandidateNode // used for Value Path elements + Preferences interface{} } func CreateValueOperation(value interface{}, stringValue string) *Operation { @@ -132,6 +131,8 @@ func NodeToString(node *CandidateNode) string { tag := value.Tag if value.Kind == yaml.DocumentNode { tag = "doc" + } else if value.Kind == yaml.AliasNode { + tag = "alias" } return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, tag, buf.String()) } diff --git a/pkg/yqlib/treeops/operator_multilpy.go b/pkg/yqlib/treeops/operator_multilpy.go index 1dd42173..3aa2feb1 100644 --- a/pkg/yqlib/treeops/operator_multilpy.go +++ b/pkg/yqlib/treeops/operator_multilpy.go @@ -107,7 +107,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid lhsPath := rhs.Path[pathIndexToStartFrom:] assignmentOp := &Operation{OperationType: AssignAttributes} - if rhs.Node.Kind == yaml.ScalarNode { + if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { assignmentOp.OperationType = Assign } rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} diff --git a/pkg/yqlib/treeops/operator_multiply_test.go b/pkg/yqlib/treeops/operator_multiply_test.go index dfad3b80..65c5af22 100644 --- a/pkg/yqlib/treeops/operator_multiply_test.go +++ b/pkg/yqlib/treeops/operator_multiply_test.go @@ -81,6 +81,20 @@ b: "D0, P[], (!!map)::{a: {c: cat}}\n", }, }, + { + document: `{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}`, + expression: `.c * .b`, + expected: []string{ + "D0, P[c], (!!map)::{g: thongs, f: *cat}\n", + }, + }, + { + document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`, + expression: `.c * .a`, + expected: []string{ + "D0, P[c], (!!map)::{g: thongs, c: frog}\n", + }, + }, } func TestMultiplyOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_recursive_descent.go b/pkg/yqlib/treeops/operator_recursive_descent.go index 68673fa5..5a364db6 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent.go +++ b/pkg/yqlib/treeops/operator_recursive_descent.go @@ -2,6 +2,8 @@ package treeops import ( "container/list" + + "gopkg.in/yaml.v3" ) func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { @@ -24,14 +26,17 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) results.PushBack(candidate) - children, err := Splat(d, nodeToMap(candidate)) + if candidate.Node.Kind != yaml.AliasNode { - if err != nil { - return err - } - err = recursiveDecent(d, results, children) - if err != nil { - return err + children, err := Splat(d, nodeToMap(candidate)) + + if err != nil { + return err + } + err = recursiveDecent(d, results, children) + if err != nil { + return err + } } } return nil diff --git a/pkg/yqlib/treeops/operator_recursive_descent_test.go b/pkg/yqlib/treeops/operator_recursive_descent_test.go index d0a654d0..0079efea 100644 --- a/pkg/yqlib/treeops/operator_recursive_descent_test.go +++ b/pkg/yqlib/treeops/operator_recursive_descent_test.go @@ -50,6 +50,16 @@ var recursiveDescentOperatorScenarios = []expressionScenario{ "D0, P[2], (!!bool)::true\n", }, }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `..`, + expected: []string{ + "D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n", + "D0, P[a], (!!map)::&cat {c: frog}\n", + "D0, P[a c], (!!str)::frog\n", + "D0, P[b], (alias)::*cat\n", + }, + }, } func TestRecursiveDescentOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/treeops/operator_traverse_path.go b/pkg/yqlib/treeops/operator_traverse_path.go index 583fab43..1bfa14a0 100644 --- a/pkg/yqlib/treeops/operator_traverse_path.go +++ b/pkg/yqlib/treeops/operator_traverse_path.go @@ -8,8 +8,13 @@ import ( "gopkg.in/yaml.v3" ) +type TraversePreferences struct { + DontFollowAlias bool +} + func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) { - splatOperation := &Operation{OperationType: TraversePath, Value: "[]"} + preferences := &TraversePreferences{DontFollowAlias: true} + splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences} splatTreeNode := &PathTreeNode{Operation: splatOperation} return TraversePathOperator(d, matches, splatTreeNode) } @@ -33,14 +38,20 @@ func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *P return matchingNodeMap, nil } -func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Operation) ([]*CandidateNode, error) { +func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, error) { log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node - if value.Tag == "!!null" && pathNode.Value != "[]" { + followAlias := true + + // if operation.Preferences != nil { + // followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias + // } + + if value.Tag == "!!null" && operation.Value != "[]" { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now - switch pathNode.Value.(type) { + switch operation.Value.(type) { case int, int64: log.Debugf("probably an array") value.Kind = yaml.SequenceNode @@ -54,33 +65,24 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Opera switch value.Kind { case yaml.MappingNode: log.Debug("its a map with %v entries", len(value.Content)/2) - return traverseMap(matchingNode, pathNode) + return traverseMap(matchingNode, operation) case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) - return traverseArray(matchingNode, pathNode) - // default: + return traverseArray(matchingNode, operation) - // if head == "+" { - // return n.appendArray(value, head, tail, pathStack) - // } else if len(value.Content) == 0 && head == "**" { - // return n.navigationStrategy.Visit(nodeContext) - // } - // return n.splatArray(value, head, tail, pathStack) - // } - // case yaml.AliasNode: - // log.Debug("its an alias!") - // DebugNode(value.Alias) - // if n.navigationStrategy.FollowAlias(nodeContext) { - // log.Debug("following the alias") - // return n.recurse(value.Alias, head, tail, pathStack) - // } - // return nil + case yaml.AliasNode: + log.Debug("its an alias!") + if followAlias { + matchingNode.Node = matchingNode.Node.Alias + return traverse(d, matchingNode, operation) + } + return []*CandidateNode{matchingNode}, nil case yaml.DocumentNode: log.Debug("digging into doc node") return traverse(d, &CandidateNode{ Node: matchingNode.Node.Content[0], - Document: matchingNode.Document}, pathNode) + Document: matchingNode.Document}, operation) default: return nil, nil } diff --git a/pkg/yqlib/treeops/operator_traverse_path_test.go b/pkg/yqlib/treeops/operator_traverse_path_test.go index d5ecbc20..8b58573a 100644 --- a/pkg/yqlib/treeops/operator_traverse_path_test.go +++ b/pkg/yqlib/treeops/operator_traverse_path_test.go @@ -83,6 +83,27 @@ var traversePathOperatorScenarios = []expressionScenario{ "D0, P[a mad], (!!str)::things\n", }, }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b`, + expected: []string{ + "D0, P[b], (alias)::*cat\n", + }, + }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b.[]`, + expected: []string{ + "D0, P[b c], (!!str)::frog\n", + }, + }, + { + document: `{a: &cat {c: frog}, b: *cat}`, + expression: `.b.c`, + expected: []string{ + "D0, P[b c], (!!str)::frog\n", + }, + }, } func TestTraversePathOperatorScenarios(t *testing.T) {