// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley // SPDX-License-Identifier: MIT package index import ( "gopkg.in/yaml.v3" ) type nodeMap struct { line int column int node *yaml.Node } // NodeOrigin represents where a node has come from within a specification. This is not useful for single file specs, // but becomes very, very important when dealing with exploded specifications, and we need to know where in the mass // of files a node has come from. type NodeOrigin struct { // Node is the node in question Node *yaml.Node // Line is yhe original line of where the node was found in the original file Line int // Column is the original column of where the node was found in the original file Column int // AbsoluteLocation is the absolute path to the reference was extracted from. // This can either be an absolute path to a file, or a URL. AbsoluteLocation string // Index is the index that contains the node that was located in. Index *SpecIndex } // GetNode returns a node from the spec based on a line and column. The second return var bool is true // if the node was found, false if not. func (index *SpecIndex) GetNode(line int, column int) (*yaml.Node, bool) { if index.nodeMap[line] == nil { return nil, false } node := index.nodeMap[line][column] return node, node != nil } // MapNodes maps all nodes in the document to a map of line/column to node. func (index *SpecIndex) MapNodes(rootNode *yaml.Node) { cruising := make(chan bool) nodeChan := make(chan *nodeMap) go func(nodeChan chan *nodeMap) { for { select { case node, ok := <-nodeChan: if !ok { cruising <- true return } if index.nodeMap[node.line] == nil { index.nodeMap[node.line] = make(map[int]*yaml.Node) } index.nodeMap[node.line][node.column] = node.node } } }(nodeChan) go enjoyALuxuryCruise(rootNode, nodeChan, true) <-cruising close(cruising) index.nodeMapCompleted <- true close(index.nodeMapCompleted) } func (index *SpecIndex) FindNodeOrigin(node *yaml.Node) *NodeOrigin { // local search, then throw up to rolodex for a full search if node != nil { if index.nodeMap[node.Line] != nil { if index.nodeMap[node.Line][node.Column] != nil { foundNode := index.nodeMap[node.Line][node.Column] match := true if foundNode.Value != node.Value || foundNode.Kind != node.Kind || foundNode.Tag != node.Tag { match = false } if len(foundNode.Content) == len(node.Content) { for i := range foundNode.Content { if foundNode.Content[i].Value != node.Content[i].Value { match = false } } } if match { return &NodeOrigin{ Node: foundNode, Line: node.Line, Column: node.Column, AbsoluteLocation: index.specAbsolutePath, Index: index, } } } } } return nil } func enjoyALuxuryCruise(node *yaml.Node, nodeChan chan *nodeMap, root bool) { if len(node.Content) > 0 { for _, child := range node.Content { nodeChan <- &nodeMap{ line: child.Line, column: child.Column, node: child, } enjoyALuxuryCruise(child, nodeChan, false) } } nodeChan <- &nodeMap{ line: node.Line, column: node.Column, node: node, } if root { close(nodeChan) } }