Skip to content

Commit

Permalink
Merge pull request #45 from JefeDavis/implement-keyName-operator
Browse files Browse the repository at this point in the history
feat: add propertyName operator
  • Loading branch information
JefeDavis authored Apr 30, 2021
2 parents ea62dcd + 734bf0e commit 4dbf4b3
Show file tree
Hide file tree
Showing 6 changed files with 565 additions and 28 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ Valid paths are strings conforming to the following BNF syntax.
<recursive descent> <subpath>
<child> ::= <dot child> | <bracket child>
<dot child> ::= "." <dotted child name> | ".*" ; named child (restricted characters) or all children
<bracket child> ::= "[" <child names> "]" ; named children
<dot child> ::= "." <dotted child name> | ".*" ; named child (restricted characters) or all children
<bracket child> ::= "[" <child names> "]" | "[" <child names> "]~" ; named children | property names of children
<child names> ::= <child name> |
<child name> "," <child names>
<undotted child> ::= <dotted child name> | ; named child (restricted characters)
<dotted child name><array access> | ; array access of named child
<dotted child name>"~" ; property name of child
"*" ; all children
"*" <array access> ; array access of all children
<child name> ::= "'" <single quoted string> "'" |
Expand Down Expand Up @@ -112,6 +113,9 @@ Although either form `.childname` or `['childname']` accepts a child name with e

As a special case, `.*` also matches all the nodes in each sequence node in the input slice.

## Property Name:
The Property Name Operator `~` can be included after a child name in the form of `.childname~`, `['childname']~` or `['childname1', "childname2"]~` to return the property name of the node instead of the value. this can only be used on the last part of the path

### Recursive Descent: `..childname` or `..*`

A matcher of the form `..childname` selects all the descendants of the nodes in the input slice (including those nodes) with the given name (using the same rules as the child matcher). The output slice consists of all the matching descendants.
Expand Down
3 changes: 2 additions & 1 deletion pkg/yamlpath/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ spec:

err = e.Encode(&n)
if err != nil {
log.Fatalf("cannot marshal node: %v", err)
log.Printf("Error: cannot marshal node: %v", err)
return
}

z := `apiVersion: apps/v1
Expand Down
44 changes: 41 additions & 3 deletions pkg/yamlpath/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const (
lexemeFilterBooleanLiteral
lexemeFilterNullLiteral
lexemeFilterRegularExpressionLiteral
lexemePropertyName
lexemeBracketPropertyName
lexemeArraySubscriptPropertyName
lexemeEOF // lexing complete
)

Expand Down Expand Up @@ -142,7 +145,6 @@ func (l lexeme) literalValue() typedValue {

func sanitiseRegularExpressionLiteral(re string) string {
return strings.ReplaceAll(re[1:len(re)-1], `\/`, `/`)

}

func (l lexeme) comparator() comparator {
Expand Down Expand Up @@ -447,6 +449,7 @@ const (
filterRegularExpressionLiteralDelimiter string = "/"
filterRegularExpressionEscape string = `\`
recursiveDescent string = ".."
propertyName string = "~"
)

var orderingOperators []orderingOperator
Expand Down Expand Up @@ -536,7 +539,7 @@ func lexSubPath(l *lexer) stateFn {
childName := false
for {
le := l.next()
if le == '.' || le == '[' || le == ')' || le == ' ' || le == '&' || le == '|' || le == '=' || le == '!' || le == '>' || le == '<' || le == eof {
if le == '.' || le == '[' || le == ')' || le == ' ' || le == '&' || le == '|' || le == '=' || le == '!' || le == '>' || le == '<' || le == '~' || le == eof {
l.backup()
break
}
Expand All @@ -545,6 +548,14 @@ func lexSubPath(l *lexer) stateFn {
if !childName {
return l.errorf("child name missing")
}
if l.consumed(propertyName) {
if l.peek() != eof {
return l.errorf("property name operator may only be used on last child in path")
}
l.emit(lexemePropertyName)
return lexSubPath
}

l.emit(lexemeDotChild)

return lexOptionalArrayIndex
Expand Down Expand Up @@ -572,6 +583,13 @@ func lexSubPath(l *lexer) stateFn {
if !l.consumedWhitespaced("]") {
return l.errorf(`missing "]" or ","`)
}
if l.consumed(propertyName) {
l.emit(lexemeBracketPropertyName)
if l.peek() != eof {
return l.errorf("property name operator may only be used on last child in path")
}
return lexSubPath
}

l.emit(lexemeBracketChild)

Expand All @@ -589,7 +607,7 @@ func lexSubPath(l *lexer) stateFn {
childName := false
for {
le := l.next()
if le == '.' || le == '[' || le == ']' || le == ')' || le == ' ' || le == '&' || le == '|' || le == '=' || le == '!' || le == '>' || le == '<' || le == eof {
if le == '.' || le == '[' || le == ']' || le == ')' || le == ' ' || le == '&' || le == '|' || le == '=' || le == '!' || le == '>' || le == '<' || le == '~' || le == eof {
l.backup()
break
}
Expand All @@ -598,6 +616,13 @@ func lexSubPath(l *lexer) stateFn {
if !childName {
return l.errorf("child name missing")
}
if l.consumed(propertyName) {
if l.peek() != eof {
return l.errorf("property name operator may only be used on last child in path")
}
l.emit(lexemePropertyName)
return lexSubPath
}
l.emit(lexemeUndottedChild)

return lexOptionalArrayIndex
Expand Down Expand Up @@ -625,6 +650,19 @@ func lexOptionalArrayIndex(l *lexer) stateFn {
if !validateArrayIndex(l) {
return nil
}
if l.consumed(propertyName) {
if l.peek() != eof {
return l.errorf("property name operator can only be used on last item in path")
}
subscript := l.value()
index := strings.TrimSuffix(strings.TrimPrefix(subscript, leftBracket), rightBracket+propertyName)
if index != "*" {
return l.errorf("property name operator can only be used on map nodes")
}
l.emit(lexemeArraySubscriptPropertyName)
return lexSubPath

}
l.emit(lexemeArraySubscript)
}

Expand Down
Loading

0 comments on commit 4dbf4b3

Please sign in to comment.