use addChild methods

This commit is contained in:
Mike Farah 2023-05-09 13:51:21 +10:00
parent 0ca8a13e36
commit d9357859ed
63 changed files with 1128 additions and 1068 deletions

View File

@ -38,7 +38,11 @@ func TestAllAtOnceEvaluateNodes(t *testing.T) {
for _, tt := range evaluateNodesScenario { for _, tt := range evaluateNodesScenario {
decoder := NewYamlDecoder(NewDefaultYamlPreferences()) decoder := NewYamlDecoder(NewDefaultYamlPreferences())
reader := bufio.NewReader(strings.NewReader(tt.document)) reader := bufio.NewReader(strings.NewReader(tt.document))
decoder.Init(reader) err := decoder.Init(reader)
if err != nil {
t.Error(err)
return
}
candidateNode, errorReading := decoder.Decode() candidateNode, errorReading := decoder.Decode()
if errorReading != nil { if errorReading != nil {

View File

@ -185,8 +185,6 @@ func (n *CandidateNode) GetNicePath() string {
path := n.GetPath() path := n.GetPath()
for i, element := range path { for i, element := range path {
elementStr := fmt.Sprintf("%v", element) elementStr := fmt.Sprintf("%v", element)
log.Debugf("element: %v", element)
log.Debugf("elementStr: %v", elementStr)
switch element.(type) { switch element.(type) {
case int: case int:
sb.WriteString("[" + elementStr + "]") sb.WriteString("[" + elementStr + "]")
@ -209,6 +207,10 @@ func (n *CandidateNode) AsList() *list.List {
return elMap return elMap
} }
func (n *CandidateNode) SetParent(parent *CandidateNode) {
n.Parent = parent
}
func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) { func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) {
key := rawKey.unwrapDocument().Copy() key := rawKey.unwrapDocument().Copy()
key.SetParent(n) key.SetParent(n)
@ -221,8 +223,18 @@ func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *Candid
n.Content = append(n.Content, key, value) n.Content = append(n.Content, key, value)
} }
func (n *CandidateNode) SetParent(parent *CandidateNode) { func (n *CandidateNode) AddChild(rawChild *CandidateNode) {
n.Parent = parent value := rawChild.unwrapDocument().Copy()
value.SetParent(n)
if value.Key != nil {
value.Key.SetParent(n)
} else {
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
}
n.Content = append(n.Content, value)
} }
func (n *CandidateNode) AddChildren(children []*CandidateNode) { func (n *CandidateNode) AddChildren(children []*CandidateNode) {
@ -230,29 +242,12 @@ func (n *CandidateNode) AddChildren(children []*CandidateNode) {
for i := 0; i < len(children); i += 2 { for i := 0; i < len(children); i += 2 {
key := children[i] key := children[i]
value := children[i+1] value := children[i+1]
n.AddKeyValueChild(key, value)
keyClone := key.Copy()
keyClone.SetParent(n)
valueClone := value.Copy()
valueClone.SetParent(n)
valueClone.Key = keyClone
n.Content = append(n.Content, keyClone, valueClone)
} }
} else { } else {
for _, rawChild := range children { for _, rawChild := range children {
value := rawChild.unwrapDocument().Copy() n.AddChild(rawChild)
value.SetParent(n)
if value.Key != nil {
value.Key.SetParent(n)
} else {
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
}
n.Content = append(n.Content, value)
} }
} }
} }
@ -269,7 +264,7 @@ func (n *CandidateNode) GetValueRep() (interface{}, error) {
// need to test this // need to test this
return strconv.ParseFloat(n.Value, 64) return strconv.ParseFloat(n.Value, 64)
case "!!bool": case "!!bool":
return isTruthyNode(n) return isTruthyNode(n), nil
case "!!null": case "!!null":
return nil, nil return nil, nil
} }
@ -307,9 +302,12 @@ func (n *CandidateNode) CreateReplacement(kind Kind, tag string, value string) *
func (n *CandidateNode) CopyAsReplacement(replacement *CandidateNode) *CandidateNode { func (n *CandidateNode) CopyAsReplacement(replacement *CandidateNode) *CandidateNode {
newCopy := replacement.Copy() newCopy := replacement.Copy()
newCopy.Parent = n.Parent newCopy.Parent = n.Parent
newCopy.Key = n.Key
newCopy.IsMapKey = n.IsMapKey if n.IsMapKey {
newCopy.Key = n
} else {
newCopy.Key = n.Key
}
return newCopy return newCopy
} }

View File

@ -168,7 +168,7 @@ func (o *CandidateNode) UnmarshalYAML(node *yaml.Node, anchorMap map[string]*Can
log.Debugf("node Style: %v", node.Style) log.Debugf("node Style: %v", node.Style)
log.Debugf("o Style: %v", o.Style) log.Debugf("o Style: %v", o.Style)
o.Content = make([]*CandidateNode, len(node.Content)) o.Content = make([]*CandidateNode, len(node.Content))
for i := 0; i < len(node.Content); i += 1 { for i := 0; i < len(node.Content); i++ {
keyNode := o.CreateChild() keyNode := o.CreateChild()
keyNode.IsMapKey = true keyNode.IsMapKey = true
keyNode.Tag = "!!int" keyNode.Tag = "!!int"
@ -230,7 +230,7 @@ func (o *CandidateNode) MarshalYAML() (*yaml.Node, error) {
log.Debugf("original style: %v", o.Style) log.Debugf("original style: %v", o.Style)
log.Debugf("original: %v, tag: %v, style: %v, kind: %v", NodeToString(o), target.Tag, target.Style, target.Kind == yaml.SequenceNode) log.Debugf("original: %v, tag: %v, style: %v, kind: %v", NodeToString(o), target.Tag, target.Style, target.Kind == yaml.SequenceNode)
target.Content = make([]*yaml.Node, len(o.Content)) target.Content = make([]*yaml.Node, len(o.Content))
for i := 0; i < len(o.Content); i += 1 { for i := 0; i < len(o.Content); i++ {
child, err := o.Content[i].MarshalYAML() child, err := o.Content[i].MarshalYAML()

View File

@ -65,89 +65,105 @@ because excel is cool
` `
var csvScenarios = []formatScenario{ var csvScenarios = []formatScenario{
// { {
// description: "Encode CSV simple", description: "Encode CSV simple",
// input: csvTestSimpleYaml, input: csvTestSimpleYaml,
// expected: expectedSimpleCsv, expected: expectedSimpleCsv,
// scenarioType: "encode-csv", scenarioType: "encode-csv",
// }, },
// { {
// description: "Encode TSV simple", description: "Encode TSV simple",
// input: csvTestSimpleYaml, input: csvTestSimpleYaml,
// expected: tsvTestExpectedSimpleCsv, expected: tsvTestExpectedSimpleCsv,
// scenarioType: "encode-tsv", scenarioType: "encode-tsv",
// }, },
// { {
// description: "Encode Empty", description: "Encode Empty",
// skipDoc: true, skipDoc: true,
// input: `[]`, input: `[]`,
// expected: "", expected: "",
// scenarioType: "encode-csv", scenarioType: "encode-csv",
// }, },
// { {
// description: "Comma in value", description: "Comma in value",
// skipDoc: true, skipDoc: true,
// input: `["comma, in, value", things]`, input: `["comma, in, value", things]`,
// expected: "\"comma, in, value\",things\n", expected: "\"comma, in, value\",things\n",
// scenarioType: "encode-csv", scenarioType: "encode-csv",
// }, },
// { {
// description: "Encode array of objects to csv", description: "Encode array of objects to csv",
// input: expectedYamlFromCSV, input: expectedYamlFromCSV,
// expected: csvSimple, expected: csvSimple,
// scenarioType: "encode-csv", scenarioType: "encode-csv",
// }, },
// { {
// description: "Encode array of objects to custom csv format", description: "Encode array of objects to custom csv format",
// subdescription: "Add the header row manually, then the we convert each object into an array of values - resulting in an array of arrays. Pick the columns and call the header whatever you like.", subdescription: "Add the header row manually, then the we convert each object into an array of values - resulting in an array of arrays. Pick the columns and call the header whatever you like.",
// input: expectedYamlFromCSV, input: expectedYamlFromCSV,
// expected: csvSimpleShort, expected: csvSimpleShort,
// expression: `[["Name", "Number of Cats"]] + [.[] | [.name, .numberOfCats ]]`, expression: `[["Name", "Number of Cats"]] + [.[] | [.name, .numberOfCats ]]`,
// scenarioType: "encode-csv", scenarioType: "encode-csv",
// }, },
// { {
// description: "Encode array of objects to csv - missing fields behaviour", description: "Encode array of objects to csv - missing fields behaviour",
// subdescription: "First entry is used to determine the headers, and it is missing 'likesApples', so it is not included in the csv. Second entry does not have 'numberOfCats' so that is blank", subdescription: "First entry is used to determine the headers, and it is missing 'likesApples', so it is not included in the csv. Second entry does not have 'numberOfCats' so that is blank",
// input: expectedYamlFromCSVMissingData, input: expectedYamlFromCSVMissingData,
// expected: csvSimpleMissingData, expected: csvSimpleMissingData,
// scenarioType: "encode-csv", scenarioType: "encode-csv",
// }, },
// { {
// description: "decode csv missing", description: "decode csv missing",
// skipDoc: true, skipDoc: true,
// input: csvMissing, input: csvMissing,
// expected: csvMissing, expected: csvMissing,
// scenarioType: "roundtrip-csv", scenarioType: "roundtrip-csv",
// }, },
// { {
// description: "Parse CSV into an array of objects", description: "decode csv key",
// subdescription: "First row is assumed to be the header row.", skipDoc: true,
// input: csvSimple, input: csvSimple,
// expected: expectedYamlFromCSV, expression: ".[0].name | key",
// scenarioType: "decode-csv-object", expected: "name\n",
// }, scenarioType: "decode-csv-object",
// { },
// description: "Scalar roundtrip", {
// skipDoc: true, description: "decode csv parent",
// input: "mike\ncat", skipDoc: true,
// expression: ".[0].mike", input: csvSimple,
// expected: "cat\n", expression: ".[0].name | parent | .height",
// scenarioType: "roundtrip-csv", expected: "168.8\n",
// }, scenarioType: "decode-csv-object",
// { },
// description: "Parse TSV into an array of objects", {
// subdescription: "First row is assumed to be the header row.", description: "Parse CSV into an array of objects",
// input: tsvSimple, subdescription: "First row is assumed to be the header row.",
// expected: expectedYamlFromCSV, input: csvSimple,
// scenarioType: "decode-tsv-object", expected: expectedYamlFromCSV,
// }, scenarioType: "decode-csv-object",
// { },
// description: "Round trip", {
// input: csvSimple, description: "Scalar roundtrip",
// expected: expectedUpdatedSimpleCsv, skipDoc: true,
// expression: `(.[] | select(.name == "Gary") | .numberOfCats) = 3`, input: "mike\ncat",
// scenarioType: "roundtrip-csv", expression: ".[0].mike",
// }, expected: "cat\n",
scenarioType: "roundtrip-csv",
},
{
description: "Parse TSV into an array of objects",
subdescription: "First row is assumed to be the header row.",
input: tsvSimple,
expected: expectedYamlFromCSV,
scenarioType: "decode-tsv-object",
},
{
description: "Round trip",
input: csvSimple,
expected: expectedUpdatedSimpleCsv,
expression: `(.[] | select(.name == "Gary") | .numberOfCats) = 3`,
scenarioType: "roundtrip-csv",
},
} }
func testCSVScenario(t *testing.T, s formatScenario) { func testCSVScenario(t *testing.T, s formatScenario) {
@ -286,5 +302,5 @@ func TestCSVScenarios(t *testing.T) {
for i, s := range csvScenarios { for i, s := range csvScenarios {
genericScenarios[i] = s genericScenarios[i] = s
} }
// documentScenarios(t, "usage", "csv-tsv", genericScenarios, documentCSVScenario) documentScenarios(t, "usage", "csv-tsv", genericScenarios, documentCSVScenario)
} }

View File

@ -39,10 +39,7 @@ func (dec *csvObjectDecoder) createObject(headerRow []string, contentRow []strin
objectNode := &CandidateNode{Kind: MappingNode, Tag: "!!map"} objectNode := &CandidateNode{Kind: MappingNode, Tag: "!!map"}
for i, header := range headerRow { for i, header := range headerRow {
objectNode.Content = append( objectNode.AddKeyValueChild(createScalarNode(header, header), dec.convertToNode(contentRow[i]))
objectNode.Content,
createScalarNode(header, header),
dec.convertToNode(contentRow[i]))
} }
return objectNode return objectNode
} }
@ -63,7 +60,7 @@ func (dec *csvObjectDecoder) Decode() (*CandidateNode, error) {
for err == nil && len(contentRow) > 0 { for err == nil && len(contentRow) > 0 {
log.Debugf("Adding contentRow: %v", contentRow) log.Debugf("Adding contentRow: %v", contentRow)
rootArray.Content = append(rootArray.Content, dec.createObject(headerRow, contentRow)) rootArray.AddChild(dec.createObject(headerRow, contentRow))
contentRow, err = dec.reader.Read() contentRow, err = dec.reader.Read()
log.Debugf("Read next contentRow: %v, %v", contentRow, err) log.Debugf("Read next contentRow: %v, %v", contentRow, err)
} }

View File

@ -42,7 +42,7 @@ func (dec *xmlDecoder) createSequence(nodes []*xmlNode) (*CandidateNode, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
yamlNode.Content = append(yamlNode.Content, yamlChild) yamlNode.AddChild(yamlChild)
} }
return yamlNode, nil return yamlNode, nil
@ -74,7 +74,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*CandidateNode, error) {
labelNode.HeadComment = dec.processComment(n.HeadComment) labelNode.HeadComment = dec.processComment(n.HeadComment)
labelNode.LineComment = dec.processComment(n.LineComment) labelNode.LineComment = dec.processComment(n.LineComment)
labelNode.FootComment = dec.processComment(n.FootComment) labelNode.FootComment = dec.processComment(n.FootComment)
yamlNode.Content = append(yamlNode.Content, labelNode, dec.createValueNodeFromData(n.Data)) yamlNode.AddKeyValueChild(labelNode, dec.createValueNodeFromData(n.Data))
} }
for i, keyValuePair := range n.Children { for i, keyValuePair := range n.Children {
@ -119,7 +119,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*CandidateNode, error) {
return nil, err return nil, err
} }
} }
yamlNode.Content = append(yamlNode.Content, labelNode, valueNode) yamlNode.AddKeyValueChild(labelNode, valueNode)
} }
return yamlNode, nil return yamlNode, nil

View File

@ -10,6 +10,56 @@ This will set the LHS nodes' comments equal to the expression on the RHS. The RH
### relative form: `|=` ### relative form: `|=`
This is similar to the plain form, but it evaluates the RHS with _each matching LHS node as context_. This is useful if you want to set the comments as a relative expression of the node, for instance its value or path. This is similar to the plain form, but it evaluates the RHS with _each matching LHS node as context_. This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
## Set line comment
Set the comment on the key node for more reliability (see below).
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '.a line_comment="single"' sample.yml
```
will output
```yaml
a: cat # single
```
## Set line comment of a maps/arrays
For maps and arrays, you need to set the line comment on the _key_ node. This will also work for scalars.
Given a sample.yml file of:
```yaml
a:
b: things
```
then
```bash
yq '(.a | key) line_comment="single"' sample.yml
```
will output
```yaml
a: # single
b: things
```
## Use update assign to perform relative updates
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq '.. line_comment |= .' sample.yml
```
will output
```yaml
a: cat # cat
b: dog # dog
```
## Where is the comment - map key example ## Where is the comment - map key example
The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value). The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).
From this, you can see the 'hello-world-comment' is actually on the 'hello' key From this, you can see the 'hello-world-comment' is actually on the 'hello' key
@ -52,3 +102,239 @@ will output
fc: "" fc: ""
``` ```
## Retrieve comment - map key example
From the previous example, we know that the comment is on the 'hello' _key_ as a lineComment
Given a sample.yml file of:
```yaml
hello: # hello-world-comment
message: world
```
then
```bash
yq '.hello | key | line_comment' sample.yml
```
will output
```yaml
hello-world-comment
```
## Where is the comment - array example
The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).
From this, you can see the 'under-name-comment' is actually on the first child
Given a sample.yml file of:
```yaml
name:
# under-name-comment
- first-array-child
```
then
```bash
yq '[... | {"p": path | join("."), "isKey": is_key, "hc": headComment, "lc": lineComment, "fc": footComment}]' sample.yml
```
will output
```yaml
- p: ""
isKey: false
hc: ""
lc: ""
fc: ""
- p: name
isKey: true
hc: ""
lc: ""
fc: ""
- p: name
isKey: false
hc: ""
lc: ""
fc: ""
- p: name.0
isKey: false
hc: under-name-comment
lc: ""
fc: ""
```
## Retrieve comment - array example
From the previous example, we know that the comment is on the first child as a headComment
Given a sample.yml file of:
```yaml
name:
# under-name-comment
- first-array-child
```
then
```bash
yq '.name[0] | headComment' sample.yml
```
will output
```yaml
under-name-comment
```
## Set head comment
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '. head_comment="single"' sample.yml
```
will output
```yaml
# single
a: cat
```
## Set head comment of a map entry
Given a sample.yml file of:
```yaml
f: foo
a:
b: cat
```
then
```bash
yq '(.a | key) head_comment="single"' sample.yml
```
will output
```yaml
f: foo
# single
a:
b: cat
```
## Set foot comment, using an expression
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq '. foot_comment=.a' sample.yml
```
will output
```yaml
a: cat
# cat
```
## Remove comment
Given a sample.yml file of:
```yaml
a: cat # comment
b: dog # leave this
```
then
```bash
yq '.a line_comment=""' sample.yml
```
will output
```yaml
a: cat
b: dog # leave this
```
## Remove (strip) all comments
Note the use of `...` to ensure key nodes are included.
Given a sample.yml file of:
```yaml
# hi
a: cat # comment
# great
b: # key comment
```
then
```bash
yq '... comments=""' sample.yml
```
will output
```yaml
a: cat
b:
```
## Get line comment
Given a sample.yml file of:
```yaml
# welcome!
a: cat # meow
# have a great day
```
then
```bash
yq '.a | line_comment' sample.yml
```
will output
```yaml
meow
```
## Get head comment
Given a sample.yml file of:
```yaml
# welcome!
a: cat # meow
# have a great day
```
then
```bash
yq '. | head_comment' sample.yml
```
will output
```yaml
welcome!
```
## Head comment with document split
Given a sample.yml file of:
```yaml
# welcome!
---
# bob
a: cat # meow
# have a great day
```
then
```bash
yq 'head_comment' sample.yml
```
will output
```yaml
welcome!
bob
```
## Get foot comment
Given a sample.yml file of:
```yaml
# welcome!
a: cat # meow
# have a great day
# no really
```
then
```bash
yq '. | foot_comment' sample.yml
```
will output
```yaml
have a great day
no really
```

View File

@ -56,7 +56,6 @@ will output
```yaml ```yaml
Mike: cat Mike: cat
Mike: dog Mike: dog
---
Rosey: monkey Rosey: monkey
Rosey: sheep Rosey: sheep
``` ```

View File

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

View File

@ -2,10 +2,119 @@
Use the `keys` operator to return map keys or array indices. Use the `keys` operator to return map keys or array indices.
## Map keys
Given a sample.yml file of:
```yaml
{dog: woof, cat: meow}
```
then
```bash
yq 'keys' sample.yml
```
will output
```yaml
- dog
- cat
```
## Array keys
Given a sample.yml file of:
```yaml
[apple, banana]
```
then
```bash
yq 'keys' sample.yml
```
will output
```yaml
- 0
- 1
```
## Retrieve array key
Given a sample.yml file of:
```yaml
[1, 2, 3]
```
then
```bash
yq '.[1] | key' sample.yml
```
will output
```yaml
1
```
## Retrieve map key
Given a sample.yml file of:
```yaml
a: thing
```
then
```bash
yq '.a | key' sample.yml
```
will output
```yaml
a
```
## No key
Given a sample.yml file of:
```yaml
{}
```
then
```bash
yq 'key' sample.yml
```
will output
```yaml
```
## Update map key
Given a sample.yml file of:
```yaml
a:
x: 3
y: 4
```
then
```bash
yq '(.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 '.a.x | key | headComment' sample.yml
```
will output
```yaml
comment on key
```
## Check node is a key ## Check node is a key
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: frog a:
b:
- cat
c: frog
``` ```
then then
```bash ```bash
@ -18,9 +127,20 @@ will output
tag: '!!map' tag: '!!map'
- p: a - p: a
isKey: true isKey: true
tag: null tag: '!!str'
'!!str': null
- p: a - p: a
isKey: false
tag: '!!map'
- p: a.c
isKey: true
tag: '!!str'
- p: a.b
isKey: false
tag: '!!seq'
- p: a.b.0
isKey: false
tag: '!!str'
- p: a.c
isKey: false isKey: false
tag: '!!str' tag: '!!str'
``` ```

View File

@ -7,7 +7,7 @@ returns length of string
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat}
``` ```
then then
```bash ```bash
@ -21,7 +21,7 @@ will output
## null length ## null length
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: null {a: null}
``` ```
then then
```bash ```bash
@ -37,8 +37,7 @@ returns number of entries
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat, c: dog}
c: dog
``` ```
then then
```bash ```bash
@ -54,10 +53,7 @@ returns number of elements
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 2 [2, 4, 6, 8]
- 4
- 6
- 8
``` ```
then then
```bash ```bash

View File

@ -48,7 +48,7 @@ bXkgc2VjcmV0IGNoaWxsaSByZWNpcGUgaXMuLi4u
## Simple example ## Simple example
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
myFile: ../../examples/thing.yml {myFile: ../../examples/thing.yml}
``` ```
then then
```bash ```bash
@ -65,8 +65,7 @@ Note that you can modify the filename in the load operator if needed.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
something: {something: {file: thing.yml}}
file: thing.yml
``` ```
then then
```bash ```bash
@ -74,9 +73,7 @@ yq '.something |= load("../../examples/" + .file)' sample.yml
``` ```
will output will output
```yaml ```yaml
something: {something: {a: apple is included, b: cool.}}
a: apple is included
b: cool.
``` ```
## Replace _all_ nodes with referenced file ## Replace _all_ nodes with referenced file
@ -84,11 +81,7 @@ Recursively match all the nodes (`..`) and then filter the ones that have a 'fil
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
something: {something: {file: thing.yml}, over: {here: [{file: thing.yml}]}}
file: thing.yml
over:
here:
- file: thing.yml
``` ```
then then
```bash ```bash
@ -96,13 +89,7 @@ yq '(.. | select(has("file"))) |= load("../../examples/" + .file)' sample.yml
``` ```
will output will output
```yaml ```yaml
something: {something: {a: apple is included, b: cool.}, over: {here: [{a: apple is included, b: cool.}]}}
a: apple is included
b: cool.
over:
here:
- a: apple is included
b: cool.
``` ```
## Replace node with referenced file as string ## Replace node with referenced file as string
@ -110,8 +97,7 @@ This will work for any text based file
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
something: {something: {file: thing.yml}}
file: thing.yml
``` ```
then then
```bash ```bash
@ -119,9 +105,7 @@ yq '.something |= load_str("../../examples/" + .file)' sample.yml
``` ```
will output will output
```yaml ```yaml
something: |- {something: "a: apple is included\nb: cool."}
a: apple is included
b: cool.
``` ```
## Load from XML ## Load from XML
@ -172,9 +156,7 @@ yq '. *= load_props("../../examples/small.properties")' sample.yml
``` ```
will output will output
```yaml ```yaml
this: is
is: a properties file
cool: ay
``` ```
## Load from base64 encoded file ## Load from base64 encoded file

View File

@ -5,9 +5,7 @@ Maps values of an array. Use `map_values` to map values of an object.
## Map array ## Map array
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 1 [1, 2, 3]
- 2
- 3
``` ```
then then
```bash ```bash
@ -15,17 +13,13 @@ yq 'map(. + 1)' sample.yml
``` ```
will output will output
```yaml ```yaml
- 2 [2, 3, 4]
- 3
- 4
``` ```
## Map object values ## Map object values
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 1 {a: 1, b: 2, c: 3}
b: 2
c: 3
``` ```
then then
```bash ```bash
@ -33,8 +27,6 @@ yq 'map_values(. + 1)' sample.yml
``` ```
will output will output
```yaml ```yaml
a: 2 {a: 2, b: 3, c: 4}
b: 3
c: 4
``` ```

View File

@ -7,8 +7,7 @@ If the lhs and rhs are ints then the expression will be calculated with ints.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 13 {a: 13, b: 2}
b: 2
``` ```
then then
```bash ```bash
@ -16,8 +15,7 @@ yq '.a = .a % .b' sample.yml
``` ```
will output will output
```yaml ```yaml
a: 1 {a: 1, b: 2}
b: 2
``` ```
## Number modulo - float ## Number modulo - float
@ -25,8 +23,7 @@ If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 12 {a: 12, b: 2.5}
b: 2.5
``` ```
then then
```bash ```bash
@ -34,8 +31,7 @@ yq '.a = .a % .b' sample.yml
``` ```
will output will output
```yaml ```yaml
a: !!float 2 {a: !!float 2, b: 2.5}
b: 2.5
``` ```
## Number modulo - int by zero ## Number modulo - int by zero
@ -43,8 +39,7 @@ If the lhs is an int and rhs is a 0 the result is an error.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 1 {a: 1, b: 0}
b: 0
``` ```
then then
```bash ```bash
@ -60,8 +55,7 @@ If the lhs is a float and rhs is a 0 the result is NaN.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 1.1 {a: 1.1, b: 0}
b: 0
``` ```
then then
```bash ```bash
@ -69,7 +63,6 @@ yq '.a = .a % .b' sample.yml
``` ```
will output will output
```yaml ```yaml
a: !!float NaN {a: !!float NaN, b: 0}
b: 0
``` ```

View File

@ -10,8 +10,7 @@ Use `setpath` to set a value to the path array returned by `path`, and similarly
## Map path ## Map path
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: cat}}
b: cat
``` ```
then then
```bash ```bash
@ -26,8 +25,7 @@ will output
## Get map key ## Get map key
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: cat}}
b: cat
``` ```
then then
```bash ```bash
@ -41,9 +39,7 @@ b
## Array path ## Array path
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: [cat, dog]}
- cat
- dog
``` ```
then then
```bash ```bash
@ -58,9 +54,7 @@ will output
## Get array index ## Get array index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: [cat, dog]}
- cat
- dog
``` ```
then then
```bash ```bash
@ -74,10 +68,7 @@ will output
## Print path and value ## Print path and value
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: [cat, dog, frog]}
- cat
- dog
- frog
``` ```
then then
```bash ```bash
@ -98,8 +89,7 @@ will output
## Set path ## Set path
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: cat}}
b: cat
``` ```
then then
```bash ```bash
@ -107,8 +97,7 @@ yq 'setpath(["a", "b"]; "things")' sample.yml
``` ```
will output will output
```yaml ```yaml
a: {a: {b: things}}
b: things
``` ```
## Set on empty document ## Set on empty document
@ -183,10 +172,7 @@ Notice delpaths takes an _array_ of paths.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: {a: {b: cat, c: dog, d: frog}}
b: cat
c: dog
d: frog
``` ```
then then
```bash ```bash
@ -194,8 +180,7 @@ yq 'delpaths([["a", "c"], ["a", "d"]])' sample.yml
``` ```
will output will output
```yaml ```yaml
a: {a: {b: cat}}
b: cat
``` ```
## Delete array path ## Delete array path

View File

@ -31,9 +31,7 @@ Note that the order of the indices matches the pick order and non existent indic
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, leopard, lion]
- leopard
- lion
``` ```
then then
```bash ```bash
@ -41,7 +39,6 @@ yq 'pick([2, 0, 734, -5])' sample.yml
``` ```
will output will output
```yaml ```yaml
- lion [lion, cat]
- cat
``` ```

View File

@ -5,9 +5,7 @@ Reverses the order of the items in an array
## Reverse ## Reverse
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 1 [1, 2, 3]
- 2
- 3
``` ```
then then
```bash ```bash
@ -15,9 +13,7 @@ yq 'reverse' sample.yml
``` ```
will output will output
```yaml ```yaml
- 3 [3, 2, 1]
- 2
- 1
``` ```
## Sort descending by string field ## Sort descending by string field
@ -25,9 +21,7 @@ Use sort with reverse to sort in descending order.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: banana [{a: banana}, {a: cat}, {a: apple}]
- a: cat
- a: apple
``` ```
then then
```bash ```bash
@ -35,8 +29,6 @@ yq 'sort_by(.a) | reverse' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: cat [{a: cat}, {a: banana}, {a: apple}]
- a: banana
- a: apple
``` ```

View File

@ -7,10 +7,7 @@ You may leave out the first or second number, which will will refer to the start
## Slicing arrays ## Slicing arrays
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, dog, frog, cow]
- dog
- frog
- cow
``` ```
then then
```bash ```bash
@ -27,10 +24,7 @@ Starts from the start of the array
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, dog, frog, cow]
- dog
- frog
- cow
``` ```
then then
```bash ```bash
@ -47,10 +41,7 @@ Finishes at the end of the array
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, dog, frog, cow]
- dog
- frog
- cow
``` ```
then then
```bash ```bash
@ -65,10 +56,7 @@ will output
## Slicing arrays - use negative numbers to count backwards from the end ## Slicing arrays - use negative numbers to count backwards from the end
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, dog, frog, cow]
- dog
- frog
- cow
``` ```
then then
```bash ```bash
@ -85,10 +73,7 @@ using an expression to find the index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, dog, frog, cow]
- dog
- frog
- cow
``` ```
then then
```bash ```bash

View File

@ -18,9 +18,7 @@ See [here](https://mikefarah.gitbook.io/yq/operators/entries#custom-sort-map-key
## Sort keys of map ## Sort keys of map
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
c: frog {c: frog, a: blah, b: bing}
a: blah
b: bing
``` ```
then then
```bash ```bash
@ -28,9 +26,7 @@ yq 'sort_keys(.)' sample.yml
``` ```
will output will output
```yaml ```yaml
a: blah {a: blah, b: bing, c: frog}
b: bing
c: frog
``` ```
## Sort keys recursively ## Sort keys recursively
@ -38,19 +34,7 @@ Note the array elements are left unsorted, but maps inside arrays are sorted
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
bParent: {bParent: {c: dog, array: [3, 1, 2]}, aParent: {z: donkey, x: [{c: yum, b: delish}, {b: ew, a: apple}]}}
c: dog
array:
- 3
- 1
- 2
aParent:
z: donkey
x:
- c: yum
b: delish
- b: ew
a: apple
``` ```
then then
```bash ```bash
@ -58,18 +42,6 @@ yq 'sort_keys(..)' sample.yml
``` ```
will output will output
```yaml ```yaml
aParent: {aParent: {x: [{b: delish, c: yum}, {a: apple, b: ew}], z: donkey}, bParent: {array: [3, 1, 2], c: dog}}
x:
- b: delish
c: yum
- a: apple
b: ew
z: donkey
bParent:
array:
- 3
- 1
- 2
c: dog
``` ```

View File

@ -10,9 +10,7 @@ Note that at this stage, `yq` only sorts scalar fields.
## Sort by string field ## Sort by string field
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: banana [{a: banana}, {a: cat}, {a: apple}]
- a: cat
- a: apple
``` ```
then then
```bash ```bash
@ -20,19 +18,13 @@ yq 'sort_by(.a)' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: apple [{a: apple}, {a: banana}, {a: cat}]
- a: banana
- a: cat
``` ```
## Sort by multiple fields ## Sort by multiple fields
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: dog [{a: dog}, {a: cat, b: banana}, {a: cat, b: apple}]
- a: cat
b: banana
- a: cat
b: apple
``` ```
then then
```bash ```bash
@ -40,11 +32,7 @@ yq 'sort_by(.a, .b)' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: cat [{a: cat, b: apple}, {a: cat, b: banana}, {a: dog}]
b: apple
- a: cat
b: banana
- a: dog
``` ```
## Sort descending by string field ## Sort descending by string field
@ -52,9 +40,7 @@ Use sort with reverse to sort in descending order.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: banana [{a: banana}, {a: cat}, {a: apple}]
- a: cat
- a: apple
``` ```
then then
```bash ```bash
@ -62,9 +48,7 @@ yq 'sort_by(.a) | reverse' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: cat [{a: cat}, {a: banana}, {a: apple}]
- a: banana
- a: apple
``` ```
## Sort array in place ## Sort array in place
@ -114,14 +98,7 @@ Note the order of the elements in unchanged when equal in sorting.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: banana [{a: banana, b: 1}, {a: banana, b: 2}, {a: banana, b: 3}, {a: banana, b: 4}]
b: 1
- a: banana
b: 2
- a: banana
b: 3
- a: banana
b: 4
``` ```
then then
```bash ```bash
@ -129,22 +106,13 @@ yq 'sort_by(.a)' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: banana [{a: banana, b: 1}, {a: banana, b: 2}, {a: banana, b: 3}, {a: banana, b: 4}]
b: 1
- a: banana
b: 2
- a: banana
b: 3
- a: banana
b: 4
``` ```
## Sort by numeric field ## Sort by numeric field
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: 10 [{a: 10}, {a: 100}, {a: 1}]
- a: 100
- a: 1
``` ```
then then
```bash ```bash
@ -152,17 +120,13 @@ yq 'sort_by(.a)' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: 1 [{a: 1}, {a: 10}, {a: 100}]
- a: 10
- a: 100
``` ```
## Sort by custom date field ## Sort by custom date field
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: 12-Jun-2011 [{a: 12-Jun-2011}, {a: 23-Dec-2010}, {a: 10-Aug-2011}]
- a: 23-Dec-2010
- a: 10-Aug-2011
``` ```
then then
```bash ```bash
@ -170,21 +134,13 @@ yq 'with_dtf("02-Jan-2006"; sort_by(.a))' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: 23-Dec-2010 [{a: 23-Dec-2010}, {a: 12-Jun-2011}, {a: 10-Aug-2011}]
- a: 12-Jun-2011
- a: 10-Aug-2011
``` ```
## Sort, nulls come first ## Sort, nulls come first
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 8 [8, 3, null, 6, true, false, cat]
- 3
- null
- 6
- true
- false
- cat
``` ```
then then
```bash ```bash
@ -192,12 +148,6 @@ yq 'sort' sample.yml
``` ```
will output will output
```yaml ```yaml
- null [null, false, true, 3, 6, 8, cat]
- false
- true
- 3
- 6
- 8
- cat
``` ```

View File

@ -91,11 +91,7 @@ will output
## Join strings ## Join strings
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, meow, 1, null, true]
- meow
- 1
- null
- true
``` ```
then then
```bash ```bash
@ -109,10 +105,7 @@ cat; meow; 1; ; true
## Trim strings ## Trim strings
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- ' cat' [' cat', 'dog ', ' cow cow ', horse]
- 'dog '
- ' cow cow '
- horse
``` ```
then then
```bash ```bash
@ -284,8 +277,7 @@ Like jq's equivalent, this works like match but only returns true/false instead
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- cat [cat, dog]
- dog
``` ```
then then
```bash ```bash
@ -354,7 +346,7 @@ b: !goat heart
## Split strings ## Split strings
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
cat; meow; 1; ; true "cat; meow; 1; ; true"
``` ```
then then
```bash ```bash
@ -372,7 +364,7 @@ will output
## Split strings one match ## Split strings one match
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
word "word"
``` ```
then then
```bash ```bash

View File

@ -28,9 +28,7 @@ Note that order of the keys does not matter
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- a: b [{a: b, c: d}, {a: b}]
c: d
- a: b
``` ```
then then
```bash ```bash
@ -38,7 +36,7 @@ yq '. - [{"c": "d", "a": "b"}]' sample.yml
``` ```
will output will output
```yaml ```yaml
- a: b [{a: b}]
``` ```
## Number subtraction - float ## Number subtraction - float
@ -46,8 +44,7 @@ If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 3 {a: 3, b: 4.5}
b: 4.5
``` ```
then then
```bash ```bash
@ -55,8 +52,7 @@ yq '.a = .a - .b' sample.yml
``` ```
will output will output
```yaml ```yaml
a: -1.5 {a: -1.5, b: 4.5}
b: 4.5
``` ```
## Number subtraction - int ## Number subtraction - int
@ -64,8 +60,7 @@ If both the lhs and rhs are ints then the expression will be calculated with int
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 3 {a: 3, b: 4}
b: 4
``` ```
then then
```bash ```bash
@ -73,15 +68,13 @@ yq '.a = .a - .b' sample.yml
``` ```
will output will output
```yaml ```yaml
a: -1 {a: -1, b: 4}
b: 4
``` ```
## Decrement numbers ## Decrement numbers
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: 3 {a: 3, b: 5}
b: 5
``` ```
then then
```bash ```bash
@ -89,8 +82,7 @@ yq '.[] -= 1' sample.yml
``` ```
will output will output
```yaml ```yaml
a: 2 {a: 2, b: 4}
b: 4
``` ```
## Date subtraction ## Date subtraction

View File

@ -5,11 +5,7 @@ The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!in
## Get tag ## Get tag
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat, b: 5, c: 3.2, e: true, f: []}
b: 5
c: 3.2
e: true
f: []
``` ```
then then
```bash ```bash
@ -28,11 +24,7 @@ will output
## type is an alias for tag ## type is an alias for tag
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat, b: 5, c: 3.2, e: true, f: []}
b: 5
c: 3.2
e: true
f: []
``` ```
then then
```bash ```bash
@ -51,7 +43,7 @@ will output
## Set custom tag ## Set custom tag
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: str {a: str}
``` ```
then then
```bash ```bash
@ -59,16 +51,13 @@ yq '.a tag = "!!mikefarah"' sample.yml
``` ```
will output will output
```yaml ```yaml
a: !!mikefarah str {a: !!mikefarah str}
``` ```
## Find numbers and convert them to strings ## Find numbers and convert them to strings
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat {a: cat, b: 5, c: 3.2, e: true}
b: 5
c: 3.2
e: true
``` ```
then then
```bash ```bash
@ -76,9 +65,6 @@ yq '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml
``` ```
will output will output
```yaml ```yaml
a: cat {a: cat, b: "5", c: 3.2, e: true}
b: "5"
c: 3.2
e: true
``` ```

View File

@ -8,10 +8,7 @@ Note that unique maintains the original order of the array.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- 2 [2, 1, 3, 2]
- 1
- 3
- 2
``` ```
then then
```bash ```bash
@ -19,9 +16,7 @@ yq 'unique' sample.yml
``` ```
will output will output
```yaml ```yaml
- 2 [2, 1, 3]
- 1
- 3
``` ```
## Unique nulls ## Unique nulls
@ -29,10 +24,7 @@ Unique works on the node value, so it considers different representations of nul
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- ~ [~, null, ~, null]
- null
- ~
- null
``` ```
then then
```bash ```bash
@ -40,8 +32,7 @@ yq 'unique' sample.yml
``` ```
will output will output
```yaml ```yaml
- ~ [~, null]
- null
``` ```
## Unique all nulls ## Unique all nulls
@ -49,10 +40,7 @@ Run against the node tag to unique all the nulls
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- ~ [~, null, ~, null]
- null
- ~
- null
``` ```
then then
```bash ```bash
@ -60,18 +48,13 @@ yq 'unique_by(tag)' sample.yml
``` ```
will output will output
```yaml ```yaml
- ~ [~]
``` ```
## Unique array object fields ## Unique array object fields
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
- name: harry [{name: harry, pet: cat}, {name: billy, pet: dog}, {name: harry, pet: dog}]
pet: cat
- name: billy
pet: dog
- name: harry
pet: dog
``` ```
then then
```bash ```bash
@ -79,9 +62,6 @@ yq 'unique_by(.name)' sample.yml
``` ```
will output will output
```yaml ```yaml
- name: harry [{name: harry, pet: cat}, {name: billy, pet: dog}]
pet: cat
- name: billy
pet: dog
``` ```

View File

@ -111,3 +111,98 @@ Gary,1
Samantha's Rabbit,2 Samantha's Rabbit,2
``` ```
## Encode array of objects to csv - missing fields behaviour
First entry is used to determine the headers, and it is missing 'likesApples', so it is not included in the csv. Second entry does not have 'numberOfCats' so that is blank
Given a sample.yml file of:
```yaml
- name: Gary
numberOfCats: 1
height: 168.8
- name: Samantha's Rabbit
height: -188.8
likesApples: false
```
then
```bash
yq -o=csv sample.yml
```
will output
```csv
name,numberOfCats,height
Gary,1,168.8
Samantha's Rabbit,,-188.8
```
## Parse CSV into an array of objects
First row is assumed to be the header row.
Given a sample.csv file of:
```csv
name,numberOfCats,likesApples,height
Gary,1,true,168.8
Samantha's Rabbit,2,false,-188.8
```
then
```bash
yq -p=csv sample.csv
```
will output
```yaml
- name: Gary
numberOfCats: 1
likesApples: true
height: 168.8
- name: Samantha's Rabbit
numberOfCats: 2
likesApples: false
height: -188.8
```
## Parse TSV into an array of objects
First row is assumed to be the header row.
Given a sample.tsv file of:
```tsv
name numberOfCats likesApples height
Gary 1 true 168.8
Samantha's Rabbit 2 false -188.8
```
then
```bash
yq -p=tsv sample.tsv
```
will output
```yaml
- name: Gary
numberOfCats: 1
likesApples: true
height: 168.8
- name: Samantha's Rabbit
numberOfCats: 2
likesApples: false
height: -188.8
```
## Round trip
Given a sample.csv file of:
```csv
name,numberOfCats,likesApples,height
Gary,1,true,168.8
Samantha's Rabbit,2,false,-188.8
```
then
```bash
yq -p=csv -o=csv '(.[] | select(.name == "Gary") | .numberOfCats) = 3' sample.csv
```
will output
```csv
name,numberOfCats,likesApples,height
Gary,3,true,168.8
Samantha's Rabbit,2,false,-188.8
```

View File

@ -55,14 +55,6 @@ func (je *jsonEncoder) Encode(writer io.Writer, node *CandidateNode) error {
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, > encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
encoder.SetIndent("", je.indentString) encoder.SetIndent("", je.indentString)
// var dataBucket orderedMap
// firstly, convert all map keys to strings
// mapKeysToStrings(node)
// errorDecoding := node.Decode(&dataBucket)
// if errorDecoding != nil {
// return errorDecoding
// }
err := encoder.Encode(node) err := encoder.Encode(node)
if err != nil { if err != nil {
return err return err

View File

@ -9,7 +9,6 @@ import (
"strings" "strings"
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
) )
var ExpressionParser ExpressionParserInterface var ExpressionParser ExpressionParserInterface
@ -291,45 +290,6 @@ func recursiveNodeEqual(lhs *CandidateNode, rhs *CandidateNode) bool {
return false return false
} }
func deepCloneContent(content []*yaml.Node) []*yaml.Node {
clonedContent := make([]*yaml.Node, len(content))
for i, child := range content {
clonedContent[i] = deepClone(child)
}
return clonedContent
}
func deepCloneNoContent(node *yaml.Node) *yaml.Node {
return deepCloneWithOptions(node, false)
}
func deepClone(node *yaml.Node) *yaml.Node {
return deepCloneWithOptions(node, true)
}
func deepCloneWithOptions(node *yaml.Node, cloneContent bool) *yaml.Node {
if node == nil {
return nil
}
var clonedContent []*yaml.Node
if cloneContent {
clonedContent = deepCloneContent(node.Content)
}
return &yaml.Node{
Content: clonedContent,
Kind: node.Kind,
Style: node.Style,
Tag: node.Tag,
Value: node.Value,
Anchor: node.Anchor,
Alias: node.Alias,
HeadComment: node.HeadComment,
LineComment: node.LineComment,
FootComment: node.FootComment,
Line: node.Line,
Column: node.Column,
}
}
// yaml numbers can be hex encoded... // yaml numbers can be hex encoded...
func parseInt64(numberString string) (string, int64, error) { func parseInt64(numberString string) (string, int64, error) {
if strings.HasPrefix(numberString, "0x") || if strings.HasPrefix(numberString, "0x") ||
@ -436,6 +396,24 @@ func NodeToString(node *CandidateNode) string {
} }
return fmt.Sprintf(`D%v, P%v, %v (%v)::%v`, node.GetDocument(), node.GetNicePath(), KindString(node.Kind), tag, valueToUse) return fmt.Sprintf(`D%v, P%v, %v (%v)::%v`, node.GetDocument(), node.GetNicePath(), KindString(node.Kind), tag, valueToUse)
} }
func NodeContentToString(node *CandidateNode, depth int) string {
if !log.IsEnabledFor(logging.DEBUG) {
return ""
}
var sb strings.Builder
for _, child := range node.Content {
for i := 0; i < depth; i++ {
sb.WriteString(" ")
}
sb.WriteString("- ")
sb.WriteString(NodeToString(child))
sb.WriteString("\n")
sb.WriteString(NodeContentToString(child, depth+1))
}
return sb.String()
}
func KindString(kind Kind) string { func KindString(kind Kind) string {
switch kind { switch kind {
case ScalarNode: case ScalarNode:

View File

@ -4,7 +4,6 @@ func matchKey(name string, pattern string) (matched bool) {
if pattern == "" { if pattern == "" {
return name == pattern return name == pattern
} }
log.Debug("pattern: %v", pattern)
if pattern == "*" { if pattern == "*" {
log.Debug("wild!") log.Debug("wild!")
return true return true

View File

@ -17,20 +17,17 @@ func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
return compoundAssignFunction(d, context, expressionNode, createAddOp) return compoundAssignFunction(d, context, expressionNode, createAddOp)
} }
func toNodes(candidate *CandidateNode, lhs *CandidateNode) ([]*CandidateNode, error) { func toNodes(candidate *CandidateNode, lhs *CandidateNode) []*CandidateNode {
if candidate.Tag == "!!null" {
return []*CandidateNode{}, nil
}
clone := candidate.Copy() clone := candidate.Copy()
switch candidate.Kind { switch candidate.Kind {
case SequenceNode: case SequenceNode:
return clone.Content, nil return clone.Content
default: default:
if len(lhs.Content) > 0 { if len(lhs.Content) > 0 {
clone.Style = lhs.Content[0].Style clone.Style = lhs.Content[0].Style
} }
return []*CandidateNode{clone}, nil return []*CandidateNode{clone}
} }
} }
@ -60,10 +57,7 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
} }
addMaps(target, lhs, rhs) addMaps(target, lhs, rhs)
case SequenceNode: case SequenceNode:
if err := addSequences(target, lhs, rhs); err != nil { addSequences(target, lhs, rhs)
return nil, err
}
case ScalarNode: case ScalarNode:
if rhs.Kind != ScalarNode { if rhs.Kind != ScalarNode {
return nil, fmt.Errorf("%v (%v) cannot be added to a %v (%v)", rhs.Tag, rhs.GetNicePath(), lhsNode.Tag, lhs.GetNicePath()) return nil, fmt.Errorf("%v (%v) cannot be added to a %v (%v)", rhs.Tag, rhs.GetNicePath(), lhsNode.Tag, lhs.GetNicePath())
@ -156,7 +150,7 @@ func addDateTimes(layout string, target *CandidateNode, lhs *CandidateNode, rhs
} }
func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) error { func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) {
log.Debugf("adding sequences! target: %v; lhs %v; rhs: %v", NodeToString(target), NodeToString(lhs), NodeToString(rhs)) log.Debugf("adding sequences! target: %v; lhs %v; rhs: %v", NodeToString(target), NodeToString(lhs), NodeToString(rhs))
target.Kind = SequenceNode target.Kind = SequenceNode
if len(lhs.Content) == 0 { if len(lhs.Content) == 0 {
@ -165,15 +159,10 @@ func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode)
} }
target.Tag = lhs.Tag target.Tag = lhs.Tag
extraNodes, err := toNodes(rhs, lhs) extraNodes := toNodes(rhs, lhs)
if err != nil {
return err
}
target.AddChildren(lhs.Content) target.AddChildren(lhs.Content)
target.AddChildren(extraNodes) target.AddChildren(extraNodes)
return nil
} }
func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) { func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) {
@ -185,8 +174,8 @@ func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) {
target.Style = 0 target.Style = 0
} }
target.Content = make([]*CandidateNode, len(lhs.Content)) target.Content = make([]*CandidateNode, 0)
copy(target.Content, lhs.Content) target.AddChildren(lhs.Content)
for index := 0; index < len(rhs.Content); index = index + 2 { for index := 0; index < len(rhs.Content); index = index + 2 {
key := rhs.Content[index] key := rhs.Content[index]
@ -196,10 +185,12 @@ func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) {
log.Debug("indexInLhs %v", indexInLHS) log.Debug("indexInLhs %v", indexInLHS)
if indexInLHS < 0 { if indexInLHS < 0 {
// not in there, append it // not in there, append it
target.Content = append(target.Content, key, value) target.AddKeyValueChild(key, value)
} else { } else {
// it's there, replace it // it's there, replace it
target.Content[indexInLHS+1] = value oldValue := target.Content[indexInLHS+1]
newValueCopy := oldValue.CopyAsReplacement(value)
target.Content[indexInLHS+1] = newValueCopy
} }
} }
target.Kind = MappingNode target.Kind = MappingNode

View File

@ -9,10 +9,7 @@ func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *
if lhs == nil { if lhs == nil {
return nil, nil return nil, nil
} }
truthy, err := isTruthyNode(lhs) truthy := isTruthyNode(lhs)
if err != nil {
return nil, err
}
if truthy { if truthy {
return lhs, nil return lhs, nil
} }
@ -30,10 +27,8 @@ func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode,
return lhs, nil return lhs, nil
} }
isTrue, err := isTruthyNode(lhs) isTrue := isTruthyNode(lhs)
if err != nil { if isTrue {
return nil, err
} else if isTrue {
return lhs, nil return lhs, nil
} }
return rhs, nil return rhs, nil

View File

@ -171,11 +171,9 @@ func reconstructAliasedMap(node *CandidateNode, context Context) error {
} }
} }
} }
node.Content = make([]*CandidateNode, newContent.Len()) node.Content = make([]*CandidateNode, 0)
index := 0
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() { for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
node.Content[index] = newEl.Value.(*CandidateNode) node.AddChild(newEl.Value.(*CandidateNode))
index++
} }
return nil return nil
} }

View File

@ -6,23 +6,23 @@ import (
"strings" "strings"
) )
func isTruthyNode(candidate *CandidateNode) (bool, error) { func isTruthyNode(candidate *CandidateNode) bool {
if candidate == nil { if candidate == nil {
return false, nil return false
} }
node := candidate.unwrapDocument() node := candidate.unwrapDocument()
if node.Tag == "!!null" { if node.Tag == "!!null" {
return false, nil return false
} }
if node.Kind == ScalarNode && node.Tag == "!!bool" { if node.Kind == ScalarNode && node.Tag == "!!bool" {
// yes/y/true/on // yes/y/true/on
return (strings.EqualFold(node.Value, "y") || return (strings.EqualFold(node.Value, "y") ||
strings.EqualFold(node.Value, "yes") || strings.EqualFold(node.Value, "yes") ||
strings.EqualFold(node.Value, "on") || strings.EqualFold(node.Value, "on") ||
strings.EqualFold(node.Value, "true")), nil strings.EqualFold(node.Value, "true"))
} }
return true, nil return true
} }
func getOwner(lhs *CandidateNode, rhs *CandidateNode) *CandidateNode { func getOwner(lhs *CandidateNode, rhs *CandidateNode) *CandidateNode {
@ -38,10 +38,7 @@ func getOwner(lhs *CandidateNode, rhs *CandidateNode) *CandidateNode {
func returnRhsTruthy(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func returnRhsTruthy(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
owner := getOwner(lhs, rhs) owner := getOwner(lhs, rhs)
rhsBool, err := isTruthyNode(rhs) rhsBool := isTruthyNode(rhs)
if err != nil {
return nil, err
}
return createBooleanCandidate(owner, rhsBool), nil return createBooleanCandidate(owner, rhsBool), nil
} }
@ -51,7 +48,7 @@ func returnLHSWhen(targetBool bool) func(lhs *CandidateNode) (*CandidateNode, er
var err error var err error
var lhsBool bool var lhsBool bool
if lhsBool, err = isTruthyNode(lhs); err != nil || lhsBool != targetBool { if lhsBool = isTruthyNode(lhs); lhsBool != targetBool {
return nil, err return nil, err
} }
owner := &CandidateNode{} owner := &CandidateNode{}
@ -79,11 +76,7 @@ func findBoolean(wantBool bool, d *dataTreeNavigator, context Context, expressio
} }
} }
truthy, err := isTruthyNode(node) if isTruthyNode(node) == wantBool {
if err != nil {
return false, err
}
if truthy == wantBool {
return true, nil return true, nil
} }
} }
@ -153,10 +146,7 @@ func notOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debug("notOperation checking %v", candidate) log.Debug("notOperation checking %v", candidate)
truthy, errDecoding := isTruthyNode(candidate) truthy := isTruthyNode(candidate)
if errDecoding != nil {
return Context{}, errDecoding
}
result := createBooleanCandidate(candidate, !truthy) result := createBooleanCandidate(candidate, !truthy)
results.PushBack(result) results.PushBack(result)
} }

View File

@ -15,8 +15,7 @@ func collectTogether(d *dataTreeNavigator, context Context, expressionNode *Expr
for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() { for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() {
resultC := result.Value.(*CandidateNode) resultC := result.Value.(*CandidateNode)
log.Debugf("found this: %v", NodeToString(resultC)) log.Debugf("found this: %v", NodeToString(resultC))
collectedNode.AddChildren([]*CandidateNode{resultC}) collectedNode.AddChild(resultC)
// collectedNode.Content = append(collectedNode.Content, resultC.unwrapDocument())
} }
} }
return collectedNode, nil return collectedNode, nil
@ -65,8 +64,7 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() { for result := collectExpResults.MatchingNodes.Front(); result != nil; result = result.Next() {
resultC := result.Value.(*CandidateNode) resultC := result.Value.(*CandidateNode)
log.Debugf("found this: %v", NodeToString(resultC)) log.Debugf("found this: %v", NodeToString(resultC))
collectCandidate.AddChildren([]*CandidateNode{resultC}) collectCandidate.AddChild(resultC)
// collectCandidate.Content = append(collectCandidate.Content, resultC.unwrapDocument())
} }
log.Debugf("done collect rhs: %v", expressionNode.RHS.Operation.toString()) log.Debugf("done collect rhs: %v", expressionNode.RHS.Operation.toString())

View File

@ -34,12 +34,14 @@ func collectObjectOperator(d *dataTreeNavigator, originalContext Context, expres
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode) candidateNode := el.Value.(*CandidateNode)
for i := 0; i < len(first.Content); i++ {
for i := 0; i < len(first.Content); i++ {
log.Debugf("rotate[%v] = %v", i, NodeToString(candidateNode.Content[i]))
log.Debugf("children:\n%v", NodeContentToString(candidateNode.Content[i], 0))
rotated[i].PushBack(candidateNode.Content[i]) rotated[i].PushBack(candidateNode.Content[i])
} }
} }
log.Debugf("-- collectObjectOperation, lenght of rotated is %v", len(rotated)) log.Debugf("-- collectObjectOperation, length of rotated is %v", len(rotated))
newObject := list.New() newObject := list.New()
for i := 0; i < len(first.Content); i++ { for i := 0; i < len(first.Content); i++ {
@ -80,7 +82,9 @@ func collect(d *dataTreeNavigator, context Context, remainingMatches *list.List)
aggCandidate := el.Value.(*CandidateNode) aggCandidate := el.Value.(*CandidateNode)
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() { for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatCandidate := splatEl.Value.(*CandidateNode) splatCandidate := splatEl.Value.(*CandidateNode)
log.Debugf("-- collectObjectOperation; splatCandidate: %v", NodeToString(splatCandidate))
newCandidate := aggCandidate.Copy() newCandidate := aggCandidate.Copy()
log.Debugf("-- collectObjectOperation; aggCandidate: %v", NodeToString(aggCandidate))
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate) newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate)

View File

@ -19,7 +19,7 @@ var columnOperatorScenarios = []expressionScenario{
document: "a: cat\nb: bob", document: "a: cat\nb: bob",
expression: `.b | key | column`, expression: `.b | key | column`,
expected: []string{ expected: []string{
"D0, P[1], (!!int)::1\n", "D0, P[b], (!!int)::1\n",
}, },
}, },
{ {
@ -27,7 +27,7 @@ var columnOperatorScenarios = []expressionScenario{
document: "a: cat", document: "a: cat",
expression: `.a | key | column`, expression: `.a | key | column`,
expected: []string{ expected: []string{
"D0, P[1], (!!int)::1\n", "D0, P[a], (!!int)::1\n",
}, },
}, },
{ {

View File

@ -54,57 +54,57 @@ var expectedWhereIsMyCommentArray = `D0, P[], (!!seq)::- p: ""
` `
var commentOperatorScenarios = []expressionScenario{ var commentOperatorScenarios = []expressionScenario{
// { {
// description: "Set line comment", description: "Set line comment",
// subdescription: "Set the comment on the key node for more reliability (see below).", subdescription: "Set the comment on the key node for more reliability (see below).",
// document: `a: cat`, document: `a: cat`,
// expression: `.a line_comment="single"`, expression: `.a line_comment="single"`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat # single\n", "D0, P[], (doc)::a: cat # single\n",
// }, },
// }, },
// { {
// description: "Set line comment of a maps/arrays", description: "Set line comment of a maps/arrays",
// subdescription: "For maps and arrays, you need to set the line comment on the _key_ node. This will also work for scalars.", subdescription: "For maps and arrays, you need to set the line comment on the _key_ node. This will also work for scalars.",
// document: "a:\n b: things", document: "a:\n b: things",
// expression: `(.a | key) line_comment="single"`, expression: `(.a | key) line_comment="single"`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: # single\n b: things\n", "D0, P[], (doc)::a: # single\n b: things\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: "a: cat\nb: dog", document: "a: cat\nb: dog",
// expression: `.a line_comment=.b`, expression: `.a line_comment=.b`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat # dog\nb: dog\n", "D0, P[], (doc)::a: cat # dog\nb: dog\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: "a: cat\n---\na: dog", document: "a: cat\n---\na: dog",
// expression: `.a line_comment |= documentIndex`, expression: `.a line_comment |= documentIndex`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat # 0\n", "D0, P[], (doc)::a: cat # 0\n",
// "D1, P[], (doc)::a: dog # 1\n", "D1, P[], (doc)::a: dog # 1\n",
// }, },
// }, },
// { {
// description: "Use update assign to perform relative updates", description: "Use update assign to perform relative updates",
// document: "a: cat\nb: dog", document: "a: cat\nb: dog",
// expression: `.. line_comment |= .`, expression: `.. line_comment |= .`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat # cat\nb: dog # dog\n", "D0, P[], (doc)::a: cat # cat\nb: dog # dog\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: "a: cat\nb: dog", document: "a: cat\nb: dog",
// expression: `.. comments |= .`, expression: `.. comments |= .`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n", "D0, P[], (doc)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n",
// }, },
// }, },
{ {
description: "Where is the comment - map key example", description: "Where is the comment - map key example",
subdescription: "The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).\nFrom this, you can see the 'hello-world-comment' is actually on the 'hello' key", subdescription: "The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).\nFrom this, you can see the 'hello-world-comment' is actually on the 'hello' key",
@ -114,152 +114,152 @@ var commentOperatorScenarios = []expressionScenario{
expectedWhereIsMyCommentMapKey, expectedWhereIsMyCommentMapKey,
}, },
}, },
// { {
// description: "Retrieve comment - map key example", description: "Retrieve comment - map key example",
// subdescription: "From the previous example, we know that the comment is on the 'hello' _key_ as a lineComment", subdescription: "From the previous example, we know that the comment is on the 'hello' _key_ as a lineComment",
// document: "hello: # hello-world-comment\n message: world", document: "hello: # hello-world-comment\n message: world",
// expression: `.hello | key | line_comment`, expression: `.hello | key | line_comment`,
// expected: []string{ expected: []string{
// "D0, P[hello], (!!str)::hello-world-comment\n", "D0, P[hello], (!!str)::hello-world-comment\n",
// }, },
// }, },
// { {
// description: "Where is the comment - array example", description: "Where is the comment - array example",
// subdescription: "The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).\nFrom this, you can see the 'under-name-comment' is actually on the first child", subdescription: "The underlying yaml parser can assign comments in a document to surprising nodes. Use an expression like this to find where you comment is. 'p' indicates the path, 'isKey' is if the node is a map key (as opposed to a map value).\nFrom this, you can see the 'under-name-comment' is actually on the first child",
// document: "name:\n # under-name-comment\n - first-array-child", document: "name:\n # under-name-comment\n - first-array-child",
// expression: `[... | {"p": path | join("."), "isKey": is_key, "hc": headComment, "lc": lineComment, "fc": footComment}]`, expression: `[... | {"p": path | join("."), "isKey": is_key, "hc": headComment, "lc": lineComment, "fc": footComment}]`,
// expected: []string{ expected: []string{
// expectedWhereIsMyCommentArray, expectedWhereIsMyCommentArray,
// }, },
// }, },
// { {
// description: "Retrieve comment - array example", description: "Retrieve comment - array example",
// subdescription: "From the previous example, we know that the comment is on the first child as a headComment", subdescription: "From the previous example, we know that the comment is on the first child as a headComment",
// document: "name:\n # under-name-comment\n - first-array-child", document: "name:\n # under-name-comment\n - first-array-child",
// expression: `.name[0] | headComment`, expression: `.name[0] | headComment`,
// expected: []string{ expected: []string{
// "D0, P[name 0], (!!str)::under-name-comment\n", "D0, P[name 0], (!!str)::under-name-comment\n",
// }, },
// }, },
// { {
// description: "Set head comment", description: "Set head comment",
// document: `a: cat`, document: `a: cat`,
// expression: `. head_comment="single"`, expression: `. head_comment="single"`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::# single\n\na: cat\n", "D0, P[], (doc)::# single\n\na: cat\n",
// }, },
// }, },
// { {
// description: "Set head comment of a map entry", description: "Set head comment of a map entry",
// document: "f: foo\na:\n b: cat", document: "f: foo\na:\n b: cat",
// expression: `(.a | key) head_comment="single"`, expression: `(.a | key) head_comment="single"`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::f: foo\n# single\na:\n b: cat\n", "D0, P[], (doc)::f: foo\n# single\na:\n b: cat\n",
// }, },
// }, },
// { {
// description: "Set foot comment, using an expression", description: "Set foot comment, using an expression",
// document: `a: cat`, document: `a: cat`,
// expression: `. foot_comment=.a`, expression: `. foot_comment=.a`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\n# cat\n", "D0, P[], (doc)::a: cat\n# cat\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// description: "Set foot comment, using an expression", description: "Set foot comment, using an expression",
// document: "a: cat\n\n# hi", document: "a: cat\n\n# hi",
// expression: `. foot_comment=""`, expression: `. foot_comment=""`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\n", "D0, P[], (doc)::a: cat\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: `a: cat`, document: `a: cat`,
// expression: `. foot_comment=.b.d`, expression: `. foot_comment=.b.d`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\n", "D0, P[], (doc)::a: cat\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: `a: cat`, document: `a: cat`,
// expression: `. foot_comment|=.b.d`, expression: `. foot_comment|=.b.d`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\n", "D0, P[], (doc)::a: cat\n",
// }, },
// }, },
// { {
// description: "Remove comment", description: "Remove comment",
// document: "a: cat # comment\nb: dog # leave this", document: "a: cat # comment\nb: dog # leave this",
// expression: `.a line_comment=""`, expression: `.a line_comment=""`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\nb: dog # leave this\n", "D0, P[], (doc)::a: cat\nb: dog # leave this\n",
// }, },
// }, },
// { {
// description: "Remove (strip) all comments", description: "Remove (strip) all comments",
// subdescription: "Note the use of `...` to ensure key nodes are included.", subdescription: "Note the use of `...` to ensure key nodes are included.",
// document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment", document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment",
// expression: `... comments=""`, expression: `... comments=""`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\nb:\n", "D0, P[], (doc)::a: cat\nb:\n",
// }, },
// }, },
// { {
// description: "Get line comment", description: "Get line comment",
// document: "# welcome!\n\na: cat # meow\n\n# have a great day", document: "# welcome!\n\na: cat # meow\n\n# have a great day",
// expression: `.a | line_comment`, expression: `.a | line_comment`,
// expected: []string{ expected: []string{
// "D0, P[a], (!!str)::meow\n", "D0, P[a], (!!str)::meow\n",
// }, },
// }, },
// { {
// description: "Get head comment", description: "Get head comment",
// dontFormatInputForDoc: true, dontFormatInputForDoc: true,
// document: "# welcome!\n\na: cat # meow\n\n# have a great day", document: "# welcome!\n\na: cat # meow\n\n# have a great day",
// expression: `. | head_comment`, expression: `. | head_comment`,
// expected: []string{ expected: []string{
// "D0, P[], (!!str)::welcome!\n\n", "D0, P[], (!!str)::welcome!\n\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// description: "strip trailing comment recurse all", description: "strip trailing comment recurse all",
// document: "a: cat\n\n# haha", document: "a: cat\n\n# haha",
// expression: `... comments= ""`, expression: `... comments= ""`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\n", "D0, P[], (doc)::a: cat\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// description: "strip trailing comment recurse values", description: "strip trailing comment recurse values",
// document: "a: cat\n\n# haha", document: "a: cat\n\n# haha",
// expression: `.. comments= ""`, expression: `.. comments= ""`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a: cat\n", "D0, P[], (doc)::a: cat\n",
// }, },
// }, },
// { {
// description: "Head comment with document split", description: "Head comment with document split",
// dontFormatInputForDoc: true, dontFormatInputForDoc: true,
// document: "# welcome!\n---\n# bob\na: cat # meow\n\n# have a great day", document: "# welcome!\n---\n# bob\na: cat # meow\n\n# have a great day",
// expression: `head_comment`, expression: `head_comment`,
// expected: []string{ expected: []string{
// "D0, P[], (!!str)::welcome!\nbob\n", "D0, P[], (!!str)::welcome!\nbob\n",
// }, },
// }, },
// { {
// description: "Get foot comment", description: "Get foot comment",
// dontFormatInputForDoc: true, dontFormatInputForDoc: true,
// document: "# welcome!\n\na: cat # meow\n\n# have a great day\n# no really", document: "# welcome!\n\na: cat # meow\n\n# have a great day\n# no really",
// expression: `. | foot_comment`, expression: `. | foot_comment`,
// expected: []string{ expected: []string{
// "D0, P[], (!!str)::have a great day\nno really\n", "D0, P[], (!!str)::have a great day\nno really\n",
// }, },
// }, },
} }
func TestCommentOperatorScenarios(t *testing.T) { func TestCommentOperatorScenarios(t *testing.T) {

View File

@ -85,7 +85,7 @@ func listToNodeSeq(list *list.List) *CandidateNode {
for entry := list.Front(); entry != nil; entry = entry.Next() { for entry := list.Front(); entry != nil; entry = entry.Next() {
entryCandidate := entry.Value.(*CandidateNode) entryCandidate := entry.Value.(*CandidateNode)
log.Debugf("Collecting %v into sequence", NodeToString(entryCandidate)) log.Debugf("Collecting %v into sequence", NodeToString(entryCandidate))
node.Content = append(node.Content, entryCandidate) node.AddChild(entryCandidate)
} }
return &node return &node
} }

View File

@ -47,7 +47,7 @@ func divideScalars(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode
tKind, tTag, res := split(lhs.Value, rhs.Value) tKind, tTag, res := split(lhs.Value, rhs.Value)
target.Kind = tKind target.Kind = tKind
target.Tag = tTag target.Tag = tTag
target.Content = res target.AddChildren(res)
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") { } else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
target.Kind = ScalarNode target.Kind = ScalarNode
target.Style = lhs.Style target.Style = lhs.Style

View File

@ -24,7 +24,7 @@ func toEntriesFromMap(candidateNode *CandidateNode) *CandidateNode {
key := contents[index] key := contents[index]
value := contents[index+1] value := contents[index+1]
sequence.Content = append(sequence.Content, entrySeqFor(key, value)) sequence.AddChild(entrySeqFor(key, value))
} }
return sequence return sequence
} }
@ -37,7 +37,7 @@ func toEntriesfromSeq(candidateNode *CandidateNode) *CandidateNode {
key := &CandidateNode{Kind: ScalarNode, Tag: "!!int", Value: fmt.Sprintf("%v", index)} key := &CandidateNode{Kind: ScalarNode, Tag: "!!int", Value: fmt.Sprintf("%v", index)}
value := contents[index] value := contents[index]
sequence.Content = append(sequence.Content, entrySeqFor(key, value)) sequence.AddChild(entrySeqFor(key, value))
} }
return sequence return sequence
} }
@ -98,7 +98,7 @@ func fromEntries(candidateNode *CandidateNode) (*CandidateNode, error) {
return nil, err return nil, err
} }
node.Content = append(node.Content, key, value) node.AddKeyValueChild(key, value)
} }
node.Kind = MappingNode node.Kind = MappingNode
node.Tag = "!!map" node.Tag = "!!map"

View File

@ -28,7 +28,8 @@ func flatten(node *CandidateNode, depth int) {
newSeq = append(newSeq, content[i]) newSeq = append(newSeq, content[i])
} }
} }
node.Content = newSeq node.Content = make([]*CandidateNode, 0)
node.AddChildren(newSeq)
} }
func flattenOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func flattenOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
@ -40,7 +41,7 @@ func flattenOp(d *dataTreeNavigator, context Context, expressionNode *Expression
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
candidateNode := candidate.unwrapDocument() candidateNode := candidate.unwrapDocument()
if candidateNode.Kind != SequenceNode { if candidateNode.Kind != SequenceNode {
return Context{}, fmt.Errorf("Only arrays are supported for flatten") return Context{}, fmt.Errorf("only arrays are supported for flatten")
} }
flatten(candidateNode, depth) flatten(candidateNode, depth)

View File

@ -45,7 +45,7 @@ func groupBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionNo
candidateNode := candidate.unwrapDocument() candidateNode := candidate.unwrapDocument()
if candidateNode.Kind != SequenceNode { if candidateNode.Kind != SequenceNode {
return Context{}, fmt.Errorf("Only arrays are supported for group by") return Context{}, fmt.Errorf("only arrays are supported for group by")
} }
newMatches, err := processIntoGroups(d, context, expressionNode.RHS, candidateNode) newMatches, err := processIntoGroups(d, context, expressionNode.RHS, candidateNode)
@ -59,10 +59,10 @@ func groupBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionNo
groupResultNode := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"} groupResultNode := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"}
groupList := groupEl.Value.(*list.List) groupList := groupEl.Value.(*list.List)
for groupItem := groupList.Front(); groupItem != nil; groupItem = groupItem.Next() { for groupItem := groupList.Front(); groupItem != nil; groupItem = groupItem.Next() {
groupResultNode.Content = append(groupResultNode.Content, groupItem.Value.(*CandidateNode)) groupResultNode.AddChild(groupItem.Value.(*CandidateNode))
} }
resultNode.Content = append(resultNode.Content, groupResultNode) resultNode.AddChild(groupResultNode)
} }
results.PushBack(resultNode) results.PushBack(resultNode)

View File

@ -13,19 +13,19 @@ var groupByOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::- - {foo: 1, bar: 10}\n - {foo: 1, bar: 1}\n- - {foo: 3, bar: 100}\n", "D0, P[], (!!seq)::- - {foo: 1, bar: 10}\n - {foo: 1, bar: 1}\n- - {foo: 3, bar: 100}\n",
}, },
}, },
{ // {
description: "Group by field, with nuls", // description: "Group by field, with nuls",
document: `[{cat: dog}, {foo: 1, bar: 10}, {foo: 3, bar: 100}, {no: foo for you}, {foo: 1, bar: 1}]`, // document: `[{cat: dog}, {foo: 1, bar: 10}, {foo: 3, bar: 100}, {no: foo for you}, {foo: 1, bar: 1}]`,
expression: `group_by(.foo)`, // expression: `group_by(.foo)`,
expected: []string{ // expected: []string{
"D0, P[], (!!seq)::- - {cat: dog}\n - {no: foo for you}\n- - {foo: 1, bar: 10}\n - {foo: 1, bar: 1}\n- - {foo: 3, bar: 100}\n", // "D0, P[], (!!seq)::- - {cat: dog}\n - {no: foo for you}\n- - {foo: 1, bar: 10}\n - {foo: 1, bar: 1}\n- - {foo: 3, bar: 100}\n",
}, // },
}, // },
} }
func TestGroupByOperatorScenarios(t *testing.T) { func TestGroupByOperatorScenarios(t *testing.T) {
for _, tt := range groupByOperatorScenarios { for _, tt := range groupByOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }
documentOperatorScenarios(t, "group-by", groupByOperatorScenarios) // documentOperatorScenarios(t, "group-by", groupByOperatorScenarios)
} }

View File

@ -31,82 +31,82 @@ var expectedIsKey = `D0, P[], (!!seq)::- p: ""
` `
var keysOperatorScenarios = []expressionScenario{ var keysOperatorScenarios = []expressionScenario{
// { {
// description: "Map keys", description: "Map keys",
// document: `{dog: woof, cat: meow}`, document: `{dog: woof, cat: meow}`,
// expression: `keys`, expression: `keys`,
// expected: []string{ expected: []string{
// "D0, P[], (!!seq)::- dog\n- cat\n", "D0, P[], (!!seq)::- dog\n- cat\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: `{}`, document: `{}`,
// expression: `keys`, expression: `keys`,
// expected: []string{ expected: []string{
// "D0, P[], (!!seq)::[]\n", "D0, P[], (!!seq)::[]\n",
// }, },
// }, },
// { {
// description: "Array keys", description: "Array keys",
// document: `[apple, banana]`, document: `[apple, banana]`,
// expression: `keys`, expression: `keys`,
// expected: []string{ expected: []string{
// "D0, P[], (!!seq)::- 0\n- 1\n", "D0, P[], (!!seq)::- 0\n- 1\n",
// }, },
// }, },
// { {
// skipDoc: true, skipDoc: true,
// document: `[]`, document: `[]`,
// expression: `keys`, expression: `keys`,
// expected: []string{ expected: []string{
// "D0, P[], (!!seq)::[]\n", "D0, P[], (!!seq)::[]\n",
// }, },
// }, },
// { {
// description: "Retrieve array key", description: "Retrieve array key",
// document: "[1,2,3]", document: "[1,2,3]",
// expression: `.[1] | key`, expression: `.[1] | key`,
// expected: []string{ expected: []string{
// "D0, P[1], (!!int)::1\n", "D0, P[1], (!!int)::1\n",
// }, },
// }, },
// { {
// description: "Retrieve map key", description: "Retrieve map key",
// document: "a: thing", document: "a: thing",
// expression: `.a | key`, expression: `.a | key`,
// expected: []string{ expected: []string{
// "D0, P[a], (!!str)::a\n", "D0, P[a], (!!str)::a\n",
// }, },
// }, },
// { {
// description: "No key", description: "No key",
// document: "{}", document: "{}",
// expression: `key`, expression: `key`,
// expected: []string{}, expected: []string{},
// }, },
// { {
// description: "Update map key", description: "Update map key",
// document: "a:\n x: 3\n y: 4", document: "a:\n x: 3\n y: 4",
// expression: `(.a.x | key) = "meow"`, expression: `(.a.x | key) = "meow"`,
// expected: []string{ expected: []string{
// "D0, P[], (doc)::a:\n meow: 3\n y: 4\n", "D0, P[], (doc)::a:\n meow: 3\n y: 4\n",
// }, },
// }, },
// { {
// description: "Get comment from map key", description: "Get comment from map key",
// document: "a: \n # comment on key\n x: 3\n y: 4", document: "a: \n # comment on key\n x: 3\n y: 4",
// expression: `.a.x | key | headComment`, expression: `.a.x | key | headComment`,
// expected: []string{ expected: []string{
// "D0, P[a x], (!!str)::comment on key\n", "D0, P[a x], (!!str)::comment on key\n",
// }, },
// }, },
{ {
description: "Check node is a key", description: "Check node is a key",
document: "a: frog\n", document: "a: \n b: [cat]\n c: frog\n",
expression: `[... | { "p": path | join("."), "isKey": is_key, "tag": tag }]`, expression: `[... | { "p": path | join("."), "isKey": is_key, "tag": tag }]`,
expected: []string{ expected: []string{
"", expectedIsKey,
}, },
}, },
} }

View File

@ -57,7 +57,7 @@ func loadYaml(filename string, decoder Decoder) (*CandidateNode, error) {
} else { } else {
sequenceNode := &CandidateNode{Kind: SequenceNode} sequenceNode := &CandidateNode{Kind: SequenceNode}
for doc := documents.Front(); doc != nil; doc = doc.Next() { for doc := documents.Front(); doc != nil; doc = doc.Next() {
sequenceNode.Content = append(sequenceNode.Content, doc.Value.(*CandidateNode).unwrapDocument()) sequenceNode.AddChild(doc.Value.(*CandidateNode).unwrapDocument())
} }
return sequenceNode, nil return sequenceNode, nil
} }
@ -81,7 +81,7 @@ func loadYamlOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
return Context{}, err return Context{}, err
} }
if rhs.MatchingNodes.Front() == nil { if rhs.MatchingNodes.Front() == nil {
return Context{}, fmt.Errorf("Filename expression returned nil") return Context{}, fmt.Errorf("filename expression returned nil")
} }
nameCandidateNode := rhs.MatchingNodes.Front().Value.(*CandidateNode) nameCandidateNode := rhs.MatchingNodes.Front().Value.(*CandidateNode)

View File

@ -163,7 +163,7 @@ func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
path := path[pathIndex] path := path[pathIndex]
content[pathIndex] = createPathNodeFor(path) content[pathIndex] = createPathNodeFor(path)
} }
node.Content = content node.AddChildren(content)
results.PushBack(node) results.PushBack(node)
} }

View File

@ -20,7 +20,7 @@ func pickMap(original *CandidateNode, indices *CandidateNode) *CandidateNode {
} }
newNode := original.CopyWithoutContent() newNode := original.CopyWithoutContent()
newNode.Content = filteredContent newNode.AddChildren(filteredContent)
return newNode return newNode
} }
@ -40,7 +40,7 @@ func pickSequence(original *CandidateNode, indices *CandidateNode) (*CandidateNo
} }
newNode := original.CopyWithoutContent() newNode := original.CopyWithoutContent()
newNode.Content = filteredContent newNode.AddChildren(filteredContent)
return newNode, nil return newNode, nil
} }

View File

@ -17,12 +17,13 @@ func reverseOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
return context, fmt.Errorf("node at path [%v] is not an array (it's a %v)", candidate.GetNicePath(), candidate.GetNiceTag()) return context, fmt.Errorf("node at path [%v] is not an array (it's a %v)", candidate.GetNicePath(), candidate.GetNiceTag())
} }
reverseList := candidate.CreateReplacementWithDocWrappers(SequenceNode, "!!tag", candidateNode.Style) reverseList := candidate.CreateReplacementWithDocWrappers(SequenceNode, "!!seq", candidateNode.Style)
reverseList.Content = make([]*CandidateNode, len(candidateNode.Content)) reverseContent := make([]*CandidateNode, len(candidateNode.Content))
for i, originalNode := range candidateNode.Content { for i, originalNode := range candidateNode.Content {
reverseList.Content[len(candidateNode.Content)-i-1] = originalNode reverseContent[len(candidateNode.Content)-i-1] = originalNode
} }
reverseList.AddChildren(reverseContent)
results.PushBack(reverseList) results.PushBack(reverseList)
} }

View File

@ -18,16 +18,11 @@ func selectOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
} }
// find any truthy node // find any truthy node
var errDecoding error
includeResult := false includeResult := false
for resultEl := rhs.MatchingNodes.Front(); resultEl != nil; resultEl = resultEl.Next() { for resultEl := rhs.MatchingNodes.Front(); resultEl != nil; resultEl = resultEl.Next() {
result := resultEl.Value.(*CandidateNode) result := resultEl.Value.(*CandidateNode)
includeResult, errDecoding = isTruthyNode(result) includeResult = isTruthyNode(result)
log.Debugf("isTruthy %v", includeResult)
if errDecoding != nil {
return Context{}, errDecoding
}
if includeResult { if includeResult {
break break
} }

View File

@ -57,7 +57,7 @@ func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *E
} }
sliceArrayNode := lhsNode.CreateReplacement(SequenceNode, lhsNode.Tag, "") sliceArrayNode := lhsNode.CreateReplacement(SequenceNode, lhsNode.Tag, "")
sliceArrayNode.Content = newResults sliceArrayNode.AddChildren(newResults)
results.PushBack(sliceArrayNode) results.PushBack(sliceArrayNode)
} }

View File

@ -47,10 +47,8 @@ func sortByOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
sortedList := candidate.CreateReplacementWithDocWrappers(SequenceNode, "!!seq", candidateNode.Style) sortedList := candidate.CreateReplacementWithDocWrappers(SequenceNode, "!!seq", candidateNode.Style)
sortedList.Content = make([]*CandidateNode, len(candidateNode.Content)) for _, sortedNode := range sortableArray {
sortedList.AddChild(sortedNode.Node)
for i, sortedNode := range sortableArray {
sortedList.Content[i] = sortedNode.Node
} }
results.PushBack(sortedList) results.PushBack(sortedList)
} }
@ -122,15 +120,9 @@ func (a sortableNodeArray) compare(lhs *CandidateNode, rhs *CandidateNode, dateT
} else if lhsTag != "!!bool" && rhsTag == "!!bool" { } else if lhsTag != "!!bool" && rhsTag == "!!bool" {
return 1 return 1
} else if lhsTag == "!!bool" && rhsTag == "!!bool" { } else if lhsTag == "!!bool" && rhsTag == "!!bool" {
lhsTruthy, err := isTruthyNode(lhs) lhsTruthy := isTruthyNode(lhs)
if err != nil {
panic(fmt.Errorf("could not parse %v as boolean: %w", lhs.Value, err))
}
rhsTruthy, err := isTruthyNode(rhs) rhsTruthy := isTruthyNode(rhs)
if err != nil {
panic(fmt.Errorf("could not parse %v as boolean: %w", rhs.Value, err))
}
if lhsTruthy == rhsTruthy { if lhsTruthy == rhsTruthy {
return 0 return 0
} else if lhsTruthy { } else if lhsTruthy {

View File

@ -46,5 +46,8 @@ func sortKeys(node *CandidateNode) {
sortedContent[index*2] = keyBucket[keyString] sortedContent[index*2] = keyBucket[keyString]
sortedContent[1+(index*2)] = valueBucket[keyString] sortedContent[1+(index*2)] = valueBucket[keyString]
} }
// re-arranging children, no need to update their parent
// relationship
node.Content = sortedContent node.Content = sortedContent
} }

View File

@ -27,7 +27,7 @@ var sortKeysOperatorScenarios = []expressionScenario{
document: `{bParent: {c: dog, array: [3,1,2]}, aParent: {z: donkey, x: [{c: yum, b: delish}, {b: ew, a: apple}]}}`, document: `{bParent: {c: dog, array: [3,1,2]}, aParent: {z: donkey, x: [{c: yum, b: delish}, {b: ew, a: apple}]}}`,
expression: `sort_keys(..)`, expression: `sort_keys(..)`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::{aParent: {x: [{b: delish, c: yum}, {a: apple, b: ew}], z: donkey}, bParent: {array: [3, 1, 2], c: dog}}\n", "D0, P[], (doc)::{aParent: {x: [{b: delish, c: yum}, {a: apple, b: ew}], z: donkey}, bParent: {array: [3, 1, 2], c: dog}}\n",
}, },
}, },
} }

View File

@ -191,16 +191,13 @@ func match(matchPrefs matchPreferences, regEx *regexp.Regexp, candidate *Candida
match, submatches := matches[0], matches[1:] match, submatches := matches[0], matches[1:]
for j, submatch := range submatches { for j, submatch := range submatches {
captureNode := &CandidateNode{Kind: MappingNode} captureNode := &CandidateNode{Kind: MappingNode}
captureNode.Content = addMatch(captureNode.Content, submatch, allIndices[i][2+j*2], subNames[j+1]) captureNode.AddChildren(addMatch(captureNode.Content, submatch, allIndices[i][2+j*2], subNames[j+1]))
capturesListNode.Content = append(capturesListNode.Content, captureNode) capturesListNode.AddChild(captureNode)
} }
node := candidate.CreateReplacement(MappingNode, "!!map", "") node := candidate.CreateReplacement(MappingNode, "!!map", "")
node.Content = addMatch(node.Content, match, allIndices[i][0], "") node.AddChildren(addMatch(node.Content, match, allIndices[i][0], ""))
node.Content = append(node.Content, node.AddKeyValueChild(createScalarNode("captures", "captures"), capturesListNode)
createScalarNode("captures", "captures"),
capturesListNode,
)
results.PushBack(node) results.PushBack(node)
} }
@ -222,20 +219,18 @@ func capture(matchPrefs matchPreferences, regEx *regexp.Regexp, candidate *Candi
_, submatches := matches[0], matches[1:] _, submatches := matches[0], matches[1:]
for j, submatch := range submatches { for j, submatch := range submatches {
capturesNode.Content = append(capturesNode.Content,
createScalarNode(subNames[j+1], subNames[j+1])) keyNode := createScalarNode(subNames[j+1], subNames[j+1])
var valueNode *CandidateNode
offset := allIndices[i][2+j*2] offset := allIndices[i][2+j*2]
// offset of -1 means there was no match, force a null value like jq // offset of -1 means there was no match, force a null value like jq
if offset < 0 { if offset < 0 {
capturesNode.Content = append(capturesNode.Content, valueNode = createScalarNode(nil, "null")
createScalarNode(nil, "null"),
)
} else { } else {
capturesNode.Content = append(capturesNode.Content, valueNode = createScalarNode(submatch, submatch)
createScalarNode(submatch, submatch),
)
} }
capturesNode.AddKeyValueChild(keyNode, valueNode)
} }
results.PushBack(capturesNode) results.PushBack(capturesNode)
@ -416,11 +411,11 @@ func splitStringOperator(d *dataTreeNavigator, context Context, expressionNode *
} }
if node.guessTagFromCustomType() != "!!str" { if node.guessTagFromCustomType() != "!!str" {
return Context{}, fmt.Errorf("Cannot split %v, can only split strings", node.Tag) return Context{}, fmt.Errorf("cannot split %v, can only split strings", node.Tag)
} }
kind, tag, content := split(node.Value, splitStr) kind, tag, content := split(node.Value, splitStr)
result := candidate.CreateReplacement(kind, tag, "") result := candidate.CreateReplacement(kind, tag, "")
result.Content = content result.AddChildren(content)
results.PushBack(result) results.PushBack(result)
} }

View File

@ -71,7 +71,7 @@ var stringsOperatorScenarios = []expressionScenario{
document: `foo bar foo`, document: `foo bar foo`,
expression: `match("foo")`, expression: `match("foo")`,
expected: []string{ expected: []string{
"D0, P[], ()::string: foo\noffset: 0\nlength: 3\ncaptures: []\n", "D0, P[], (!!map)::string: foo\noffset: 0\nlength: 3\ncaptures: []\n",
}, },
}, },
{ {
@ -79,7 +79,7 @@ var stringsOperatorScenarios = []expressionScenario{
document: `!horse foo bar foo`, document: `!horse foo bar foo`,
expression: `match("foo")`, expression: `match("foo")`,
expected: []string{ expected: []string{
"D0, P[], ()::string: foo\noffset: 0\nlength: 3\ncaptures: []\n", "D0, P[], (!!map)::string: foo\noffset: 0\nlength: 3\ncaptures: []\n",
}, },
}, },
{ {
@ -111,7 +111,7 @@ var stringsOperatorScenarios = []expressionScenario{
document: `xyzzy-14`, document: `xyzzy-14`,
expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`, expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`,
expected: []string{ expected: []string{
"D0, P[], ()::a: xyzzy\nn: \"14\"\n", "D0, P[], (!!map)::a: xyzzy\nn: \"14\"\n",
}, },
}, },
{ {
@ -119,7 +119,7 @@ var stringsOperatorScenarios = []expressionScenario{
document: `!horse xyzzy-14`, document: `!horse xyzzy-14`,
expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`, expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`,
expected: []string{ expected: []string{
"D0, P[], ()::a: xyzzy\nn: \"14\"\n", "D0, P[], (!!map)::a: xyzzy\nn: \"14\"\n",
}, },
}, },
{ {
@ -128,7 +128,7 @@ var stringsOperatorScenarios = []expressionScenario{
document: `xyzzy-14`, document: `xyzzy-14`,
expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)(?P<bar123>bar)?")`, expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)(?P<bar123>bar)?")`,
expected: []string{ expected: []string{
"D0, P[], ()::a: xyzzy\nn: \"14\"\nbar123: null\n", "D0, P[], (!!map)::a: xyzzy\nn: \"14\"\nbar123: null\n",
}, },
}, },
{ {
@ -136,7 +136,7 @@ var stringsOperatorScenarios = []expressionScenario{
document: `cat cat`, document: `cat cat`,
expression: `match("cat")`, expression: `match("cat")`,
expected: []string{ expected: []string{
"D0, P[], ()::string: cat\noffset: 0\nlength: 3\ncaptures: []\n", "D0, P[], (!!map)::string: cat\noffset: 0\nlength: 3\ncaptures: []\n",
}, },
}, },
{ {

View File

@ -37,6 +37,7 @@ func subtractArray(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, erro
newLHSArray = append(newLHSArray, lhs.Content[lindex]) newLHSArray = append(newLHSArray, lhs.Content[lindex])
} }
} }
// removing children from LHS, parent hasn't changed
lhs.Content = newLHSArray lhs.Content = newLHSArray
return lhs, nil return lhs, nil
} }

View File

@ -5,6 +5,16 @@ import (
) )
var tagOperatorScenarios = []expressionScenario{ var tagOperatorScenarios = []expressionScenario{
{
description: "tag of key is not a key",
subdescription: "so it should have 'a' as the path",
skipDoc: true,
document: "a: frog\n",
expression: `.a | key | tag`,
expected: []string{
"D0, P[a], (!!str)::!!str\n",
},
},
{ {
description: "Get tag", description: "Get tag",
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`, document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
@ -69,7 +79,7 @@ var tagOperatorScenarios = []expressionScenario{
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `(.. | select(tag == "!!int")) tag= "!!str"`, expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n", "D0, P[], (doc)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
}, },
}, },
{ {

View File

@ -203,13 +203,8 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*CandidateNode
node.Style = 0 node.Style = 0
} }
valueNode := node.CreateChild() valueNode := createScalarNode(nil, "null")
valueNode.Kind = ScalarNode node.AddChild(valueNode)
valueNode.Tag = "!!null"
valueNode.Value = "null"
valueNode.Key = createScalarNode(contentLength, fmt.Sprintf("%v", contentLength))
node.Content = append(node.Content, valueNode)
contentLength = len(node.Content) contentLength = len(node.Content)
} }
@ -251,7 +246,7 @@ func traverseMap(context Context, matchingNode *CandidateNode, keyNode *Candidat
matchingNode.Style = 0 matchingNode.Style = 0
} }
matchingNode.Content = append(matchingNode.Content, keyNode, valueNode) matchingNode.AddKeyValueChild(keyNode, valueNode)
if prefs.IncludeMapKeys { if prefs.IncludeMapKeys {
newMatches.Set(keyNode.GetKey(), keyNode) newMatches.Set(keyNode.GetKey(), keyNode)
@ -281,11 +276,10 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, node *CandidateNode, wante
key := contents[index] key := contents[index]
value := contents[index+1] value := contents[index+1]
log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first //skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && !prefs.DontFollowAlias { if key.Tag == "!!merge" && !prefs.DontFollowAlias {
log.Debug("Merge anchor") log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, node, value, wantedKey, prefs, splat) err := traverseMergeAnchor(newMatches, value, wantedKey, prefs, splat)
if err != nil { if err != nil {
return err return err
} }
@ -305,17 +299,16 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, node *CandidateNode, wante
return nil return nil
} }
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error { func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, value *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error {
switch value.Kind { switch value.Kind {
case AliasNode: case AliasNode:
if value.Alias.Kind != MappingNode { if value.Alias.Kind != MappingNode {
return fmt.Errorf("can only use merge anchors with maps (!!map), but got %v", value.Alias.Tag) return fmt.Errorf("can only use merge anchors with maps (!!map), but got %v", value.Alias.Tag)
} }
// candidateNode := originalCandidate.CreateReplacement(value.Alias)
return doTraverseMap(newMatches, value.Alias, wantedKey, prefs, splat) return doTraverseMap(newMatches, value.Alias, wantedKey, prefs, splat)
case SequenceNode: case SequenceNode:
for _, childValue := range value.Content { for _, childValue := range value.Content {
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, prefs, splat) err := traverseMergeAnchor(newMatches, childValue, wantedKey, prefs, splat)
if err != nil { if err != nil {
return err return err
} }

View File

@ -51,7 +51,7 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN
} }
resultNode := candidate.CreateReplacementWithDocWrappers(SequenceNode, "!!seq", candidateNode.Style) resultNode := candidate.CreateReplacementWithDocWrappers(SequenceNode, "!!seq", candidateNode.Style)
for el := newMatches.Front(); el != nil; el = el.Next() { for el := newMatches.Front(); el != nil; el = el.Next() {
resultNode.Content = append(resultNode.Content, el.Value.(*CandidateNode)) resultNode.AddChild(el.Value.(*CandidateNode))
} }
results.PushBack(resultNode) results.PushBack(resultNode)

View File

@ -6,7 +6,6 @@ import (
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
"gopkg.in/yaml.v3"
) )
type operatorHandler func(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) type operatorHandler func(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error)
@ -52,13 +51,6 @@ func compoundAssignFunction(d *dataTreeNavigator, context Context, expressionNod
return context, nil return context, nil
} }
func unwrapDoc(node *yaml.Node) *yaml.Node {
if node.Kind == yaml.DocumentNode {
return node.Content[0]
}
return node
}
func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
context.MatchingNodes = list.New() context.MatchingNodes = list.New()
return context, nil return context, nil

View File

@ -31,7 +31,7 @@ type expressionScenario struct {
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
logging.SetLevel(logging.ERROR, "") logging.SetLevel(logging.DEBUG, "")
Now = func() time.Time { Now = func() time.Time {
return time.Date(2021, time.May, 19, 1, 2, 3, 4, time.UTC) return time.Date(2021, time.May, 19, 1, 2, 3, 4, time.UTC)
} }

View File

@ -1,14 +0,0 @@
package yqlib
// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
// order of keys and values in a map or an object.
type orderedMap struct {
// if this is an object, kv != nil. If this is not an object, kv == nil.
kv []orderedMapKV
altVal interface{}
}
type orderedMapKV struct {
K string
V orderedMap
}

View File

@ -1,83 +0,0 @@
package yqlib
import (
"bytes"
"encoding/json"
"errors"
"io"
)
func (o *orderedMap) UnmarshalJSON(data []byte) error {
switch data[0] {
case '{':
// initialise so that even if the object is empty it is not nil
o.kv = []orderedMapKV{}
// create decoder
dec := json.NewDecoder(bytes.NewReader(data))
_, err := dec.Token() // open object
if err != nil {
return err
}
// cycle through k/v
var tok json.Token
for tok, err = dec.Token(); err == nil; tok, err = dec.Token() {
// we can expect two types: string or Delim. Delim automatically means
// that it is the closing bracket of the object, whereas string means
// that there is another key.
if _, ok := tok.(json.Delim); ok {
break
}
kv := orderedMapKV{
K: tok.(string),
}
if err := dec.Decode(&kv.V); err != nil {
return err
}
o.kv = append(o.kv, kv)
}
// unexpected error
if err != nil && !errors.Is(err, io.EOF) {
return err
}
return nil
case '[':
var res []*orderedMap
if err := json.Unmarshal(data, &res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
}
return json.Unmarshal(data, &o.altVal)
}
func (o orderedMap) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
if o.kv == nil {
if err := enc.Encode(o.altVal); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
buf.WriteByte('{')
for idx, el := range o.kv {
if err := enc.Encode(el.K); err != nil {
return nil, err
}
buf.WriteByte(':')
if err := enc.Encode(el.V); err != nil {
return nil, err
}
if idx != len(o.kv)-1 {
buf.WriteByte(',')
}
}
buf.WriteByte('}')
return buf.Bytes(), nil
}

View File

@ -1,79 +0,0 @@
package yqlib
import (
"fmt"
yaml "gopkg.in/yaml.v3"
)
func (o *orderedMap) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.DocumentNode:
if len(node.Content) == 0 {
return nil
}
return o.UnmarshalYAML(node.Content[0])
case yaml.AliasNode:
return o.UnmarshalYAML(node.Alias)
case yaml.ScalarNode:
return node.Decode(&o.altVal)
case yaml.MappingNode:
// set kv to non-nil
o.kv = []orderedMapKV{}
for i := 0; i < len(node.Content); i += 2 {
var key string
var val orderedMap
if err := node.Content[i].Decode(&key); err != nil {
return err
}
if err := node.Content[i+1].Decode(&val); err != nil {
return err
}
o.kv = append(o.kv, orderedMapKV{
K: key,
V: val,
})
}
return nil
case yaml.SequenceNode:
// note that this has to be a pointer, so that nulls can be represented.
var res []*orderedMap
if err := node.Decode(&res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
case 0:
// null
o.kv = nil
o.altVal = nil
return nil
default:
return fmt.Errorf("orderedMap: invalid yaml node")
}
}
func (o *orderedMap) MarshalYAML() (interface{}, error) {
// fast path: kv is nil, use altVal
if o.kv == nil {
return o.altVal, nil
}
content := make([]*yaml.Node, 0, len(o.kv)*2)
for _, val := range o.kv {
n := new(yaml.Node)
if err := n.Encode(val.V); err != nil {
return nil, err
}
content = append(content, &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: val.K,
}, n)
}
return &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Content: content,
}, nil
}