Skip to content

Commit 306dc93

Browse files
committed
Fixing TOML ArrayTable parsing issues #1758
1 parent f00852b commit 306dc93

File tree

5 files changed

+160
-46
lines changed

5 files changed

+160
-46
lines changed

examples/data1.yaml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
# 001
2-
---
3-
abc: # 001
4-
- 1 # one
5-
- 2 # two
6-
7-
---
8-
def # 002
1+
[[fruits]]
2+
[[fruits.varieties]] # nested array of tables
3+
name = "red delicious

examples/sample.toml

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,6 @@
1+
[[fruits]]
12

3+
[animals]
24

3-
# This is a TOML document
4-
5-
title = "TOML Example"
6-
7-
[owner]
8-
name = "Tom Preston-Werner"
9-
dob = 1979-05-27T07:32:00-08:00
10-
11-
[database]
12-
enabled = true
13-
ports = [ 8000, 8001, 8002 ]
14-
data = [ ["delta", "phi"], [3.14] ]
15-
temp_targets = { cpu = 79.5, case = 72.0 }
16-
17-
[servers]
18-
19-
[servers.alpha]
20-
ip = "10.0.0.1"
21-
role = "frontend"
22-
23-
[servers.beta]
24-
ip = "10.0.0.2"
25-
role = "backend"
26-
5+
[[fruits.varieties]] # nested array of tables
6+
name = "red delicious"

pkg/yqlib/decoder_toml.go

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -324,32 +324,58 @@ func (dec *tomlDecoder) arrayAppend(context Context, path []interface{}, rhsNode
324324
}
325325

326326
func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error) {
327-
log.Debug("Entering processArrayTable")
327+
log.Debug("c")
328328
fullPath := dec.getFullPath(currentNode.Child())
329329
log.Debug("Fullpath: %v", fullPath)
330330

331+
c := Context{}
332+
c = c.SingleChildContext(dec.rootMap)
333+
334+
pathToCheck := fullPath
335+
if len(fullPath) >= 1 {
336+
pathToCheck = fullPath[:len(fullPath)-1]
337+
}
338+
339+
// if fullPath points to an array of maps rather than a map
340+
// then it should set this element into the _last_ element of that array.
341+
// Because TOML. So we'll inject the last index into the path.
342+
readOp := createTraversalTree(pathToCheck, traversePreferences{DontAutoCreate: true}, false)
343+
344+
resultContext, err := dec.d.GetMatchingNodes(c, readOp)
345+
if err != nil {
346+
return false, err
347+
}
348+
if resultContext.MatchingNodes.Len() >= 1 {
349+
match := resultContext.MatchingNodes.Front().Value.(*CandidateNode)
350+
// path refers to an array, we need to add this to the last element in the array
351+
if match.Kind == SequenceNode {
352+
fullPath = append(pathToCheck, len(match.Content)-1, fullPath[len(fullPath)-1])
353+
log.Debugf("Adding to end of %v array, using path: %v", pathToCheck, fullPath)
354+
}
355+
}
356+
331357
// need to use the array append exp to add another entry to
332358
// this array: fullpath += [ thing ]
333-
334359
hasValue := dec.parser.NextExpression()
335-
if !hasValue {
336-
return false, fmt.Errorf("error retrieving table %v value: %w", fullPath, dec.parser.Error())
337-
}
338360

339361
tableNodeValue := &CandidateNode{
340362
Kind: MappingNode,
341363
Tag: "!!map",
342364
}
343-
344-
tableValue := dec.parser.Expression()
345-
runAgainstCurrentExp, err := dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
346-
log.Debugf("table node err: %w", err)
347-
if err != nil && !errors.Is(err, io.EOF) {
348-
return false, err
365+
runAgainstCurrentExp := false
366+
// if the next value is a ArrayTable or Table, then its not part of this declaration (not a key value pair)
367+
// so lets leave that expression for the next round of parsing
368+
if hasValue && (dec.parser.Expression().Kind == toml.ArrayTable || dec.parser.Expression().Kind == toml.Table) {
369+
runAgainstCurrentExp = true
370+
} else if hasValue {
371+
// otherwise, if there is a value, it must be some key value pairs of the
372+
// first object in the array!
373+
tableValue := dec.parser.Expression()
374+
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
375+
if err != nil && !errors.Is(err, io.EOF) {
376+
return false, err
377+
}
349378
}
350-
c := Context{}
351-
352-
c = c.SingleChildContext(dec.rootMap)
353379

354380
// += function
355381
err = dec.arrayAppend(c, fullPath, tableNodeValue)

pkg/yqlib/doc/usage/toml.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,27 @@ owner:
104104
suburb: nice
105105
```
106106
107+
## Parse: Array of Array Table
108+
Given a sample.toml file of:
109+
```toml
110+
111+
[[fruits]]
112+
name = "apple"
113+
[[fruits.varieties]] # nested array of tables
114+
name = "red delicious"
115+
```
116+
then
117+
```bash
118+
yq -oy '.' sample.toml
119+
```
120+
will output
121+
```yaml
122+
fruits:
123+
- name: apple
124+
varieties:
125+
- name: red delicious
126+
```
127+
107128
## Parse: Empty Table
108129
Given a sample.toml file of:
109130
```toml

pkg/yqlib/toml_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,64 @@ owner:
3737
age: 36
3838
`
3939

40+
var doubleArrayTable = `
41+
[[fruits]]
42+
name = "apple"
43+
[[fruits.varieties]] # nested array of tables
44+
name = "red delicious"`
45+
46+
var doubleArrayTableExpected = `fruits:
47+
- name: apple
48+
varieties:
49+
- name: red delicious
50+
`
51+
52+
var doubleArrayTableMultipleEntries = `
53+
[[fruits]]
54+
name = "banana"
55+
[[fruits]]
56+
name = "apple"
57+
[[fruits.varieties]] # nested array of tables
58+
name = "red delicious"`
59+
60+
var doubleArrayTableMultipleEntriesExpected = `fruits:
61+
- name: banana
62+
- name: apple
63+
varieties:
64+
- name: red delicious
65+
`
66+
67+
var doubleArrayTableNothingAbove = `
68+
[[fruits.varieties]] # nested array of tables
69+
name = "red delicious"`
70+
71+
var doubleArrayTableNothingAboveExpected = `fruits:
72+
varieties:
73+
- name: red delicious
74+
`
75+
76+
var doubleArrayTableEmptyAbove = `
77+
[[fruits]]
78+
[[fruits.varieties]] # nested array of tables
79+
name = "red delicious"`
80+
81+
var doubleArrayTableEmptyAboveExpected = `fruits:
82+
- varieties:
83+
- name: red delicious
84+
`
85+
86+
var emptyArrayTableThenTable = `
87+
[[fruits]]
88+
[animals]
89+
[[fruits.varieties]] # nested array of tables
90+
name = "red delicious"`
91+
92+
var emptyArrayTableThenTableExpected = `fruits:
93+
- varieties:
94+
- name: red delicious
95+
animals: {}
96+
`
97+
4098
var sampleArrayTable = `
4199
[owner.contact]
42100
name = "Tom Preston-Werner"
@@ -249,6 +307,40 @@ var tomlScenarios = []formatScenario{
249307
expected: sampleArrayTableExpected,
250308
scenarioType: "decode",
251309
},
310+
{
311+
description: "Parse: Array of Array Table",
312+
input: doubleArrayTable,
313+
expected: doubleArrayTableExpected,
314+
scenarioType: "decode",
315+
},
316+
{
317+
skipDoc: true,
318+
description: "Parse: Array of Array Table; nothing above",
319+
input: doubleArrayTableNothingAbove,
320+
expected: doubleArrayTableNothingAboveExpected,
321+
scenarioType: "decode",
322+
},
323+
{
324+
skipDoc: true,
325+
description: "Parse: Array of Array Table; empty above",
326+
input: doubleArrayTableEmptyAbove,
327+
expected: doubleArrayTableEmptyAboveExpected,
328+
scenarioType: "decode",
329+
},
330+
{
331+
skipDoc: true,
332+
description: "Parse: Array of Array Table; multiple entries",
333+
input: doubleArrayTableMultipleEntries,
334+
expected: doubleArrayTableMultipleEntriesExpected,
335+
scenarioType: "decode",
336+
},
337+
{
338+
skipDoc: true,
339+
description: "Parse: Array of Array Table; then table; then array table",
340+
input: emptyArrayTableThenTable,
341+
expected: emptyArrayTableThenTableExpected,
342+
scenarioType: "decode",
343+
},
252344
{
253345
description: "Parse: Empty Table",
254346
input: emptyTable,

0 commit comments

Comments
 (0)