Added key operator

This commit is contained in:
Mike Farah 2021-11-24 09:57:35 +11:00
parent 7f629d5e36
commit b44fecdfa5
27 changed files with 149 additions and 48 deletions

View File

@ -11,6 +11,7 @@ import (
type CandidateNode struct {
Node *yaml.Node // the actual node
Parent *CandidateNode // parent node
Key *yaml.Node // node key, if this is a value from a map (or index in an array)
LeadingContent string
@ -38,11 +39,41 @@ func (n *CandidateNode) AsList() *list.List {
return elMap
}
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
func (n *CandidateNode) CreateChildInMap(key *yaml.Node, node *yaml.Node) *CandidateNode {
var value interface{} = nil
if key != nil {
value = key.Value
}
return &CandidateNode{
Node: node,
Path: n.createChildPath(path),
Path: n.createChildPath(value),
Parent: n,
Key: key,
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
}
}
func (n *CandidateNode) CreateChildInArray(index int, node *yaml.Node) *CandidateNode {
return &CandidateNode{
Node: node,
Path: n.createChildPath(index),
Parent: n,
Key: &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", index), Tag: "!!int"},
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
}
}
func (n *CandidateNode) CreateReplacement(node *yaml.Node) *CandidateNode {
return &CandidateNode{
Node: node,
Path: n.createChildPath(nil),
Parent: n.Parent,
Key: n.Key,
IsMapKey: n.IsMapKey,
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,

View File

@ -34,3 +34,38 @@ will output
- 1
```
## Update map key
Given a sample.yml file of:
```yaml
a:
x: 3
y: 4
```
then
```bash
yq eval '(.a.x | key) = "meow"' sample.yml
```
will output
```yaml
a:
meow: 3
y: 4
```
## Get comment from map key
Given a sample.yml file of:
```yaml
a:
# comment on key
x: 3
y: 4
```
then
```bash
yq eval '.a.x | key | headComment' sample.yml
```
will output
```yaml
comment on key
```

View File

@ -368,6 +368,8 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`contains`), opToken(containsOpType))
lexer.Add([]byte(`split`), opToken(splitStringOpType))
lexer.Add([]byte(`key`), opToken(getKeyOpType))
lexer.Add([]byte(`keys`), opToken(keysOpType))
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))

View File

@ -78,6 +78,9 @@ var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Preceden
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
var getKeyOpType = &operationType{Type: "GET_KEY", NumArgs: 0, Precedence: 50, Handler: getKeyOperator}
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}

View File

@ -45,10 +45,10 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
lhsNode := lhs.Node
if lhsNode.Tag == "!!null" {
return lhs.CreateChild(nil, rhs.Node), nil
return lhs.CreateReplacement(rhs.Node), nil
}
target := lhs.CreateChild(nil, &yaml.Node{})
target := lhs.CreateReplacement(&yaml.Node{})
switch lhsNode.Kind {
case yaml.MappingNode:
@ -60,7 +60,7 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
case yaml.ScalarNode:
if rhs.Node.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
return nil, fmt.Errorf("%v (%v) cannot be added to a 2%v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
}
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhsNode.Style

View File

@ -56,7 +56,7 @@ func getAliasOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}
return context.ChildContext(results), nil
@ -112,7 +112,7 @@ func getAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
candidate := el.Value.(*CandidateNode)
anchor := candidate.Node.Anchor
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}
return context.ChildContext(results), nil

View File

@ -20,7 +20,7 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
var collectC *CandidateNode
if context.MatchingNodes.Front() != nil {
collectC = context.MatchingNodes.Front().Value.(*CandidateNode).CreateChild(nil, node)
collectC = context.MatchingNodes.Front().Value.(*CandidateNode).CreateReplacement(node)
if len(collectC.Path) > 0 {
collectC.Path = collectC.Path[:len(collectC.Path)-1]
}

View File

@ -37,7 +37,7 @@ func collectObjectOperator(d *dataTreeNavigator, originalContext Context, expres
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode)
for i := 0; i < len(first.Node.Content); i++ {
rotated[i].PushBack(candidateNode.CreateChild(i, candidateNode.Node.Content[i]))
rotated[i].PushBack(candidateNode.CreateChildInArray(i, candidateNode.Node.Content[i]))
}
}

View File

@ -105,7 +105,7 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
comment = subsequentCommentCharaterRegExp.ReplaceAllString(comment, "\n")
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}
return context.ChildContext(results), nil

View File

@ -47,7 +47,7 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
key := contents[index]
value := contents[index+1]
childCandidate := candidate.CreateChild(key.Value, value)
childCandidate := candidate.CreateChildInMap(key, value)
shouldDelete := key.Value == childPath

View File

@ -13,7 +13,7 @@ func getDocumentIndexOperator(d *dataTreeNavigator, context Context, expressionN
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
scalar := candidate.CreateChild(nil, node)
scalar := candidate.CreateReplacement(node)
results.PushBack(scalar)
}
return context.ChildContext(results), nil

View File

@ -61,7 +61,7 @@ func encodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
}
stringContentNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: stringValue}
results.PushBack(candidate.CreateChild(nil, stringContentNode))
results.PushBack(candidate.CreateReplacement(stringContentNode))
}
return context.ChildContext(results), nil
}
@ -85,7 +85,7 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
//first node is a doc
node := unwrapDoc(&dataBucket)
results.PushBack(candidate.CreateChild(nil, node))
results.PushBack(candidate.CreateReplacement(node))
}
return context.ChildContext(results), nil
}

View File

@ -20,7 +20,7 @@ func entrySeqFor(key *yaml.Node, value *yaml.Node) *yaml.Node {
func toEntriesFromMap(candidateNode *CandidateNode) *CandidateNode {
var sequence = &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
var entriesNode = candidateNode.CreateChild(nil, sequence)
var entriesNode = candidateNode.CreateReplacement(sequence)
var contents = unwrapDoc(candidateNode.Node).Content
for index := 0; index < len(contents); index = index + 2 {
@ -34,7 +34,7 @@ func toEntriesFromMap(candidateNode *CandidateNode) *CandidateNode {
func toEntriesfromSeq(candidateNode *CandidateNode) *CandidateNode {
var sequence = &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
var entriesNode = candidateNode.CreateChild(nil, sequence)
var entriesNode = candidateNode.CreateReplacement(sequence)
var contents = unwrapDoc(candidateNode.Node).Content
for index := 0; index < len(contents); index = index + 1 {
@ -94,7 +94,7 @@ func parseEntry(d *dataTreeNavigator, entry *yaml.Node, position int) (*yaml.Nod
func fromEntries(d *dataTreeNavigator, candidateNode *CandidateNode) (*CandidateNode, error) {
var node = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
var mapCandidateNode = candidateNode.CreateChild(nil, node)
var mapCandidateNode = candidateNode.CreateReplacement(node)
var contents = unwrapDoc(candidateNode.Node).Content

View File

@ -15,7 +15,7 @@ func getFilenameOperator(d *dataTreeNavigator, context Context, expressionNode *
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}
@ -30,7 +30,7 @@ func getFileIndexOperator(d *dataTreeNavigator, context Context, expressionNode
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}

View File

@ -67,7 +67,7 @@ func groupBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionNo
resultNode.Content = append(resultNode.Content, groupResultNode)
}
results.PushBack(candidate.CreateChild(nil, resultNode))
results.PushBack(candidate.CreateReplacement(resultNode))
}

View File

@ -7,6 +7,21 @@ import (
"gopkg.in/yaml.v3"
)
func getKeyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- getKeyOperator")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
results.PushBack(candidate.CreateReplacement(candidate.Key))
}
return context.ChildContext(results), nil
}
func keysOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- keysOperator")
@ -24,7 +39,7 @@ func keysOperator(d *dataTreeNavigator, context Context, expressionNode *Express
return Context{}, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", node.Tag)
}
result := candidate.CreateChild(nil, targetNode)
result := candidate.CreateReplacement(targetNode)
results.PushBack(result)
}

View File

@ -37,6 +37,22 @@ var keysOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[]\n",
},
},
{
description: "Update map key",
document: "a:\n x: 3\n y: 4",
expression: `(.a.x | key) = "meow"`,
expected: []string{
"D0, P[], (doc)::a:\n meow: 3\n y: 4\n",
},
},
{
description: "Get comment from map key",
document: "a: \n # comment on key\n x: 3\n y: 4",
expression: `.a.x | key | headComment`,
expected: []string{
"D0, P[a x], (!!str)::comment on key\n",
},
},
}
func TestKeysOperatorScenarios(t *testing.T) {

View File

@ -31,7 +31,7 @@ func lengthOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}

View File

@ -67,7 +67,7 @@ func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, contex
}
func multiplyFloats(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target := lhs.CreateReplacement(&yaml.Node{})
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = "!!float"
@ -85,7 +85,7 @@ func multiplyFloats(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, err
}
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target := lhs.CreateReplacement(&yaml.Node{})
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = "!!int"

View File

@ -31,7 +31,7 @@ func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
content[pathIndex] = createPathNodeFor(path)
}
node.Content = content
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}

View File

@ -66,7 +66,7 @@ func substituteStringOperator(d *dataTreeNavigator, context Context, expressionN
}
targetNode := substitute(node.Value, regEx, replacementText)
result := candidate.CreateChild(nil, targetNode)
result := candidate.CreateReplacement(targetNode)
results.PushBack(result)
}
@ -150,7 +150,7 @@ func match(matchPrefs matchPreferences, regEx *regexp.Regexp, candidate *Candida
createScalarNode("captures", "captures"),
capturesNode,
)
results.PushBack(candidate.CreateChild(nil, node))
results.PushBack(candidate.CreateReplacement(node))
}
@ -187,7 +187,7 @@ func capture(matchPrefs matchPreferences, regEx *regexp.Regexp, candidate *Candi
}
}
results.PushBack(candidate.CreateChild(nil, capturesNode))
results.PushBack(candidate.CreateReplacement(capturesNode))
}
@ -321,7 +321,7 @@ func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *E
return Context{}, fmt.Errorf("cannot join with %v, can only join arrays of scalars", node.Tag)
}
targetNode := join(node.Content, joinStr)
result := candidate.CreateChild(nil, targetNode)
result := candidate.CreateReplacement(targetNode)
results.PushBack(result)
}
@ -365,7 +365,7 @@ func splitStringOperator(d *dataTreeNavigator, context Context, expressionNode *
return Context{}, fmt.Errorf("Cannot split %v, can only split strings", node.Tag)
}
targetNode := split(node.Value, splitStr)
result := candidate.CreateChild(nil, targetNode)
result := candidate.CreateReplacement(targetNode)
results.PushBack(result)
}

View File

@ -100,7 +100,7 @@ func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
style = "<unknown>"
}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}

View File

@ -49,10 +49,10 @@ func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Ca
lhsNode := lhs.Node
if lhsNode.Tag == "!!null" {
return lhs.CreateChild(nil, rhs.Node), nil
return lhs.CreateReplacement(rhs.Node), nil
}
target := lhs.CreateChild(nil, &yaml.Node{})
target := lhs.CreateReplacement(&yaml.Node{})
switch lhsNode.Kind {
case yaml.MappingNode:

View File

@ -55,7 +55,7 @@ func getTagOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: unwrapDoc(candidate.Node).Tag, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
result := candidate.CreateReplacement(node)
results.PushBack(result)
}

View File

@ -70,7 +70,7 @@ func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode
case yaml.DocumentNode:
log.Debug("digging into doc node")
return traverse(d, context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation)
return traverse(d, context, matchingNode.CreateChildInMap(nil, matchingNode.Node.Content[0]), operation)
default:
return list.New(), nil
}
@ -147,7 +147,7 @@ func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesT
} else if node.Kind == yaml.MappingNode {
return traverseMapWithIndices(context, matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
return traverseArrayIndices(context, matchingNode.CreateChildInMap(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
}
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil
@ -178,10 +178,9 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node, pr
node := unwrapDoc(candidate.Node)
if len(indices) == 0 {
log.Debug("splatting")
var index int64
for index = 0; index < int64(len(node.Content)); index = index + 1 {
newMatches.PushBack(candidate.CreateChild(index, node.Content[index]))
var index int
for index = 0; index < len(node.Content); index = index + 1 {
newMatches.PushBack(candidate.CreateChildInArray(index, node.Content[index]))
}
return newMatches, nil
@ -211,7 +210,7 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node, pr
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
}
newMatches.PushBack(candidate.CreateChild(index, node.Content[indexToUse]))
newMatches.PushBack(candidate.CreateChildInArray(int(index), node.Content[indexToUse]))
}
return newMatches, nil
}
@ -237,13 +236,13 @@ func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs
if prefs.IncludeMapKeys {
log.Debug("including key")
candidateNode := matchingNode.CreateChild(key, keyNode)
candidateNode := matchingNode.CreateChildInMap(keyNode, keyNode)
candidateNode.IsMapKey = true
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
if !prefs.DontIncludeMapValues {
log.Debug("including value")
candidateNode := matchingNode.CreateChild(key, valueNode)
candidateNode := matchingNode.CreateChildInMap(keyNode, valueNode)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
}
@ -282,13 +281,13 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
log.Debug("MATCHED")
if prefs.IncludeMapKeys {
log.Debug("including key")
candidateNode := candidate.CreateChild(key.Value, key)
candidateNode := candidate.CreateChildInMap(key, key)
candidateNode.IsMapKey = true
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
if !prefs.DontIncludeMapValues {
log.Debug("including value")
candidateNode := candidate.CreateChild(key.Value, value)
candidateNode := candidate.CreateChildInMap(key, value)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
}
@ -300,7 +299,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs traversePreferences, splat bool) error {
switch value.Kind {
case yaml.AliasNode:
candidateNode := originalCandidate.CreateChild(nil, value.Alias)
candidateNode := originalCandidate.CreateReplacement(value.Alias)
return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
case yaml.SequenceNode:
for _, childValue := range value.Content {

View File

@ -56,7 +56,7 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN
resultNode.Content = append(resultNode.Content, el.Value.(*yaml.Node))
}
results.PushBack(candidate.CreateChild(nil, resultNode))
results.PushBack(candidate.CreateReplacement(resultNode))
}
return context.ChildContext(results), nil

View File

@ -144,7 +144,7 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
valString = "false"
}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: valString, Tag: "!!bool"}
return owner.CreateChild(nil, node)
return owner.CreateReplacement(node)
}
func createTraversalTree(path []interface{}, traversePrefs traversePreferences, targetKey bool) *ExpressionNode {