mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Added pipe and length docs, fix pipe precedence
This commit is contained in:
@@ -8,7 +8,7 @@ This will do a similar thing to the plain form, however, the RHS expression is r
|
||||
## Create yaml file
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '(.a.b = "cat") | (.x = "frog")'
|
||||
yq eval --null-input '.a.b = "cat" | .x = "frog"'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -112,7 +112,7 @@ a:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml
|
||||
yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -130,7 +130,7 @@ Given a sample.yml file of:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | select(. == "*andy") |= "bogs"' sample.yml
|
||||
yq eval '(.[] | select(. == "*andy")) = "bogs"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
54
pkg/yqlib/doc/Length.md
Normal file
54
pkg/yqlib/doc/Length.md
Normal file
@@ -0,0 +1,54 @@
|
||||
Returns the lengths of the nodes. Length is defined according to the type of the node.
|
||||
|
||||
## String length
|
||||
returns length of string
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | length' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
3
|
||||
```
|
||||
|
||||
## Map length
|
||||
returns number of entries
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
c: dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'length' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
2
|
||||
```
|
||||
|
||||
## Array length
|
||||
returns number of elements
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 2
|
||||
- 4
|
||||
- 6
|
||||
- 8
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'length' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
4
|
||||
```
|
||||
|
||||
35
pkg/yqlib/doc/Pipe.md
Normal file
35
pkg/yqlib/doc/Pipe.md
Normal file
@@ -0,0 +1,35 @@
|
||||
Pipe the results of an expression into another. Like the bash operator.
|
||||
|
||||
## Simple Pipe
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
## Multiple updates
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cow
|
||||
b: sheep
|
||||
c: same
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = "cat" | .b = "dog"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat
|
||||
b: dog
|
||||
c: same
|
||||
```
|
||||
|
||||
1
pkg/yqlib/doc/headers/Length.md
Normal file
1
pkg/yqlib/doc/headers/Length.md
Normal file
@@ -0,0 +1 @@
|
||||
Returns the lengths of the nodes. Length is defined according to the type of the node.
|
||||
1
pkg/yqlib/doc/headers/Pipe.md
Normal file
1
pkg/yqlib/doc/headers/Pipe.md
Normal file
@@ -0,0 +1 @@
|
||||
Pipe the results of an expression into another. Like the bash operator.
|
||||
@@ -29,6 +29,8 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp
|
||||
|
||||
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
||||
|
||||
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: PipeOperator}
|
||||
|
||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
||||
var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
|
||||
|
||||
@@ -42,7 +44,8 @@ var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOp
|
||||
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
||||
|
||||
var ShortPipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
||||
|
||||
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
||||
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Create yaml file",
|
||||
expression: `(.a.b = "cat") | (.x = "frog")`,
|
||||
expression: `.a.b = "cat" | .x = "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a:\n b: cat\nx: frog\n",
|
||||
},
|
||||
@@ -80,7 +80,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update selected results",
|
||||
document: `{a: {b: apple, c: cactus}}`,
|
||||
expression: `.a.[] | select(. == "apple") |= "frog"`,
|
||||
expression: `(.a.[] | select(. == "apple")) = "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||
},
|
||||
@@ -88,7 +88,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update array values",
|
||||
document: `[candy, apple, sandy]`,
|
||||
expression: `.[] | select(. == "*andy") |= "bogs"`,
|
||||
expression: `(.[] | select(. == "*andy")) = "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[bogs, apple, bogs]\n",
|
||||
},
|
||||
|
||||
35
pkg/yqlib/operator_length.go
Normal file
35
pkg/yqlib/operator_length.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- lengthOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
targetNode := UnwrapDoc(candidate.Node)
|
||||
var length int
|
||||
switch targetNode.Kind {
|
||||
case yaml.ScalarNode:
|
||||
length = len(targetNode.Value)
|
||||
case yaml.MappingNode:
|
||||
length = len(targetNode.Content) / 2
|
||||
case yaml.SequenceNode:
|
||||
length = len(targetNode.Content)
|
||||
default:
|
||||
length = 0
|
||||
}
|
||||
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
42
pkg/yqlib/operator_length_test.go
Normal file
42
pkg/yqlib/operator_length_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var lengthOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "String length",
|
||||
subdescription: "returns length of string",
|
||||
document: `{a: cat}`,
|
||||
expression: `.a | length`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Map length",
|
||||
subdescription: "returns number of entries",
|
||||
document: `{a: cat, c: dog}`,
|
||||
expression: `length`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::2\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Array length",
|
||||
subdescription: "returns number of elements",
|
||||
document: `[2,4,6,8]`,
|
||||
expression: `length`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::4\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLengthOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range lengthOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Length", lengthOperatorScenarios)
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func createTraversalTree(path []interface{}) *PathTreeNode {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
||||
}
|
||||
return &PathTreeNode{
|
||||
Operation: &Operation{OperationType: Pipe},
|
||||
Operation: &Operation{OperationType: ShortPipe},
|
||||
Lhs: createTraversalTree(path[0:1]),
|
||||
Rhs: createTraversalTree(path[1:])}
|
||||
|
||||
|
||||
11
pkg/yqlib/operator_pipe.go
Normal file
11
pkg/yqlib/operator_pipe.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.GetMatchingNodes(lhs, pathNode.Rhs)
|
||||
}
|
||||
31
pkg/yqlib/operator_pipe_test.go
Normal file
31
pkg/yqlib/operator_pipe_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var pipeOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Simple Pipe",
|
||||
document: `{a: {b: cat}}`,
|
||||
expression: `.a | .b`,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Multiple updates",
|
||||
document: `{a: cow, b: sheep, c: same}`,
|
||||
expression: `.a = "cat" | .b = "dog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: cat, b: dog, c: same}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPipeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range pipeOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Pipe", pipeOperatorScenarios)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -20,14 +19,6 @@ func EmptyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
||||
return list.New(), nil
|
||||
}
|
||||
|
||||
func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.GetMatchingNodes(lhs, pathNode.Rhs)
|
||||
}
|
||||
|
||||
func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
|
||||
valString := "true"
|
||||
if !value {
|
||||
@@ -42,29 +33,3 @@ func nodeToMap(candidate *CandidateNode) *list.List {
|
||||
elMap.PushBack(candidate)
|
||||
return elMap
|
||||
}
|
||||
|
||||
func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- lengthOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
var length int
|
||||
switch candidate.Node.Kind {
|
||||
case yaml.ScalarNode:
|
||||
length = len(candidate.Node.Value)
|
||||
case yaml.MappingNode:
|
||||
length = len(candidate.Node.Content) / 2
|
||||
case yaml.SequenceNode:
|
||||
length = len(candidate.Node.Content)
|
||||
default:
|
||||
length = 0
|
||||
}
|
||||
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er
|
||||
// now we should have [] as the last element on the opStack, get rid of it
|
||||
opStack = opStack[0 : len(opStack)-1]
|
||||
//and append a collect to the opStack
|
||||
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Pipe}})
|
||||
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: ShortPipe}})
|
||||
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: collectOperator}})
|
||||
case CloseBracket:
|
||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket {
|
||||
|
||||
@@ -318,7 +318,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
|
||||
if index != len(tokens)-1 && token.CheckForPostTraverse &&
|
||||
tokens[index+1].TokenType == OperationToken &&
|
||||
tokens[index+1].Operation.OperationType == TraversePath {
|
||||
op := &Operation{OperationType: Pipe, Value: "PIPE"}
|
||||
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user