extracted out operators

This commit is contained in:
Mike Farah 2020-10-10 15:00:39 +11:00
parent 23083ed974
commit 8170eec6d1
4 changed files with 132 additions and 100 deletions

View File

@ -1,11 +1,14 @@
package treeops package treeops
import ( import (
"fmt"
"github.com/elliotchance/orderedmap" "github.com/elliotchance/orderedmap"
) )
type dataTreeNavigator struct { type dataTreeNavigator struct {
traverser Traverser leafTraverser LeafTraverser
operatorHandlers map[OperationType]OperatorHandler
} }
type NavigationPrefs struct { type NavigationPrefs struct {
@ -17,8 +20,15 @@ type DataTreeNavigator interface {
} }
func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator { func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
traverse := NewTraverser(navigationPrefs) leafTraverser := NewLeafTraverser(navigationPrefs)
return &dataTreeNavigator{traverse} operatorHandlers := make(map[OperationType]OperatorHandler)
operatorHandlers[Traverse] = TraverseOperator
operatorHandlers[Equals] = EqualsOperator
operatorHandlers[Or] = UnionOperator
operatorHandlers[And] = IntersectionOperator
return &dataTreeNavigator{leafTraverser, operatorHandlers}
} }
func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *PathElement) (*orderedmap.OrderedMap, error) { func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *PathElement) (*orderedmap.OrderedMap, error) {
@ -28,7 +38,7 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *
var err error var err error
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
newNodes, err = d.traverser.Traverse(el.Value.(*CandidateNode), pathNode) newNodes, err = d.leafTraverser.Traverse(el.Value.(*CandidateNode), pathNode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -40,73 +50,6 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *
return matchingNodeMap, nil return matchingNodeMap, nil
} }
func (d *dataTreeNavigator) equalsOperation(matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debugf("-- equalsOperation")
var results = orderedmap.NewOrderedMap()
for el := matchMap.Front(); el != nil; el = el.Next() {
elMap := orderedmap.NewOrderedMap()
elMap.Set(el.Key, el.Value)
//need to splat matching nodes, then search through them
splatter := &PathTreeNode{PathElement: &PathElement{
PathElementType: PathKey,
Value: "*",
StringValue: "*",
}}
children, err := d.getMatchingNodes(elMap, splatter)
log.Debugf("-- splatted matches, ")
if err != nil {
return nil, err
}
for childEl := children.Front(); childEl != nil; childEl = childEl.Next() {
childMap := orderedmap.NewOrderedMap()
childMap.Set(childEl.Key, childEl.Value)
childMatches, errChild := d.getMatchingNodes(childMap, pathNode.Lhs)
if errChild != nil {
return nil, errChild
}
if d.containsMatchingValue(childMatches, pathNode.Rhs.PathElement.StringValue) {
results.Set(childEl.Key, childEl.Value)
}
}
}
return results, nil
}
func (d *dataTreeNavigator) containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool {
log.Debugf("-- findMatchingValues")
for el := matchMap.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
if Match(node.Node.Value, valuePattern) {
return true
}
}
return false
}
func (d *dataTreeNavigator) setFunction(op OperationType, lhs *orderedmap.OrderedMap, rhs *orderedmap.OrderedMap) *orderedmap.OrderedMap {
if op == Or {
for el := rhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
lhs.Set(node.getKey(), node)
}
return lhs
}
var matchingNodeMap = orderedmap.NewOrderedMap()
for el := lhs.Front(); el != nil; el = el.Next() {
_, exists := rhs.Get(el.Key)
if exists {
matchingNodeMap.Set(el.Key, el.Value)
}
}
return matchingNodeMap
}
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) { func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pathNode *PathTreeNode) ([]*CandidateNode, error) {
var matchingNodeMap = orderedmap.NewOrderedMap() var matchingNodeMap = orderedmap.NewOrderedMap()
@ -132,33 +75,12 @@ func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMa
if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex { if pathNode.PathElement.PathElementType == PathKey || pathNode.PathElement.PathElementType == ArrayIndex {
return d.traverse(matchingNodes, pathNode.PathElement) return d.traverse(matchingNodes, pathNode.PathElement)
} else { } else {
var lhs, rhs *orderedmap.OrderedMap handler := d.operatorHandlers[pathNode.PathElement.OperationType]
var err error if handler != nil {
switch pathNode.PathElement.OperationType { return handler(d, matchingNodes, pathNode)
case Traverse: } else {
lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs) return nil, fmt.Errorf("Unknown operator %v", pathNode.PathElement.OperationType)
if err != nil {
return nil, err
} }
return d.getMatchingNodes(lhs, pathNode.Rhs)
case Or, And:
lhs, err = d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err = d.getMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
return d.setFunction(pathNode.PathElement.OperationType, lhs, rhs), nil
case Equals:
return d.equalsOperation(matchingNodes, pathNode)
// case EqualsSelf:
// return d.findMatchingValues(matchingNodes, pathNode.Rhs)
default:
return nil, nil
}
} }
} }

View File

@ -8,11 +8,11 @@ type traverser struct {
prefs NavigationPrefs prefs NavigationPrefs
} }
type Traverser interface { type LeafTraverser interface {
Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) Traverse(matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error)
} }
func NewTraverser(navigationPrefs NavigationPrefs) Traverser { func NewLeafTraverser(navigationPrefs NavigationPrefs) LeafTraverser {
return &traverser{navigationPrefs} return &traverser{navigationPrefs}
} }

View File

@ -8,6 +8,8 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var log = logging.MustGetLogger("yq-treeops")
type CandidateNode struct { type CandidateNode struct {
Node *yaml.Node // the actual node Node *yaml.Node // the actual node
Path []interface{} /// the path we took to get to this node Path []interface{} /// the path we took to get to this node
@ -22,7 +24,19 @@ func (n *CandidateNode) getKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path) return fmt.Sprintf("%v - %v", n.Document, n.Path)
} }
var log = logging.MustGetLogger("yq-treeops") type YqTreeLib interface {
Get(rootNode *yaml.Node, path string) ([]*CandidateNode, error)
// GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
// Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
// New(path string) yaml.Node
// PathStackToString(pathStack []interface{}) string
// MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
}
type lib struct {
treeCreator PathTreeCreator
}
func NodeToString(node *CandidateNode) string { func NodeToString(node *CandidateNode) string {
if !log.IsEnabledFor(logging.DEBUG) { if !log.IsEnabledFor(logging.DEBUG) {

View File

@ -0,0 +1,96 @@
package treeops
import "github.com/elliotchance/orderedmap"
type OperatorHandler func(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error)
func TraverseOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
return d.getMatchingNodes(lhs, pathNode.Rhs)
}
func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
for el := rhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
lhs.Set(node.getKey(), node)
}
return lhs, nil
}
func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.getMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
var matchingNodeMap = orderedmap.NewOrderedMap()
for el := lhs.Front(); el != nil; el = el.Next() {
_, exists := rhs.Get(el.Key)
if exists {
matchingNodeMap.Set(el.Key, el.Value)
}
}
return matchingNodeMap, nil
}
func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debugf("-- equalsOperation")
var results = orderedmap.NewOrderedMap()
for el := matchMap.Front(); el != nil; el = el.Next() {
elMap := orderedmap.NewOrderedMap()
elMap.Set(el.Key, el.Value)
//need to splat matching nodes, then search through them
splatter := &PathTreeNode{PathElement: &PathElement{
PathElementType: PathKey,
Value: "*",
StringValue: "*",
}}
children, err := d.getMatchingNodes(elMap, splatter)
log.Debugf("-- splatted matches, ")
if err != nil {
return nil, err
}
for childEl := children.Front(); childEl != nil; childEl = childEl.Next() {
childMap := orderedmap.NewOrderedMap()
childMap.Set(childEl.Key, childEl.Value)
childMatches, errChild := d.getMatchingNodes(childMap, pathNode.Lhs)
if errChild != nil {
return nil, errChild
}
if containsMatchingValue(childMatches, pathNode.Rhs.PathElement.StringValue) {
results.Set(childEl.Key, childEl.Value)
}
}
}
return results, nil
}
func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool {
log.Debugf("-- findMatchingValues")
for el := matchMap.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
if Match(node.Node.Value, valuePattern) {
return true
}
}
return false
}