Added tag operator

This commit is contained in:
Mike Farah 2020-11-19 16:45:05 +11:00
parent 9b48cf80e0
commit 36084a60a9
16 changed files with 241 additions and 120 deletions

View File

@ -20,7 +20,8 @@ yq eval '{"wrap": .}' sample.yml
``` ```
will output will output
```yaml ```yaml
wrap: {name: Mike} wrap:
name: Mike
``` ```
### Using splat to create multiple objects ### Using splat to create multiple objects
@ -62,9 +63,7 @@ will output
```yaml ```yaml
Mike: cat Mike: cat
Mike: dog Mike: dog
---
Rosey: monkey Rosey: monkey
---
Rosey: sheep Rosey: sheep
``` ```

View File

@ -101,7 +101,7 @@ yq eval '. | headComment' sample.yml
``` ```
will output will output
```yaml ```yaml
welcome!
``` ```
### Get foot comment ### Get foot comment
@ -115,6 +115,6 @@ yq eval '. | footComment' sample.yml
``` ```
will output will output
```yaml ```yaml
have a great day
``` ```

View File

@ -12,7 +12,7 @@ yq eval 'del(.b)' sample.yml
``` ```
will output will output
```yaml ```yaml
{a: cat} a: cat
``` ```
### Delete entry in array ### Delete entry in array
@ -28,7 +28,8 @@ yq eval 'del(.[1])' sample.yml
``` ```
will output will output
```yaml ```yaml
[1, 3] - 1
- 3
``` ```
### Delete no matches ### Delete no matches
@ -43,6 +44,7 @@ yq eval 'del(.c)' sample.yml
``` ```
will output will output
```yaml ```yaml
{a: cat, b: dog} a: cat
b: dog
``` ```

View File

@ -49,7 +49,6 @@ will output
```yaml ```yaml
match: cat match: cat
doc: 0 doc: 0
---
match: frog match: frog
doc: 1 doc: 1
``` ```

View File

@ -13,7 +13,9 @@ yq eval 'explode(.f)' sample.yml
``` ```
will output will output
```yaml ```yaml
{f: {a: cat, b: cat}} f:
a: cat
b: cat
``` ```
### Explode with no aliases or anchors ### Explode with no aliases or anchors
@ -43,7 +45,9 @@ yq eval 'explode(.f)' sample.yml
``` ```
will output will output
```yaml ```yaml
{f: {a: cat, cat: b}} f:
a: cat
cat: b
``` ```
### Explode with merge anchors ### Explode with merge anchors

View File

@ -4,20 +4,6 @@ This operator recursively matches all children nodes given of a particular eleme
yq eval '.. style= "flow"' file.yaml yq eval '.. style= "flow"' file.yaml
``` ```
## Examples ## Examples
### Matches single scalar value
Given a sample.yml file of:
```yaml
cat
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
cat
```
### Map ### Map
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@ -146,39 +146,7 @@ c: 3.2
e: true e: true
``` ```
### Set style using a path ### Read style
Given a sample.yml file of:
```yaml
a: cat
b: double
```
then
```bash
yq eval '.a style=.b' sample.yml
```
will output
```yaml
a: "cat"
b: double
```
### Example 8
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval '.. style=""' sample.yml
```
will output
```yaml
a: cat
b: dog
```
### Example 9
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat a: cat
@ -193,20 +161,5 @@ will output
```
### Example 10
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '.. | style' sample.yml
```
will output
```yaml
``` ```

View File

@ -0,0 +1,45 @@
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).
## Examples
### Get tag
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
f: []
```
then
```bash
yq eval '.. | tag' sample.yml
```
will output
```yaml
!!map
!!str
!!int
!!float
!!bool
!!seq
```
### Convert numbers to strings
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '(.. | select(tag == "!!int")) tag = "!!str"' sample.yml
```
will output
```yaml
a: cat
b: "5"
c: 3.2
e: true
```

View File

@ -29,21 +29,3 @@ fieldA
fieldC fieldC
``` ```
### Combine selected paths
Given a sample.yml file of:
```yaml
a: fieldA
b: fieldB
c: fieldC
```
then
```bash
yq eval '(.a, .c) |= "potatoe"' sample.yml
```
will output
```yaml
a: potatoe
b: fieldB
c: potatoe
```

View File

@ -0,0 +1 @@
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).

View File

@ -36,8 +36,12 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
// TODO: implement this
var PlainAssign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator} var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
@ -49,6 +53,7 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator} var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator} var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator} var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}

View File

@ -10,7 +10,7 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="tagged"`, expression: `.. style="tagged"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", "D0, P[], (!!map)::!!map\na: !!str cat\nb: !!int 5\nc: !!float 3.2\ne: !!bool true\n",
}, },
}, },
{ {
@ -18,7 +18,7 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="double"`, expression: `.. style="double"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", "D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n",
}, },
}, },
{ {
@ -26,7 +26,7 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="single"`, expression: `.. style="single"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", "D0, P[], (!!map)::a: 'cat'\nb: '5'\nc: '3.2'\ne: 'true'\n",
}, },
}, },
{ {
@ -34,7 +34,15 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="literal"`, expression: `.. style="literal"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", `D0, P[], (!!map)::a: |-
cat
b: |-
5
c: |-
3.2
e: |-
true
`,
}, },
}, },
{ {
@ -42,7 +50,15 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="folded"`, expression: `.. style="folded"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", `D0, P[], (!!map)::a: >-
cat
b: >-
5
c: >-
3.2
e: >-
true
`,
}, },
}, },
{ {
@ -50,7 +66,7 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="flow"`, expression: `.. style="flow"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", "D0, P[], (!!map)::{a: cat, b: 5, c: 3.2, e: true}\n",
}, },
}, },
{ {
@ -58,7 +74,7 @@ var styleOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style=""`, expression: `.. style=""`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n", "D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
}, },
}, },
{ {

51
pkg/yqlib/operator_tag.go Normal file
View File

@ -0,0 +1,51 @@
package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
)
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
tag := ""
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey())
candidate.Node.Tag = tag
}
return matchingNodes, nil
}
func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetTagOperator")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}

View File

@ -0,0 +1,36 @@
package yqlib
import (
"testing"
)
var tagOperatorScenarios = []expressionScenario{
{
description: "Get tag",
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
expression: `.. | tag`,
expected: []string{
"D0, P[], (!!str)::'!!map'\n",
"D0, P[a], (!!str)::'!!str'\n",
"D0, P[b], (!!str)::'!!int'\n",
"D0, P[c], (!!str)::'!!float'\n",
"D0, P[e], (!!str)::'!!bool'\n",
"D0, P[f], (!!str)::'!!seq'\n",
},
},
{
description: "Convert numbers to strings",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
expected: []string{
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
},
},
}
func TestTagOperatorScenarios(t *testing.T) {
for _, tt := range tagOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Tag Operator", tagOperatorScenarios)
}

View File

@ -99,6 +99,27 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"), append(make([]interface{}, 0), "a", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"),
append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"), append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"),
}, },
{
`tag == "str"`,
append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"),
append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"),
},
{
`. tag= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"),
},
{
`lineComment == "str"`,
append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"),
append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"),
},
{
`. lineComment= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
},
// { // {
// `.a.b tag="!!str"`, // `.a.b tag="!!str"`,
// append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"), // append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"),

View File

@ -26,8 +26,9 @@ const (
type Token struct { type Token struct {
TokenType TokenType TokenType TokenType
Operation *Operation Operation *Operation
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
} }
func (t *Token) toString() string { func (t *Token) toString() string {
@ -83,14 +84,22 @@ func documentToken() lex.Action {
} }
func opToken(op *OperationType) lex.Action { func opToken(op *OperationType) lex.Action {
return opTokenWithPrefs(op, nil) return opTokenWithPrefs(op, nil, nil)
} }
func opTokenWithPrefs(op *OperationType, preferences interface{}) lex.Action { func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.Action {
return opTokenWithPrefs(opType, assignOpType, nil)
}
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) 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)
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences} op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
return &Token{TokenType: OperationToken, Operation: op}, nil var assign *Operation
if assignOpType != nil {
assign = &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, Preferences: preferences}
}
return &Token{TokenType: OperationToken, Operation: op, AssignOperation: assign}, nil
} }
} }
@ -191,23 +200,22 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex)) lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
lexer.Add([]byte(`style\s*=`), opToken(AssignStyle)) lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
lexer.Add([]byte(`style`), opToken(GetStyle))
lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true})) lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{LineComment: true}))
lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{HeadComment: true})) lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{FootComment: true})) lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}))
lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
lexer.Add([]byte(`\s*=\s*`), opToken(PlainAssign))
lexer.Add([]byte(`del`), opToken(DeleteChild)) lexer.Add([]byte(`del`), opToken(DeleteChild))
@ -286,7 +294,19 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
} }
var postProcessedTokens = make([]*Token, 0) var postProcessedTokens = make([]*Token, 0)
skipNextToken := false
for index, token := range tokens { for index, token := range tokens {
if skipNextToken {
skipNextToken = false
} else {
if index != len(tokens)-1 && token.AssignOperation != nil &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == PlainAssign {
token.Operation = token.AssignOperation
skipNextToken = true
}
postProcessedTokens = append(postProcessedTokens, token) postProcessedTokens = append(postProcessedTokens, token)
@ -297,6 +317,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
} }
} }
}
return postProcessedTokens, nil return postProcessedTokens, nil
} }