yq/pkg/yqlib/path_parser.go

91 lines
2.3 KiB
Go
Raw Normal View History

package yqlib
2015-10-03 05:10:29 +00:00
2019-12-27 21:51:54 +00:00
import (
"strings"
)
type PathParser interface {
ParsePath(path string) []string
2019-12-28 07:19:37 +00:00
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
2015-10-03 05:10:29 +00:00
}
type pathParser struct{}
func NewPathParser() PathParser {
return &pathParser{}
}
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-27 21:51:54 +00:00
var prefixMatch = strings.TrimSuffix(head, "*")
if prefixMatch != head {
2019-12-28 07:19:37 +00:00
log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))
return strings.HasPrefix(nodeKey, prefixMatch)
2019-12-27 21:51:54 +00:00
}
2019-12-28 07:19:37 +00:00
return nodeKey == head
2019-12-27 21:51:54 +00:00
}
func (p *pathParser) ParsePath(path string) []string {
2019-12-09 02:44:53 +00:00
if path == "" {
return []string{}
}
return p.parsePathAccum([]string{}, path)
}
func (p *pathParser) parsePathAccum(paths []string, remaining string) []string {
head, tail := p.nextYamlPath(remaining)
2015-10-03 05:10:29 +00:00
if tail == "" {
return append(paths, head)
}
return p.parsePathAccum(append(paths, head), tail)
2015-10-03 05:10:29 +00:00
}
func (p *pathParser) nextYamlPath(path string) (pathElement string, 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"
return p.search(path[1:], []uint8{']'}, true)
2015-10-03 05:10:29 +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"
return p.search(path[0:], []uint8{'.', '['}, false)
2015-10-03 05:10:29 +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]
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)
}
return path[0:i], path[remainingStart:]
2015-10-03 05:10:29 +00:00
}
}
return path, ""
}
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
}