1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Mike Farah
5911ab2929 Bump version 2021-02-25 16:52:49 +11:00
Mike Farah
2ed5b2ff59 Improved lexer performance! 2021-02-25 16:47:55 +11:00
Mike Farah
111c6e0be1 Increment version 2021-02-18 11:19:16 +11:00
Mike Farah
81136ad57e Arrays no longer deeply merge by defauly, like jq 2021-02-18 11:16:54 +11:00
Mike Farah
a6cd250987 nicer reduce example 2021-02-15 18:23:50 +11:00
Mike Farah
ee1f55630f nicer reduce example 2021-02-15 17:33:41 +11:00
Mike Farah
9072e8d3b3 Added context variable for reduce 2021-02-15 17:31:12 +11:00
Mike Farah
99b08fd612 Added reduce examples and doc 2021-02-15 16:38:53 +11:00
15 changed files with 277 additions and 32 deletions

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string GitDescribe string
// Version is main version number that is being run at the moment. // Version is main version number that is being run at the moment.
Version = "4.5.1" Version = "4.6.1"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release // then it means that it is a final release. Otherwise, this is a pre-release

View File

@@ -1,4 +1,4 @@
FROM mikefarah/yq:4.5.1 FROM mikefarah/yq:4.6.1
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

View File

@@ -35,6 +35,26 @@ will output
0 0
``` ```
## Get file indices of multiple documents
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
a: cat
```
then
```bash
yq eval-all 'fileIndex' sample.yml another.yml
```
will output
```yaml
0
---
1
```
## Get file index alias ## Get file index alias
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -1,19 +1,34 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS. Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them. ## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
Multiplication of strings and numbers are not yet supported. ### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
## Merging files - `+` to append arrays
- `?` to only merge existing fields
- `d` to deeply merge arrays
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory. Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash ```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
``` ```
## Multiply integers
Running
```bash
yq eval --null-input '3 * 4'
```
will output
```yaml
12
```
## Merge objects together, returning merged result only ## Merge objects together, returning merged result only
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -190,6 +205,32 @@ thing:
- 4 - 4
``` ```
## Merge, deeply merging arrays
Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.
Given a sample.yml file of:
```yaml
a:
- name: fred
age: 12
- name: bob
age: 32
b:
- name: fred
age: 34
```
then
```bash
yq eval '.a *d .b' sample.yml
```
will output
```yaml
- name: fred
age: 34
- name: bob
age: 32
```
## Merge to prefix an element ## Merge to prefix an element
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -0,0 +1,76 @@
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
## Sum numbers
Given a sample.yml file of:
```yaml
- 10
- 2
- 5
- 3
```
then
```bash
yq eval '.[] as $item ireduce (0; . + $item)' sample.yml
```
will output
```yaml
20
```
## Merge all yaml files together
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
b: dog
```
then
```bash
yq eval-all '. as $item ireduce ({}; . * $item )' sample.yml another.yml
```
will output
```yaml
a: cat
b: dog
```
## Convert an array to an object
Given a sample.yml file of:
```yaml
- name: Cathy
has: apples
- name: Bob
has: bananas
```
then
```bash
yq eval '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
```
will output
```yaml
Cathy: apples
Bob: bananas
```

View File

@@ -1,13 +1,18 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS. Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them. ## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
Multiplication of strings and numbers are not yet supported. ### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
## Merging files - `+` to append arrays
- `?` to only merge existing fields
- `d` to deeply merge arrays
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory. Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash ```bash

View File

@@ -0,0 +1,21 @@
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.

View File

@@ -5,9 +5,6 @@ import (
"strings" "strings"
) )
var myPathTokeniser = newExpressionTokeniser()
var myPathPostfixer = newExpressionPostFixer()
type ExpressionNode struct { type ExpressionNode struct {
Operation *Operation Operation *Operation
Lhs *ExpressionNode Lhs *ExpressionNode
@@ -19,19 +16,21 @@ type ExpressionParser interface {
} }
type expressionParserImpl struct { type expressionParserImpl struct {
pathTokeniser expressionTokeniser
pathPostFixer expressionPostFixer
} }
func NewExpressionParser() ExpressionParser { func NewExpressionParser() ExpressionParser {
return &expressionParserImpl{} return &expressionParserImpl{newExpressionTokeniser(), newExpressionPostFixer()}
} }
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) { func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
tokens, err := myPathTokeniser.Tokenise(expression) tokens, err := p.pathTokeniser.Tokenise(expression)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var Operations []*Operation var Operations []*Operation
Operations, err = myPathPostfixer.ConvertToPostfix(tokens) Operations, err = p.pathPostFixer.ConvertToPostfix(tokens)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -100,6 +100,9 @@ func multiplyWithPrefs() lex.Action {
if strings.Contains(options, "?") { if strings.Contains(options, "?") {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true} prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
} }
if strings.Contains(options, "d") {
prefs.DeepMergeArrays = true
}
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs} op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil return &token{TokenType: operationToken, Operation: op}, nil
} }
@@ -186,7 +189,7 @@ func getVariableOpToken() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes) value := string(m.Bytes)
value = value[1 : len(value)-1] value = value[1:]
getVarOperation := createValueOperation(value, value) getVarOperation := createValueOperation(value, value)
getVarOperation.OperationType = getVariableOpType getVarOperation.OperationType = getVariableOpType
@@ -319,13 +322,13 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\]`), literalToken(closeCollect, true)) lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false)) lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true)) lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs()) lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs())
lexer.Add([]byte(`\+`), opToken(addOpType)) lexer.Add([]byte(`\+`), opToken(addOpType))
lexer.Add([]byte(`\+=`), opToken(addAssignOpType)) lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken()) lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
lexer.Add([]byte(`as`), opToken(assignVariableOpType)) lexer.Add([]byte(`as`), opToken(assignVariableOpType))
err := lexer.Compile() err := lexer.CompileNFA()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -25,7 +25,7 @@ type operationType struct {
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator} var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator} var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 5, Handler: reduceOperator} var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}
var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator} var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator}

View File

@@ -21,6 +21,16 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n", "D0, P[], (!!int)::0\n",
}, },
}, },
{
description: "Get file indices of multiple documents",
document: `{a: cat}`,
document2: `{a: cat}`,
expression: `fileIndex`,
expected: []string{
"D0, P[], (!!int)::0\n",
"D0, P[], (!!int)::1\n",
},
},
{ {
description: "Get file index alias", description: "Get file index alias",
document: `{a: cat}`, document: `{a: cat}`,

View File

@@ -10,8 +10,9 @@ import (
) )
type multiplyPreferences struct { type multiplyPreferences struct {
AppendArrays bool AppendArrays bool
TraversePrefs traversePreferences DeepMergeArrays bool
TraversePrefs traversePreferences
} }
func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
@@ -37,11 +38,31 @@ func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, contex
return mergeObjects(d, context, newThing, rhs, preferences) return mergeObjects(d, context, newThing, rhs, preferences)
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" { } else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
return multiplyIntegers(lhs, rhs) return multiplyIntegers(lhs, rhs)
} else if (lhs.Node.Tag == "!!int" || lhs.Node.Tag == "!!float") && (rhs.Node.Tag == "!!int" || rhs.Node.Tag == "!!float") {
return multiplyFloats(lhs, rhs)
} }
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
} }
} }
func multiplyFloats(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = "!!float"
lhsNum, err := strconv.ParseFloat(lhs.Node.Value, 64)
if err != nil {
return nil, err
}
rhsNum, err := strconv.ParseFloat(rhs.Node.Value, 64)
if err != nil {
return nil, err
}
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
return target, nil
}
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{}) target := lhs.CreateChild(nil, &yaml.Node{})
target.Node.Kind = yaml.ScalarNode target.Node.Kind = yaml.ScalarNode
@@ -98,11 +119,12 @@ func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom
lhsPath := rhs.Path[pathIndexToStartFrom:] lhsPath := rhs.Path[pathIndexToStartFrom:]
assignmentOp := &Operation{OperationType: assignAttributesOpType} assignmentOp := &Operation{OperationType: assignAttributesOpType}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = addAssignOpType
} else if !preferences.DeepMergeArrays && rhs.Node.Kind == yaml.SequenceNode ||
(rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode) {
assignmentOp.OperationType = assignOpType assignmentOp.OperationType = assignOpType
assignmentOp.UpdateAssign = false assignmentOp.UpdateAssign = false
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = addAssignOpType
} }
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs} rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}

View File

@@ -5,6 +5,27 @@ import (
) )
var multiplyOperatorScenarios = []expressionScenario{ var multiplyOperatorScenarios = []expressionScenario{
{
description: "Multiply integers",
expression: `3 * 4`,
expected: []string{
"D0, P[], (!!int)::12\n",
},
},
{
skipDoc: true,
expression: `3 * 4.5`,
expected: []string{
"D0, P[], (!!float)::13.5\n",
},
},
{
skipDoc: true,
expression: `4.5 * 3`,
expected: []string{
"D0, P[], (!!float)::13.5\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `{a: {also: [1]}, b: {also: me}}`, document: `{a: {also: [1]}, b: {also: me}}`,
@@ -157,7 +178,7 @@ var multiplyOperatorScenarios = []expressionScenario{
{ {
skipDoc: true, skipDoc: true,
document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`, document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`,
expression: `.a *? .b`, expression: `.a *?d .b`,
expected: []string{ expected: []string{
"D0, P[a], (!!seq)::[{thing: two}]\n", "D0, P[a], (!!seq)::[{thing: two}]\n",
}, },
@@ -186,6 +207,15 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n", "D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n",
}, },
}, },
{
description: "Merge, deeply merging arrays",
subdescription: "Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.",
document: `{a: [{name: fred, age: 12}, {name: bob, age: 32}], b: [{name: fred, age: 34}]}`,
expression: `.a *d .b`,
expected: []string{
"D0, P[a], (!!seq)::[{name: fred, age: 34}, {name: bob, age: 32}]\n",
},
},
{ {
description: "Merge to prefix an element", description: "Merge to prefix an element",
document: `{a: cat, b: dog}`, document: `{a: cat, b: dog}`,

View File

@@ -6,17 +6,35 @@ import (
var reduceOperatorScenarios = []expressionScenario{ var reduceOperatorScenarios = []expressionScenario{
{ {
document: `[10,2, 5, 3]`, description: "Sum numbers",
expression: `.[] as $item ireduce (0; . + $item)`, document: `[10,2, 5, 3]`,
expression: `.[] as $item ireduce (0; . + $item)`,
expected: []string{ expected: []string{
"D0, P[], (!!int)::20\n", "D0, P[], (!!int)::20\n",
}, },
}, },
{
description: "Merge all yaml files together",
document: `a: cat`,
document2: `b: dog`,
expression: `. as $item ireduce ({}; . * $item )`,
expected: []string{
"D0, P[], (!!map)::a: cat\nb: dog\n",
},
},
{
description: "Convert an array to an object",
document: `[{name: Cathy, has: apples},{name: Bob, has: bananas}]`,
expression: `.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )`,
expected: []string{
"D0, P[], (!!map)::Cathy: apples\nBob: bananas\n",
},
},
} }
func TestReduceOperatorScenarios(t *testing.T) { func TestReduceOperatorScenarios(t *testing.T) {
for _, tt := range reduceOperatorScenarios { for _, tt := range reduceOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }
// documentScenarios(t, "Reduce", reduceOperatorScenarios) documentScenarios(t, "Reduce", reduceOperatorScenarios)
} }

View File

@@ -1,5 +1,5 @@
name: yq name: yq
version: '4.5.1' version: '4.6.1'
summary: A lightweight and portable command-line YAML processor summary: A lightweight and portable command-line YAML processor
description: | description: |
The aim of the project is to be the jq or sed of yaml files. The aim of the project is to be the jq or sed of yaml files.