Added map, map_values

This commit is contained in:
Mike Farah 2021-12-01 10:32:36 +11:00
parent 54b355bffb
commit 14f8f92b76
6 changed files with 163 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# Map
Maps values of an array. Use `map_values` to map values of an object.

40
pkg/yqlib/doc/map.md Normal file
View File

@ -0,0 +1,40 @@
# Map
Maps values of an array. Use `map_values` to map values of an object.
## Map array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq eval 'map(. + 1)' sample.yml
```
will output
```yaml
- 2
- 3
- 4
```
## Map object values
Given a sample.yml file of:
```yaml
a: 1
b: 2
c: 3
```
then
```bash
yq eval 'map_values(. + 1)' sample.yml
```
will output
```yaml
a: 2
b: 3
c: 4
```

View File

@ -312,6 +312,9 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`:\s*`), opToken(createMapOpType)) lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
lexer.Add([]byte(`length`), opToken(lengthOpType)) lexer.Add([]byte(`length`), opToken(lengthOpType))
lexer.Add([]byte(`map`), opToken(mapOpType))
lexer.Add([]byte(`map_values`), opToken(mapValuesOpType))
lexer.Add([]byte(`flatten\([0-9]+\)`), flattenWithDepth()) lexer.Add([]byte(`flatten\([0-9]+\)`), flattenWithDepth())
lexer.Add([]byte(`flatten`), opTokenWithPrefs(flattenOpType, nil, flattenPreferences{depth: -1})) lexer.Add([]byte(`flatten`), opTokenWithPrefs(flattenOpType, nil, flattenPreferences{depth: -1}))

View File

@ -64,6 +64,8 @@ var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence:
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator} var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator} var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
var mapValuesOpType = &operationType{Type: "MAP_VALUES", NumArgs: 1, Precedence: 50, Handler: mapValuesOperator}
var encodeOpType = &operationType{Type: "ENCODE", NumArgs: 0, Precedence: 50, Handler: encodeOperator} var encodeOpType = &operationType{Type: "ENCODE", NumArgs: 0, Precedence: 50, Handler: encodeOperator}
var decodeOpType = &operationType{Type: "DECODE", NumArgs: 0, Precedence: 50, Handler: decodeOperator} var decodeOpType = &operationType{Type: "DECODE", NumArgs: 0, Precedence: 50, Handler: decodeOperator}

64
pkg/yqlib/operator_map.go Normal file
View File

@ -0,0 +1,64 @@
package yqlib
import (
"container/list"
)
func mapValuesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
//run expression against entries
// splat toEntries and pipe it into Rhs
splatted, err := splat(d, context.SingleChildContext(candidate), traversePreferences{})
if err != nil {
return Context{}, err
}
assignUpdateExp := &ExpressionNode{
Operation: &Operation{OperationType: assignOpType, UpdateAssign: true},
Rhs: expressionNode.Rhs,
}
_, err = assignUpdateOperator(d, splatted, assignUpdateExp)
if err != nil {
return Context{}, err
}
}
return context, nil
}
func mapOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
//run expression against entries
// splat toEntries and pipe it into Rhs
splatted, err := splat(d, context.SingleChildContext(candidate), traversePreferences{})
if err != nil {
return Context{}, err
}
result, err := d.GetMatchingNodes(splatted, expressionNode.Rhs)
log.Debug("expressionNode.Rhs %v", expressionNode.Rhs.Operation.OperationType)
log.Debug("result %v", result)
if err != nil {
return Context{}, err
}
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
collected, err := collectTogether(d, result, selfExpression)
if err != nil {
return Context{}, err
}
collected.Node.Style = unwrapDoc(candidate.Node).Style
results.PushBack(collected)
}
return context.ChildContext(results), nil
}

View File

@ -0,0 +1,51 @@
package yqlib
import (
"testing"
)
var mapOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `[1,2,3]`,
document2: `[5,6,7]`,
expression: `map(. + 1)`,
expected: []string{
"D0, P[], (!!seq)::[2, 3, 4]\n",
"D0, P[], (!!seq)::[6, 7, 8]\n",
},
},
{
description: "Map array",
document: `[1,2,3]`,
expression: `map(. + 1)`,
expected: []string{
"D0, P[], (!!seq)::[2, 3, 4]\n",
},
},
{
skipDoc: true,
document: `{a: 1, b: 2, c: 3}`,
document2: `{x: 10, y: 20, z: 30}`,
expression: `map_values(. + 1)`,
expected: []string{
"D0, P[], (doc)::{a: 2, b: 3, c: 4}\n",
"D0, P[], (doc)::{x: 11, y: 21, z: 31}\n",
},
},
{
description: "Map object values",
document: `{a: 1, b: 2, c: 3}`,
expression: `map_values(. + 1)`,
expected: []string{
"D0, P[], (doc)::{a: 2, b: 3, c: 4}\n",
},
},
}
func TestMapOperatorScenarios(t *testing.T) {
for _, tt := range mapOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "map", mapOperatorScenarios)
}