package treeops import ( "fmt" "github.com/elliotchance/orderedmap" "gopkg.in/yaml.v3" ) func TraversePathOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { log.Debugf("-- Traversing") var matchingNodeMap = orderedmap.NewOrderedMap() var newNodes []*CandidateNode var err error for el := matchMap.Front(); el != nil; el = el.Next() { newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.PathElement) if err != nil { return nil, err } for _, n := range newNodes { matchingNodeMap.Set(n.GetKey(), n) } } return matchingNodeMap, nil } func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { log.Debug("Traversing %v", NodeToString(matchingNode)) value := matchingNode.Node if value.Kind == 0 { log.Debugf("Guessing kind") // we must ahve added this automatically, lets guess what it should be now switch pathNode.Value.(type) { case int, int64: log.Debugf("probably an array") value.Kind = yaml.SequenceNode default: log.Debugf("probabel a map") value.Kind = yaml.MappingNode } } switch value.Kind { case yaml.MappingNode: log.Debug("its a map with %v entries", len(value.Content)/2) return traverseMap(matchingNode, pathNode) case yaml.SequenceNode: log.Debug("its a sequence of %v things!", len(value.Content)) return traverseArray(matchingNode, pathNode) // default: // 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.DocumentNode: log.Debug("digging into doc node") return traverse(d, &CandidateNode{ Node: matchingNode.Node.Content[0], Document: matchingNode.Document}, pathNode) default: return nil, nil } } func keyMatches(key *yaml.Node, pathNode *PathElement) bool { return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue) } func traverseMap(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { // 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 don't find a match directly on this node first. //TODO ALIASES, auto creation? var newMatches = make([]*CandidateNode, 0) node := candidate.Node var contents = node.Content for index := 0; index < len(contents); index = index + 2 { key := contents[index] value := contents[index+1] log.Debug("checking %v (%v)", key.Value, key.Tag) if keyMatches(key, pathNode) { log.Debug("MATCHED") newMatches = append(newMatches, &CandidateNode{ Node: value, Path: append(candidate.Path, key.Value), Document: candidate.Document, }) } } if len(newMatches) == 0 { //no matches, create one automagically valueNode := &yaml.Node{} node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) newMatches = append(newMatches, &CandidateNode{ Node: valueNode, Path: append(candidate.Path, pathNode.StringValue), Document: candidate.Document, }) } return newMatches, nil } func traverseArray(candidate *CandidateNode, pathNode *PathElement) ([]*CandidateNode, error) { log.Debug("pathNode Value %v", pathNode.Value) if pathNode.Value == "[]" { var contents = candidate.Node.Content var newMatches = make([]*CandidateNode, len(contents)) var index int64 for index = 0; index < int64(len(contents)); index = index + 1 { newMatches[index] = &CandidateNode{ Document: candidate.Document, Path: append(candidate.Path, index), Node: contents[index], } } return newMatches, nil } index := pathNode.Value.(int64) indexToUse := index contentLength := int64(len(candidate.Node.Content)) for contentLength <= index { candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{}) contentLength = int64(len(candidate.Node.Content)) } if indexToUse < 0 { indexToUse = contentLength + indexToUse } if indexToUse < 0 { return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength) } return []*CandidateNode{&CandidateNode{ Node: candidate.Node.Content[indexToUse], Document: candidate.Document, Path: append(candidate.Path, index), }}, nil }