Skip to content

Commit 17d4e16

Browse files
committed
cue: clean up Format
Makes format more intuitive and gives bonus features to mitigate any pain this causes for backwards incompatibility. Fixes #882 Change-Id: I6390f95c4e7b27b9e0a195bd936f6aae55aadf27 Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9487 Reviewed-by: CUE cueckoo <[email protected]> Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent b937727 commit 17d4e16

File tree

11 files changed

+558
-65
lines changed

11 files changed

+558
-65
lines changed

cmd/cue/cmd/mod.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func runModInit(cmd *Command, args []string) (err error) {
9090
return fmt.Errorf("invalid module name: %v", module)
9191
}
9292
if h := u.Hostname(); !strings.Contains(h, ".") {
93-
return fmt.Errorf("invalid host name %q", h)
93+
return fmt.Errorf("invalid host name %s", h)
9494
}
9595
}
9696

cue/builtin_test.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ func TestBuiltins(t *testing.T) {
4747
`3`,
4848
}, {
4949
test("math", "math.Pi(3)"),
50-
`_|_(cannot call non-function math.Pi (type float))`,
50+
`_|_ // cannot call non-function math.Pi (type float)`,
5151
}, {
5252
test("math", "math.Floor(3, 5)"),
53-
`_|_(too many arguments in call to math.Floor (have 2, want 1))`,
53+
`_|_ // too many arguments in call to math.Floor (have 2, want 1)`,
5454
}, {
5555
test("math", `math.Floor("foo")`),
56-
`_|_(cannot use "foo" (type string) as number in argument 1 to math.Floor)`,
56+
`_|_ // cannot use "foo" (type string) as number in argument 1 to math.Floor`,
5757
}, {
5858
test("crypto/sha256", `sha256.Sum256("hash me")`),
5959
`'\xeb \x1a\xf5\xaa\xf0\xd6\x06)\xd3Ҧ\x1eFl\xfc\x0f\xed\xb5\x17\xad\xd81\xec\xacR5\xe1کc\xd6'`,
@@ -62,13 +62,13 @@ func TestBuiltins(t *testing.T) {
6262
`16`,
6363
}, {
6464
test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<3})`),
65-
`_|_(error in call to encoding/yaml.Validate: a: invalid value 4 (out of bound <3))`,
65+
`_|_ // error in call to encoding/yaml.Validate: a: invalid value 4 (out of bound <3)`,
6666
}, {
6767
test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<5})`),
6868
`true`,
6969
}, {
7070
test("encoding/yaml", `yaml.Validate("a: 2\n", {a:<5, b:int})`),
71-
`_|_(error in call to encoding/yaml.Validate: b: incomplete value int)`,
71+
`_|_ // error in call to encoding/yaml.Validate: b: incomplete value int`,
7272
}, {
7373
test("strconv", `strconv.FormatUint(64, 16)`),
7474
`"40"`,
@@ -77,7 +77,7 @@ func TestBuiltins(t *testing.T) {
7777
`"foo"`,
7878
}, {
7979
test("regexp", `regexp.Find(#"f\w\w"#, "bar")`),
80-
`_|_(error in call to regexp.Find: no match)`,
80+
`_|_ // error in call to regexp.Find: no match`,
8181
}, {
8282
testExpr(`len([1, 2, 3])`),
8383
`3`,
@@ -86,33 +86,38 @@ func TestBuiltins(t *testing.T) {
8686
`3`,
8787
}, {
8888
test("encoding/json", `json.MarshalStream([{a: 1}, {b: 2}])`),
89-
`"{\"a\":1}\n{\"b\":2}\n"`,
89+
`"""` + "\n\t{\"a\":1}\n\t{\"b\":2}\n\t\n\t" + `"""`,
9090
}, {
9191
test("encoding/json", `{
9292
x: int
9393
y: json.Marshal({a: x})
9494
}`),
95-
`{x:int,y:_|_(cannot convert incomplete value "int" to JSON)}`,
95+
`{
96+
x: int
97+
y: _|_ // cannot convert incomplete value "int" to JSON
98+
}`,
9699
}, {
97100
test("encoding/yaml", `yaml.MarshalStream([{a: 1}, {b: 2}])`),
98-
`"a: 1\n---\nb: 2\n"`,
101+
`"""` + "\n\ta: 1\n\t---\n\tb: 2\n\t\n\t" + `"""`,
99102
}, {
100103
test("struct", `struct.MinFields(0) & ""`),
101-
`_|_(conflicting values struct.MinFields(0) and "" (mismatched types struct and string))`,
104+
`_|_ // conflicting values struct.MinFields(0) and "" (mismatched types struct and string)`,
102105
}, {
103106
test("struct", `struct.MinFields(0) & {a: 1}`),
104-
`{a:1}`,
107+
`{
108+
a: 1
109+
}`,
105110
}, {
106111
test("struct", `struct.MinFields(2) & {a: 1}`),
107112
// TODO: original value may be better.
108-
// `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)))`,
109-
`_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (1 < 2))`,
113+
// `_|_ // invalid value {a:1} (does not satisfy struct.MinFields(2))`,
114+
`_|_ // invalid value {a:1} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (1 < 2)`,
110115
}, {
111116
test("time", `time.Time & "1937-01-01T12:00:27.87+00:20"`),
112117
`"1937-01-01T12:00:27.87+00:20"`,
113118
}, {
114119
test("time", `time.Time & "no time"`),
115-
`_|_(invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time")`,
120+
`_|_ // invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time"`,
116121
}, {
117122
test("time", `time.Unix(1500000000, 123456)`),
118123
`"2017-07-14T02:40:00.000123456Z"`,
@@ -146,7 +151,7 @@ func TestSingleBuiltin(t *testing.T) {
146151
emit string
147152
}{{
148153
test("list", `list.Sort([{a:1}, {b:2}], list.Ascending)`),
149-
`_|_(error in call to list.Sort: less: invalid operands {b:2} and {a:1} to '<' (type struct and struct))`,
154+
`_|_ // error in call to list.Sort: less: invalid operands {b:2} and {a:1} to '<' (type struct and struct)`,
150155
}}
151156
for i, tc := range testCases {
152157
t.Run(fmt.Sprint(i), func(t *testing.T) {

cue/examplecompile_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ func ExampleContext() {
3131
`)
3232

3333
p("lookups")
34-
p("a: %+v", v.LookupPath(cue.ParsePath("a")))
35-
p("b: %+v", v.LookupPath(cue.ParsePath("b")))
36-
p(`"a+b": %+v`, v.LookupPath(cue.ParsePath(`"a+b"`)))
34+
p("a: %v", v.LookupPath(cue.ParsePath("a")))
35+
p("b: %v", v.LookupPath(cue.ParsePath("b")))
36+
p(`"a+b": %v`, v.LookupPath(cue.ParsePath(`"a+b"`)))
3737
p("")
3838
p("expressions")
39-
p("a + b: %+v", ctx.CompileString("a + b", cue.Scope(v)))
40-
p("a * b: %+v", ctx.CompileString("a * b", cue.Scope(v)))
39+
p("a + b: %v", ctx.CompileString("a + b", cue.Scope(v)))
40+
p("a * b: %v", ctx.CompileString("a * b", cue.Scope(v)))
4141

4242
// Output:
4343
// lookups

cue/format.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Copyright 2021 CUE Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cue
16+
17+
import (
18+
"bytes"
19+
"fmt"
20+
"math/big"
21+
22+
"cuelang.org/go/cue/ast"
23+
"cuelang.org/go/cue/format"
24+
"cuelang.org/go/internal/core/export"
25+
)
26+
27+
// TODO:
28+
// * allow '-' to strip outer curly braces?
29+
// - simplify output; can be used in combination with other flags
30+
// * advertise:
31+
// c like v, but print comments
32+
// a like c, but print attributes and package-local hidden fields as well
33+
34+
// Format prints a CUE value.
35+
//
36+
// WARNING:
37+
// although we are narrowing down the semantics, the verbs and options
38+
// are still subject to change. this API is experimental although it is
39+
// likely getting close to the final design.
40+
//
41+
// It recognizes the following verbs:
42+
//
43+
// v print CUE value
44+
//
45+
// The verbs support the following flags:
46+
// # print as schema and include definitions.
47+
// The result is printed as a self-contained file, instead of an the
48+
// expression format.
49+
// + evaluate: resolve defaults and error on incomplete errors
50+
//
51+
// Indentation can be controlled as follows:
52+
// width indent the cue block by <width> tab stops (e.g. %2v)
53+
// precision convert tabs to <precision> spaces (e.g. %.2v), where
54+
// a value of 0 means no indentation or newlines (TODO).
55+
//
56+
// If the value kind corresponds to one of the following Go types, the
57+
// usual Go formatting verbs for that type can be used:
58+
//
59+
// Int: b,d,o,O,q,x,X
60+
// Float: f,e,E,g,G
61+
// String/Bytes: s,q,x,X
62+
//
63+
// The %v directive will be used if the type is not supported for that verb.
64+
//
65+
func (v Value) Format(state fmt.State, verb rune) {
66+
if v.v == nil {
67+
fmt.Fprint(state, "<nil>")
68+
return
69+
}
70+
71+
switch verb {
72+
case 'a':
73+
formatCUE(state, v, true, true)
74+
case 'c':
75+
formatCUE(state, v, true, false)
76+
case 'v':
77+
formatCUE(state, v, false, false)
78+
79+
case 'd', 'o', 'O', 'U':
80+
var i big.Int
81+
if _, err := v.Int(&i); err != nil {
82+
formatCUE(state, v, false, false)
83+
return
84+
}
85+
i.Format(state, verb)
86+
87+
case 'f', 'e', 'E', 'g', 'G':
88+
d, err := v.Decimal()
89+
if err != nil {
90+
formatCUE(state, v, false, false)
91+
return
92+
}
93+
d.Format(state, verb)
94+
95+
case 's', 'q':
96+
// TODO: this drops other formatting directives
97+
msg := "%s"
98+
if verb == 'q' {
99+
msg = "%q"
100+
}
101+
102+
if b, err := v.Bytes(); err == nil {
103+
fmt.Fprintf(state, msg, b)
104+
} else {
105+
s := fmt.Sprintf("%+v", v)
106+
fmt.Fprintf(state, msg, s)
107+
}
108+
109+
case 'x', 'X':
110+
switch v.Kind() {
111+
case StringKind, BytesKind:
112+
b, _ := v.Bytes()
113+
// TODO: this drops other formatting directives
114+
msg := "%x"
115+
if verb == 'X' {
116+
msg = "%X"
117+
}
118+
fmt.Fprintf(state, msg, b)
119+
120+
case IntKind, NumberKind:
121+
var i big.Int
122+
_, _ = v.Int(&i)
123+
i.Format(state, verb)
124+
125+
case FloatKind:
126+
dec, _ := v.Decimal()
127+
dec.Format(state, verb)
128+
129+
default:
130+
formatCUE(state, v, false, false)
131+
}
132+
133+
default:
134+
formatCUE(state, v, false, false)
135+
}
136+
}
137+
138+
func formatCUE(state fmt.State, v Value, showDocs, showAll bool) {
139+
140+
pkgPath := v.instance().ID()
141+
142+
p := *export.Simplified
143+
144+
isDef := false
145+
switch {
146+
case state.Flag('#'):
147+
isDef = true
148+
p = export.Profile{
149+
ShowOptional: true,
150+
ShowDefinitions: true,
151+
ShowHidden: true,
152+
}
153+
154+
case state.Flag('+'):
155+
p = *export.Final
156+
fallthrough
157+
158+
default:
159+
p.ShowHidden = showAll
160+
}
161+
162+
p.ShowDocs = showDocs
163+
p.ShowAttributes = showAll
164+
165+
var n ast.Node
166+
if isDef {
167+
n, _ = p.Def(v.idx, pkgPath, v.v)
168+
} else {
169+
n, _ = p.Value(v.idx, pkgPath, v.v)
170+
}
171+
172+
formatExpr(state, n)
173+
}
174+
175+
func formatExpr(state fmt.State, n ast.Node) {
176+
opts := make([]format.Option, 0, 3)
177+
if state.Flag('-') {
178+
opts = append(opts, format.Simplify())
179+
}
180+
// TODO: handle verbs to allow formatting based on type:
181+
if width, ok := state.Width(); ok {
182+
opts = append(opts, format.IndentPrefix(width))
183+
}
184+
// TODO: consider this: should tabs or spaces be the default?
185+
if tabwidth, ok := state.Precision(); ok {
186+
// TODO: 0 means no newlines.
187+
opts = append(opts,
188+
format.UseSpaces(tabwidth),
189+
format.TabIndent(false))
190+
}
191+
// TODO: consider this.
192+
// else if state.Flag(' ') {
193+
// opts = append(opts,
194+
// format.UseSpaces(4),
195+
// format.TabIndent(false))
196+
// }
197+
198+
b, _ := format.Node(n, opts...)
199+
b = bytes.Trim(b, "\n\r")
200+
_, _ = state.Write(b)
201+
}

0 commit comments

Comments
 (0)