Skip to content

Commit a83c6c6

Browse files
committed
encoding/yaml: quote strings more aggressively
Rather than always quoting strings, quote any string that matches any of the regexpes in the tag repository. The algorithm is rather aggressive with matching and matches a superset of these tags. This implementation is an alternative to the suggestion in Issue #1368. If this is not sufficient, we can still address that. Fixes #1076 Issue #1368 Signed-off-by: Marcel van Lohuizen <mpvl@golang.org> Change-Id: I03e4b820a0f90b594feffce8a0bccbb3ffda35e1 Signed-off-by: Marcel van Lohuizen <mpvl@golang.org> Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/527375 Unity-Result: CUEcueckoo <cueckoo@cuelang.org> TryBot-Result: CUEcueckoo <cueckoo@cuelang.org> Reviewed-by: Paul Jolly <paul@myitcv.io>
1 parent b9ac1ea commit a83c6c6

File tree

9 files changed

+184
-13
lines changed

9 files changed

+184
-13
lines changed

.github/workflows/new_version_triggers.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Generated by internal/ci/ci_tool.cue; do not edit
22

33
name: New release triggers
4-
on:
4+
"on":
55
push:
66
tags:
77
- v*

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Generated by internal/ci/ci_tool.cue; do not edit
22

33
name: Release
4-
on:
4+
"on":
55
push:
66
tags:
77
- v*

.github/workflows/repository_dispatch.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Generated by internal/ci/ci_tool.cue; do not edit
22

33
name: Repository Dispatch
4-
on:
4+
"on":
55
- repository_dispatch
66
jobs:
77
runtrybot:

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Generated by internal/ci/ci_tool.cue; do not edit
22

33
name: Test
4-
on:
4+
"on":
55
push:
66
branches:
77
- '**'

.github/workflows/tip_triggers.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Generated by internal/ci/ci_tool.cue; do not edit
22

33
name: Push to tip triggers
4-
on:
4+
"on":
55
push:
66
branches:
77
- master

cmd/cue/cmd/testdata/script/cmd_github.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
# Generated by internal/ci/ci_tool.cue; do not edit
5252

5353
name: New release triggers
54-
on:
54+
"on":
5555
push:
5656
tags:
5757
- v*
@@ -74,7 +74,7 @@ jobs:
7474
# Generated by internal/ci/ci_tool.cue; do not edit
7575

7676
name: Release
77-
on:
77+
"on":
7878
push:
7979
tags:
8080
- v*
@@ -115,7 +115,7 @@ jobs:
115115
# Generated by internal/ci/ci_tool.cue; do not edit
116116

117117
name: Repository Dispatch
118-
on:
118+
"on":
119119
- repository_dispatch
120120
jobs:
121121
runtrybot:
@@ -148,7 +148,7 @@ jobs:
148148
# Generated by internal/ci/ci_tool.cue; do not edit
149149

150150
name: Test
151-
on:
151+
"on":
152152
push:
153153
branches:
154154
- '**'
@@ -290,7 +290,7 @@ jobs:
290290
# Generated by internal/ci/ci_tool.cue; do not edit
291291

292292
name: Push to tip triggers
293-
on:
293+
"on":
294294
push:
295295
branches:
296296
- master

encoding/yaml/yaml_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"cuelang.org/go/cue"
2222
"cuelang.org/go/cue/ast"
23+
"cuelang.org/go/cue/cuecontext"
2324
"cuelang.org/go/cue/format"
2425
)
2526

@@ -149,3 +150,98 @@ null
149150
})
150151
}
151152
}
153+
154+
func TestYAMLValues(t *testing.T) {
155+
testCases := []struct {
156+
cue string
157+
yaml string
158+
}{
159+
// strings
160+
{`"""
161+
single
162+
"""`, "single"}, // TODO: CUE simplifies this.
163+
164+
{`"""
165+
aaaa
166+
bbbb
167+
"""`, `|-
168+
aaaa
169+
bbbb`},
170+
171+
// keep as is
172+
{`"non"`, `non`},
173+
174+
// Non-strings in v1.2. These are single-quoted by the go-yaml.v3 package.
175+
{`"#cloudmon"`, `'#cloudmon'`},
176+
177+
// Strings that mimic numeric values are double quoted by the go-yaml.v3
178+
// package.
179+
{`".inf"`, `".inf"`},
180+
{`".Inf"`, `".Inf"`},
181+
{`".INF"`, `".INF"`},
182+
{`".NaN"`, `".NaN"`},
183+
{`"+.Inf"`, `"+.Inf"`},
184+
{`"-.Inf"`, `"-.Inf"`},
185+
{`"2002"`, `"2002"`},
186+
{`"685_230.15"`, `"685_230.15"`},
187+
188+
// Legacy values.format.
189+
{`"no"`, `"no"`},
190+
{`"on"`, `"on"`},
191+
{`".Nan"`, `".Nan"`},
192+
193+
// binary
194+
{`'no'`, `!!binary bm8=`},
195+
196+
// floats
197+
{`.2`, "0.2"},
198+
{`2.`, "2."},
199+
{`".inf"`, `".inf"`},
200+
{`685_230.15`, `685230.15`},
201+
202+
// Date and time-like
203+
{`"2001-12-15T02:59:43.1Z"`, `"2001-12-15T02:59:43.1Z"`},
204+
{`"2001-12-14t21:59:43.10-05:00"`, `"2001-12-14t21:59:43.10-05:00"`},
205+
{`"2001-12-14 21:59:43.10 -5"`, `"2001-12-14 21:59:43.10 -5"`},
206+
{`"2001-12-15 2:59:43.10"`, `"2001-12-15 2:59:43.10"`},
207+
{`"2002-12-14"`, `"2002-12-14"`},
208+
{`"12-12-12"`, `"12-12-12"`},
209+
210+
// legacy base60 floats
211+
{`"2222:22"`, `"2222:22"`},
212+
213+
// hostport
214+
{`"hostname:22"`, `hostname:22`},
215+
216+
// maps
217+
{
218+
cue: `
219+
true: 1
220+
True: 2
221+
".Nan": 3
222+
".Inf": 4
223+
y: 5
224+
`,
225+
yaml: `"true": 1
226+
"True": 2
227+
".Nan": 3
228+
".Inf": 4
229+
"y": 5`,
230+
},
231+
}
232+
for _, tc := range testCases {
233+
t.Run(tc.cue, func(t *testing.T) {
234+
c := cuecontext.New()
235+
v := c.CompileString(tc.cue)
236+
237+
b, err := Encode(v)
238+
if err != nil {
239+
t.Error(err)
240+
}
241+
if got := strings.TrimSpace(string(b)); got != tc.yaml {
242+
t.Errorf("Encode:\ngot %q\nwant %q", got, tc.yaml)
243+
}
244+
245+
})
246+
}
247+
}

internal/encoding/yaml/encode.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ package yaml
1616

1717
import (
1818
"bytes"
19+
"encoding/base64"
20+
"fmt"
1921
"math/big"
22+
"regexp"
2023
"strings"
2124

2225
"gopkg.in/yaml.v3"
@@ -101,6 +104,8 @@ func encode(n ast.Node) (y *yaml.Node, err error) {
101104
func encodeScalar(b *ast.BasicLit) (n *yaml.Node, err error) {
102105
n = &yaml.Node{Kind: yaml.ScalarNode}
103106

107+
// TODO: use cue.Value and support attributes for setting YAML tags.
108+
104109
switch b.Kind {
105110
case token.INT:
106111
var x big.Int
@@ -118,18 +123,85 @@ func encodeScalar(b *ast.BasicLit) (n *yaml.Node, err error) {
118123
n.Value = b.Value
119124

120125
case token.STRING:
121-
str, err := literal.Unquote(b.Value)
126+
info, nStart, _, err := literal.ParseQuotes(b.Value, b.Value)
122127
if err != nil {
123128
return nil, err
124129
}
130+
str, err := info.Unquote(b.Value[nStart:])
131+
if err != nil {
132+
panic(fmt.Sprintf("invalid string: %v", err))
133+
}
125134
n.SetString(str)
126135

136+
switch {
137+
case !info.IsDouble():
138+
n.Tag = "!!binary"
139+
n.Value = base64.StdEncoding.EncodeToString([]byte(str))
140+
141+
case info.IsMulti():
142+
// Preserve multi-line format.
143+
n.Style = yaml.LiteralStyle
144+
145+
default:
146+
if shouldQuote(str) {
147+
n.Style = yaml.DoubleQuotedStyle
148+
}
149+
}
150+
127151
default:
128152
return nil, errors.Newf(b.Pos(), "unknown literal type %v", b.Kind)
129153
}
130154
return n, nil
131155
}
132156

157+
// shouldQuote indicates that a string may be a YAML 1.1. legacy value and that
158+
// the string should be quoted.
159+
func shouldQuote(str string) bool {
160+
return legacyStrings[str] || useQuote.MatchString(str)
161+
}
162+
163+
// This regular expression conservatively matches any date, time string,
164+
// or base60 float.
165+
var useQuote = regexp.MustCompile(`^[\-+0-9:\. \t]+([-:]|[tT])[\-+0-9:\. \t]+[zZ]?$`)
166+
167+
// legacyStrings contains a map of fixed strings with special meaning for any
168+
// type in the YAML Tag registry (https://yaml.org/type/index.html) as used
169+
// in YAML 1.1.
170+
//
171+
// These strings are always quoted upon export to allow for backward
172+
// compatibility with YAML 1.1 parsers.
173+
var legacyStrings = map[string]bool{
174+
"y": true,
175+
"Y": true,
176+
"yes": true,
177+
"Yes": true,
178+
"YES": true,
179+
"n": true,
180+
"N": true,
181+
"t": true,
182+
"T": true,
183+
"f": true,
184+
"F": true,
185+
"no": true,
186+
"No": true,
187+
"NO": true,
188+
"true": true,
189+
"True": true,
190+
"TRUE": true,
191+
"false": true,
192+
"False": true,
193+
"FALSE": true,
194+
"on": true,
195+
"On": true,
196+
"ON": true,
197+
"off": true,
198+
"Off": true,
199+
"OFF": true,
200+
201+
// Non-standard.
202+
".Nan": true,
203+
}
204+
133205
func setNum(n *yaml.Node, s string, x interface{}) error {
134206
if yaml.Unmarshal([]byte(s), x) == nil {
135207
n.Value = s
@@ -204,6 +276,9 @@ func encodeDecls(decls []ast.Decl) (n *yaml.Node, err error) {
204276
label := &yaml.Node{}
205277
addDocs(x.Label, label, label)
206278
label.SetString(name)
279+
if shouldQuote(name) {
280+
label.Style = yaml.DoubleQuotedStyle
281+
}
207282

208283
value, err := encode(x.Value)
209284
if err != nil {

internal/encoding/yaml/encode_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ a:
6161
c: 3
6262
b:
6363
x: 0
64-
y: 1
64+
"y": 1
6565
z: 2
6666
`,
6767
}, {
@@ -89,7 +89,7 @@ hex: 0x11
8989
dec: .3
9090
dat: !!binary gA==
9191
nil: null
92-
yes: true
92+
"yes": true
9393
non: false
9494
`,
9595
}, {

0 commit comments

Comments
 (0)