2019-11-23 03:52:29 +00:00
|
|
|
package yqlib
|
2015-10-03 05:10:29 +00:00
|
|
|
|
2019-12-27 21:51:54 +00:00
|
|
|
import (
|
2020-02-12 04:40:21 +00:00
|
|
|
"fmt"
|
2019-12-31 02:21:39 +00:00
|
|
|
"strconv"
|
2019-12-27 21:51:54 +00:00
|
|
|
"strings"
|
2020-02-28 04:01:46 +00:00
|
|
|
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
2019-12-27 21:51:54 +00:00
|
|
|
)
|
|
|
|
|
2019-12-01 19:44:44 +00:00
|
|
|
type PathParser interface {
|
2020-02-12 04:40:21 +00:00
|
|
|
ParsePath(path string) []interface{}
|
2019-12-28 07:19:37 +00:00
|
|
|
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
|
2020-01-11 08:30:27 +00:00
|
|
|
IsPathExpression(pathElement string) bool
|
2015-10-03 05:10:29 +00:00
|
|
|
}
|
|
|
|
|
2019-12-03 04:50:32 +00:00
|
|
|
type pathParser struct{}
|
2019-12-01 19:44:44 +00:00
|
|
|
|
|
|
|
func NewPathParser() PathParser {
|
2019-12-03 04:50:32 +00:00
|
|
|
return &pathParser{}
|
2019-12-01 19:44:44 +00:00
|
|
|
}
|
|
|
|
|
2020-01-11 08:52:33 +00:00
|
|
|
func matchesString(expression string, value string) bool {
|
|
|
|
var prefixMatch = strings.TrimSuffix(expression, "*")
|
|
|
|
if prefixMatch != expression {
|
|
|
|
log.Debug("prefix match, %v", strings.HasPrefix(value, prefixMatch))
|
|
|
|
return strings.HasPrefix(value, prefixMatch)
|
|
|
|
}
|
|
|
|
return value == expression
|
|
|
|
}
|
|
|
|
|
2020-01-11 08:30:27 +00:00
|
|
|
func (p *pathParser) IsPathExpression(pathElement string) bool {
|
|
|
|
return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==")
|
|
|
|
}
|
|
|
|
|
2019-12-27 21:51:54 +00:00
|
|
|
/**
|
|
|
|
* node: node that we may traverse/visit
|
|
|
|
* head: path element expression to match against
|
|
|
|
* tail: remaining path element expressions
|
|
|
|
* pathStack: stack of actual paths we've matched to get to node
|
2019-12-28 07:19:37 +00:00
|
|
|
* nodeKey: actual value of this nodes 'key' or index.
|
2019-12-27 21:51:54 +00:00
|
|
|
*/
|
2019-12-28 07:19:37 +00:00
|
|
|
func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool {
|
|
|
|
head := nodeContext.Head
|
2019-12-30 03:51:07 +00:00
|
|
|
if head == "**" || head == "*" {
|
|
|
|
return true
|
|
|
|
}
|
2020-02-12 04:40:21 +00:00
|
|
|
var headString = fmt.Sprintf("%v", head)
|
|
|
|
|
2020-02-28 04:01:46 +00:00
|
|
|
if strings.Contains(headString, "==") && nodeContext.Node.Kind != yaml.ScalarNode {
|
2020-01-11 07:52:15 +00:00
|
|
|
log.Debug("ooh deep recursion time")
|
2020-02-12 04:40:21 +00:00
|
|
|
result := strings.SplitN(headString, "==", 2)
|
2020-01-11 07:52:15 +00:00
|
|
|
path := strings.TrimSpace(result[0])
|
|
|
|
value := strings.TrimSpace(result[1])
|
|
|
|
log.Debug("path %v", path)
|
|
|
|
log.Debug("value %v", value)
|
|
|
|
DebugNode(nodeContext.Node)
|
|
|
|
navigationStrategy := FilterMatchingNodesNavigationStrategy(value)
|
|
|
|
|
|
|
|
navigator := NewDataNavigator(navigationStrategy)
|
|
|
|
err := navigator.Traverse(nodeContext.Node, p.ParsePath(path))
|
|
|
|
if err != nil {
|
2020-01-30 04:11:47 +00:00
|
|
|
log.Error("Error deep recursing - ignoring")
|
|
|
|
log.Error(err.Error())
|
2020-01-11 07:52:15 +00:00
|
|
|
}
|
|
|
|
log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes()))
|
|
|
|
return len(navigationStrategy.GetVisitedNodes()) > 0
|
2020-02-28 04:24:16 +00:00
|
|
|
} else if strings.Contains(headString, "==") && nodeContext.Node.Kind == yaml.ScalarNode {
|
|
|
|
result := strings.SplitN(headString, "==", 2)
|
|
|
|
path := strings.TrimSpace(result[0])
|
|
|
|
value := strings.TrimSpace(result[1])
|
|
|
|
if path == "." {
|
|
|
|
log.Debug("need to match scalar")
|
|
|
|
return matchesString(value, nodeContext.Node.Value)
|
|
|
|
}
|
2020-01-11 07:52:15 +00:00
|
|
|
}
|
|
|
|
|
2019-12-31 02:21:39 +00:00
|
|
|
if head == "+" {
|
|
|
|
log.Debug("head is +, nodeKey is %v", nodeKey)
|
|
|
|
var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint
|
|
|
|
if err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2020-01-11 08:52:33 +00:00
|
|
|
|
2020-02-12 04:40:21 +00:00
|
|
|
return matchesString(headString, nodeKey)
|
2019-12-27 21:51:54 +00:00
|
|
|
}
|
|
|
|
|
2020-02-12 04:40:21 +00:00
|
|
|
func (p *pathParser) ParsePath(path string) []interface{} {
|
|
|
|
var paths = make([]interface{}, 0)
|
2019-12-09 02:44:53 +00:00
|
|
|
if path == "" {
|
2020-02-12 04:40:21 +00:00
|
|
|
return paths
|
2019-12-09 02:44:53 +00:00
|
|
|
}
|
2020-02-12 04:40:21 +00:00
|
|
|
return p.parsePathAccum(paths, path)
|
2019-12-01 19:44:44 +00:00
|
|
|
}
|
|
|
|
|
2020-02-12 04:40:21 +00:00
|
|
|
func (p *pathParser) parsePathAccum(paths []interface{}, remaining string) []interface{} {
|
2019-12-01 19:44:44 +00:00
|
|
|
head, tail := p.nextYamlPath(remaining)
|
2015-10-03 05:10:29 +00:00
|
|
|
if tail == "" {
|
|
|
|
return append(paths, head)
|
|
|
|
}
|
2019-12-01 19:44:44 +00:00
|
|
|
return p.parsePathAccum(append(paths, head), tail)
|
2015-10-03 05:10:29 +00:00
|
|
|
}
|
|
|
|
|
2020-02-12 04:40:21 +00:00
|
|
|
func (p *pathParser) nextYamlPath(path string) (pathElement interface{}, remaining string) {
|
2015-10-03 05:10:29 +00:00
|
|
|
switch path[0] {
|
|
|
|
case '[':
|
|
|
|
// e.g [0].blah.cat -> we need to return "0" and "blah.cat"
|
2020-02-12 04:40:21 +00:00
|
|
|
var value, remainingBit = p.search(path[1:], []uint8{']'}, true)
|
|
|
|
var number, errParsingInt = strconv.ParseInt(value, 10, 64) // nolint
|
|
|
|
if errParsingInt == nil {
|
|
|
|
return number, remainingBit
|
|
|
|
}
|
|
|
|
return value, remainingBit
|
2015-10-03 05:10:29 +00:00
|
|
|
case '"':
|
|
|
|
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
|
2019-12-01 19:44:44 +00:00
|
|
|
return p.search(path[1:], []uint8{'"'}, true)
|
2020-01-11 07:52:15 +00:00
|
|
|
case '(':
|
|
|
|
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
|
|
|
|
return p.search(path[1:], []uint8{')'}, true)
|
2015-10-03 05:10:29 +00:00
|
|
|
default:
|
|
|
|
// e.g "a.blah.cat" -> return "a" and "blah.cat"
|
2020-01-11 07:52:15 +00:00
|
|
|
return p.search(path[0:], []uint8{'.', '[', '"', '('}, false)
|
2015-10-03 05:10:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-03 04:50:32 +00:00
|
|
|
func (p *pathParser) search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) {
|
2015-10-03 05:10:29 +00:00
|
|
|
for i := 0; i < len(path); i++ {
|
|
|
|
var char = path[i]
|
2019-12-01 19:44:44 +00:00
|
|
|
if p.contains(matchingChars, char) {
|
2015-10-03 05:10:29 +00:00
|
|
|
var remainingStart = i + 1
|
|
|
|
if skipNext {
|
|
|
|
remainingStart = remainingStart + 1
|
|
|
|
} else if !skipNext && char != '.' {
|
|
|
|
remainingStart = i
|
|
|
|
}
|
|
|
|
if remainingStart > len(path) {
|
|
|
|
remainingStart = len(path)
|
|
|
|
}
|
2017-09-20 23:40:33 +00:00
|
|
|
return path[0:i], path[remainingStart:]
|
2015-10-03 05:10:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return path, ""
|
|
|
|
}
|
|
|
|
|
2019-12-03 04:50:32 +00:00
|
|
|
func (p *pathParser) contains(matchingChars []uint8, candidate uint8) bool {
|
2015-10-03 05:10:29 +00:00
|
|
|
for _, a := range matchingChars {
|
|
|
|
if a == candidate {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|