Skip to content

Commit d374ff7

Browse files
committedFeb 28, 2025·
yaml: Support multi document decode/encode
Related to #1087
1 parent 89b3aab commit d374ff7

File tree

7 files changed

+108
-17
lines changed

7 files changed

+108
-17
lines changed
 

‎doc/usage.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -814,9 +814,12 @@ JSON and jq-flavoured JSON
814814
Note that `fromjson` and `tojson` use different naming conventions as they originate from jq's standard library.
815815

816816
YAML
817-
- `from_yaml` Parse YAML into jq value.
817+
- `from_yaml`/`from_yaml($opts)` Parse YAML into jq value.<br>
818+
`$opts` are:
819+
- `{multi_document: boolean}` Force multi document mode.<br>
818820
- `to_yaml`/`to_yaml($opts)` Serialize jq value into YAML. `$opts` are:
819821
- `{indent: number}` Indent depth.
822+
- `{multi_document: boolean}` Force multi document mode.<br>
820823

821824
TOML
822825
- `from_toml` Parse TOML into jq value.

‎format/format.go

+4
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,7 @@ type Pg_Heap_In struct {
408408
type Pg_BTree_In struct {
409409
Page int `doc:"First page number in file, default is 0"`
410410
}
411+
412+
type YAML_In struct {
413+
MultiDocument bool `doc:"Force multi document"`
414+
}

‎format/yaml/testdata/multi.fqtest

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
$ fq -d yaml . single.yaml
2+
[]
3+
$ fq -d yaml . multi.yaml
4+
[
5+
1,
6+
2,
7+
3
8+
]
9+
$ fq -o multi_document=true -d yaml . single.yaml
10+
[
11+
[]
12+
]
13+
$ fq -o multi_document=true -d yaml . multi.yaml
14+
[
15+
1,
16+
2,
17+
3
18+
]
19+
$ fq -o multi_document=false -d yaml . single.yaml
20+
[]
21+
$ fq -o multi_document=false -d yaml . multi.yaml
22+
[
23+
1,
24+
2,
25+
3
26+
]
27+
$ fq -r -o multi_document=true -d yaml '. as $i | to_yaml({multi_document: true}), $i == from_yaml' multi.yaml
28+
1
29+
---
30+
2
31+
---
32+
3
33+
34+
true
35+
$ fq -nr '"[]" | from_yaml({multi_document: true})'
36+
[
37+
[]
38+
]
39+
$ fq -nr '"[]" | from_yaml({multi_document: false})'
40+
[]
41+
$ fq -n '123 | to_yaml({multi_document: true})'
42+
exitcode: 5
43+
stderr:
44+
error: to_yaml cannot be applied to: number (123)

‎format/yaml/testdata/multi.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
1
2+
---
3+
2
4+
---
5+
3

‎format/yaml/testdata/single.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

‎format/yaml/testdata/trailing.fqtest

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ error: error at position 0xc: yaml: line 2: could not find expected ':'
55
$ fq -n '`{"a":123}{"b":444}` | from_yaml'
66
exitcode: 5
77
stderr:
8-
error: error at position 0x12: trialing data after top-level value
8+
error: error at position 0x12: trialing data after document

‎format/yaml/yaml.go

+49-15
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,46 @@ func init() {
2828
ProbeOrder: format.ProbeOrderTextFuzzy,
2929
Groups: []*decode.Group{format.Probe},
3030
DecodeFn: decodeYAML,
31-
Functions: []string{"_todisplay"},
31+
DefaultInArg: format.YAML_In{
32+
MultiDocument: false,
33+
},
34+
Functions: []string{"_todisplay"},
3235
})
3336
interp.RegisterFS(yamlFS)
3437
interp.RegisterFunc1("_to_yaml", toYAML)
3538
}
3639

3740
func decodeYAML(d *decode.D) any {
41+
var yi format.YAML_In
42+
d.ArgAs(&yi)
43+
3844
br := d.RawLen(d.Len())
39-
var r any
45+
46+
var vs []any
4047

4148
yd := yaml.NewDecoder(bitio.NewIOReader(br))
42-
if err := yd.Decode(&r); err != nil {
43-
d.Fatalf("%s", err)
44-
}
45-
if err := yd.Decode(new(any)); !errors.Is(err, io.EOF) {
46-
d.Fatalf("trialing data after top-level value")
49+
for {
50+
var v any
51+
err := yd.Decode(&v)
52+
if err != nil {
53+
if len(vs) == 0 {
54+
d.Fatalf("%s", err)
55+
} else if errors.Is(err, io.EOF) {
56+
break
57+
} else {
58+
d.Fatalf("trialing data after document")
59+
}
60+
}
61+
62+
vs = append(vs, v)
4763
}
4864

4965
var s scalar.Any
50-
s.Actual = gojqx.Normalize(r)
66+
if !yi.MultiDocument && len(vs) == 1 {
67+
s.Actual = gojqx.Normalize(vs[0])
68+
} else {
69+
s.Actual = gojqx.Normalize(vs)
70+
}
5171

5272
switch s.Actual.(type) {
5373
case map[string]any,
@@ -63,18 +83,32 @@ func decodeYAML(d *decode.D) any {
6383
}
6484

6585
type ToYAMLOpts struct {
66-
Indent int `default:"4"` // 4 is default for gopkg.in/yaml.v3
86+
Indent int `default:"4"` // 4 is default for gopkg.in/yaml.v3
87+
MultiDocument bool `default:"false"`
6788
}
6889

6990
func toYAML(_ *interp.Interp, c any, opts ToYAMLOpts) any {
91+
c = gojqx.Normalize(c)
92+
93+
cs, isArray := c.([]any)
94+
if opts.MultiDocument {
95+
if !isArray {
96+
return gojqx.FuncTypeError{Name: "to_yaml", V: c}
97+
}
98+
} else {
99+
cs = []any{c}
100+
}
101+
70102
b := &bytes.Buffer{}
71103
e := yaml.NewEncoder(b)
72-
// yaml.SetIndent panics if < 0
73-
if opts.Indent >= 0 {
74-
e.SetIndent(opts.Indent)
75-
}
76-
if err := e.Encode(gojqx.Normalize(c)); err != nil {
77-
return err
104+
for _, c := range cs {
105+
// yaml.SetIndent panics if < 0
106+
if opts.Indent >= 0 {
107+
e.SetIndent(opts.Indent)
108+
}
109+
if err := e.Encode(gojqx.Normalize(c)); err != nil {
110+
return err
111+
}
78112
}
79113

80114
return b.String()

0 commit comments

Comments
 (0)
Please sign in to comment.