diff --git a/cmd/cue/cmd/mod.go b/cmd/cue/cmd/mod.go index ff95eb18b..833ad0f4e 100644 --- a/cmd/cue/cmd/mod.go +++ b/cmd/cue/cmd/mod.go @@ -90,7 +90,7 @@ func runModInit(cmd *Command, args []string) (err error) { return fmt.Errorf("invalid module name: %v", module) } if h := u.Hostname(); !strings.Contains(h, ".") { - return fmt.Errorf("invalid host name %q", h) + return fmt.Errorf("invalid host name %s", h) } } diff --git a/cue/builtin_test.go b/cue/builtin_test.go index 20d92c871..e8b72d54f 100644 --- a/cue/builtin_test.go +++ b/cue/builtin_test.go @@ -47,13 +47,13 @@ func TestBuiltins(t *testing.T) { `3`, }, { test("math", "math.Pi(3)"), - `_|_(cannot call non-function math.Pi (type float))`, + `_|_ // cannot call non-function math.Pi (type float)`, }, { test("math", "math.Floor(3, 5)"), - `_|_(too many arguments in call to math.Floor (have 2, want 1))`, + `_|_ // too many arguments in call to math.Floor (have 2, want 1)`, }, { test("math", `math.Floor("foo")`), - `_|_(cannot use "foo" (type string) as number in argument 1 to math.Floor)`, + `_|_ // cannot use "foo" (type string) as number in argument 1 to math.Floor`, }, { test("crypto/sha256", `sha256.Sum256("hash me")`), `'\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) { `16`, }, { test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<3})`), - `_|_(error in call to encoding/yaml.Validate: a: invalid value 4 (out of bound <3))`, + `_|_ // error in call to encoding/yaml.Validate: a: invalid value 4 (out of bound <3)`, }, { test("encoding/yaml", `yaml.Validate("a: 2\n---\na: 4", {a:<5})`), `true`, }, { test("encoding/yaml", `yaml.Validate("a: 2\n", {a:<5, b:int})`), - `_|_(error in call to encoding/yaml.Validate: b: incomplete value int)`, + `_|_ // error in call to encoding/yaml.Validate: b: incomplete value int`, }, { test("strconv", `strconv.FormatUint(64, 16)`), `"40"`, @@ -77,7 +77,7 @@ func TestBuiltins(t *testing.T) { `"foo"`, }, { test("regexp", `regexp.Find(#"f\w\w"#, "bar")`), - `_|_(error in call to regexp.Find: no match)`, + `_|_ // error in call to regexp.Find: no match`, }, { testExpr(`len([1, 2, 3])`), `3`, @@ -86,33 +86,38 @@ func TestBuiltins(t *testing.T) { `3`, }, { test("encoding/json", `json.MarshalStream([{a: 1}, {b: 2}])`), - `"{\"a\":1}\n{\"b\":2}\n"`, + `"""` + "\n\t{\"a\":1}\n\t{\"b\":2}\n\t\n\t" + `"""`, }, { test("encoding/json", `{ x: int y: json.Marshal({a: x}) }`), - `{x:int,y:_|_(cannot convert incomplete value "int" to JSON)}`, + `{ + x: int + y: _|_ // cannot convert incomplete value "int" to JSON +}`, }, { test("encoding/yaml", `yaml.MarshalStream([{a: 1}, {b: 2}])`), - `"a: 1\n---\nb: 2\n"`, + `"""` + "\n\ta: 1\n\t---\n\tb: 2\n\t\n\t" + `"""`, }, { test("struct", `struct.MinFields(0) & ""`), - `_|_(conflicting values struct.MinFields(0) and "" (mismatched types struct and string))`, + `_|_ // conflicting values struct.MinFields(0) and "" (mismatched types struct and string)`, }, { test("struct", `struct.MinFields(0) & {a: 1}`), - `{a:1}`, + `{ + a: 1 +}`, }, { test("struct", `struct.MinFields(2) & {a: 1}`), // TODO: original value may be better. - // `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)))`, - `_|_(invalid value {a:1} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (1 < 2))`, + // `_|_ // invalid value {a:1} (does not satisfy struct.MinFields(2))`, + `_|_ // invalid value {a:1} (does not satisfy struct.MinFields(2)): len(fields) < MinFields(2) (1 < 2)`, }, { test("time", `time.Time & "1937-01-01T12:00:27.87+00:20"`), `"1937-01-01T12:00:27.87+00:20"`, }, { test("time", `time.Time & "no time"`), - `_|_(invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time")`, + `_|_ // invalid value "no time" (does not satisfy time.Time): error in call to time.Time: invalid time "no time"`, }, { test("time", `time.Unix(1500000000, 123456)`), `"2017-07-14T02:40:00.000123456Z"`, @@ -146,7 +151,7 @@ func TestSingleBuiltin(t *testing.T) { emit string }{{ test("list", `list.Sort([{a:1}, {b:2}], list.Ascending)`), - `_|_(error in call to list.Sort: less: invalid operands {b:2} and {a:1} to '<' (type struct and struct))`, + `_|_ // error in call to list.Sort: less: invalid operands {b:2} and {a:1} to '<' (type struct and struct)`, }} for i, tc := range testCases { t.Run(fmt.Sprint(i), func(t *testing.T) { diff --git a/cue/examplecompile_test.go b/cue/examplecompile_test.go index 6803da3c7..0172235cd 100644 --- a/cue/examplecompile_test.go +++ b/cue/examplecompile_test.go @@ -31,13 +31,13 @@ func ExampleContext() { `) p("lookups") - p("a: %+v", v.LookupPath(cue.ParsePath("a"))) - p("b: %+v", v.LookupPath(cue.ParsePath("b"))) - p(`"a+b": %+v`, v.LookupPath(cue.ParsePath(`"a+b"`))) + p("a: %v", v.LookupPath(cue.ParsePath("a"))) + p("b: %v", v.LookupPath(cue.ParsePath("b"))) + p(`"a+b": %v`, v.LookupPath(cue.ParsePath(`"a+b"`))) p("") p("expressions") - p("a + b: %+v", ctx.CompileString("a + b", cue.Scope(v))) - p("a * b: %+v", ctx.CompileString("a * b", cue.Scope(v))) + p("a + b: %v", ctx.CompileString("a + b", cue.Scope(v))) + p("a * b: %v", ctx.CompileString("a * b", cue.Scope(v))) // Output: // lookups diff --git a/cue/format.go b/cue/format.go new file mode 100644 index 000000000..707a7990e --- /dev/null +++ b/cue/format.go @@ -0,0 +1,201 @@ +// Copyright 2021 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cue + +import ( + "bytes" + "fmt" + "math/big" + + "cuelang.org/go/cue/ast" + "cuelang.org/go/cue/format" + "cuelang.org/go/internal/core/export" +) + +// TODO: +// * allow '-' to strip outer curly braces? +// - simplify output; can be used in combination with other flags +// * advertise: +// c like v, but print comments +// a like c, but print attributes and package-local hidden fields as well + +// Format prints a CUE value. +// +// WARNING: +// although we are narrowing down the semantics, the verbs and options +// are still subject to change. this API is experimental although it is +// likely getting close to the final design. +// +// It recognizes the following verbs: +// +// v print CUE value +// +// The verbs support the following flags: +// # print as schema and include definitions. +// The result is printed as a self-contained file, instead of an the +// expression format. +// + evaluate: resolve defaults and error on incomplete errors +// +// Indentation can be controlled as follows: +// width indent the cue block by tab stops (e.g. %2v) +// precision convert tabs to spaces (e.g. %.2v), where +// a value of 0 means no indentation or newlines (TODO). +// +// If the value kind corresponds to one of the following Go types, the +// usual Go formatting verbs for that type can be used: +// +// Int: b,d,o,O,q,x,X +// Float: f,e,E,g,G +// String/Bytes: s,q,x,X +// +// The %v directive will be used if the type is not supported for that verb. +// +func (v Value) Format(state fmt.State, verb rune) { + if v.v == nil { + fmt.Fprint(state, "") + return + } + + switch verb { + case 'a': + formatCUE(state, v, true, true) + case 'c': + formatCUE(state, v, true, false) + case 'v': + formatCUE(state, v, false, false) + + case 'd', 'o', 'O', 'U': + var i big.Int + if _, err := v.Int(&i); err != nil { + formatCUE(state, v, false, false) + return + } + i.Format(state, verb) + + case 'f', 'e', 'E', 'g', 'G': + d, err := v.Decimal() + if err != nil { + formatCUE(state, v, false, false) + return + } + d.Format(state, verb) + + case 's', 'q': + // TODO: this drops other formatting directives + msg := "%s" + if verb == 'q' { + msg = "%q" + } + + if b, err := v.Bytes(); err == nil { + fmt.Fprintf(state, msg, b) + } else { + s := fmt.Sprintf("%+v", v) + fmt.Fprintf(state, msg, s) + } + + case 'x', 'X': + switch v.Kind() { + case StringKind, BytesKind: + b, _ := v.Bytes() + // TODO: this drops other formatting directives + msg := "%x" + if verb == 'X' { + msg = "%X" + } + fmt.Fprintf(state, msg, b) + + case IntKind, NumberKind: + var i big.Int + _, _ = v.Int(&i) + i.Format(state, verb) + + case FloatKind: + dec, _ := v.Decimal() + dec.Format(state, verb) + + default: + formatCUE(state, v, false, false) + } + + default: + formatCUE(state, v, false, false) + } +} + +func formatCUE(state fmt.State, v Value, showDocs, showAll bool) { + + pkgPath := v.instance().ID() + + p := *export.Simplified + + isDef := false + switch { + case state.Flag('#'): + isDef = true + p = export.Profile{ + ShowOptional: true, + ShowDefinitions: true, + ShowHidden: true, + } + + case state.Flag('+'): + p = *export.Final + fallthrough + + default: + p.ShowHidden = showAll + } + + p.ShowDocs = showDocs + p.ShowAttributes = showAll + + var n ast.Node + if isDef { + n, _ = p.Def(v.idx, pkgPath, v.v) + } else { + n, _ = p.Value(v.idx, pkgPath, v.v) + } + + formatExpr(state, n) +} + +func formatExpr(state fmt.State, n ast.Node) { + opts := make([]format.Option, 0, 3) + if state.Flag('-') { + opts = append(opts, format.Simplify()) + } + // TODO: handle verbs to allow formatting based on type: + if width, ok := state.Width(); ok { + opts = append(opts, format.IndentPrefix(width)) + } + // TODO: consider this: should tabs or spaces be the default? + if tabwidth, ok := state.Precision(); ok { + // TODO: 0 means no newlines. + opts = append(opts, + format.UseSpaces(tabwidth), + format.TabIndent(false)) + } + // TODO: consider this. + // else if state.Flag(' ') { + // opts = append(opts, + // format.UseSpaces(4), + // format.TabIndent(false)) + // } + + b, _ := format.Node(n, opts...) + b = bytes.Trim(b, "\n\r") + _, _ = state.Write(b) +} diff --git a/cue/format_test.go b/cue/format_test.go new file mode 100644 index 000000000..8b02725f3 --- /dev/null +++ b/cue/format_test.go @@ -0,0 +1,307 @@ +// Copyright 2021 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cue_test + +import ( + "fmt" + "path" + "testing" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" +) + +func ExampleValue_Format() { + ctx := cuecontext.New() + + v := ctx.CompileString(` + a: 2 + b + b: *3 | int + s: "foo\nbar" + `) + + fmt.Println("### ALL") + fmt.Println(v) + fmt.Println("---") + fmt.Printf("%#v\n", v) + fmt.Println("---") + fmt.Printf("%+v\n", v) + + a := v.LookupPath(cue.ParsePath("a")) + fmt.Println("\n### INT") + fmt.Printf("%%v: %v\n", a) + fmt.Printf("%%05d: %05d\n", a) + + s := v.LookupPath(cue.ParsePath("s")) + fmt.Println("\n### STRING") + fmt.Printf("%%v: %v\n", s) + fmt.Printf("%%s: %s\n", s) + fmt.Printf("%%q: %q\n", s) + + v = ctx.CompileString(` + #Def: a: [string]: int + b: #Def + b: a: { + a: 3 + b: 3 + } + `) + b := v.LookupPath(cue.ParsePath("b.a")) + fmt.Println("\n### DEF") + fmt.Println(b) + fmt.Println("---") + // This will indicate that the result is closed by including a hidden + // definition. + fmt.Printf("%#v\n", b) + + // Output: + // ### ALL + // { + // a: 5 + // b: *3 | int + // s: """ + // foo + // bar + // """ + // } + // --- + // a: 2 + b + // b: *3 | int + // s: "foo\nbar" + // --- + // { + // a: 5 + // b: 3 + // s: """ + // foo + // bar + // """ + // } + // + // ### INT + // %v: 5 + // %05d: 00005 + // + // ### STRING + // %v: """ + // foo + // bar + // """ + // %s: foo + // bar + // %q: "foo\nbar" + // + // ### DEF + // { + // a: 3 + // b: 3 + // } + // --- + // _#def + // _#def: { + // { + // [string]: int + // } + // a: 3 + // b: 3 + // } +} + +func TestFormat(t *testing.T) { + tests := func(s ...string) (a [][2]string) { + for i := 0; i < len(s); i += 2 { + a = append(a, [2]string{s[i], s[i+1]}) + } + return a + } + testCases := []struct { + desc string + in string + out [][2]string + }{{ + desc: "int", + in: `12 + 14`, + out: tests( + "%#v", "26", + "%d", "26", + "%o", "32", + "%O", "0o32", + "%x", "1a", + "%X", "1A", + "%q", `"26"`, + "%0.3d", "026", + ), + }, { + desc: "float", + in: `12.2 + 14.4`, + out: tests( + "%#v", "26.6", + "%5f", " 26.6", + "%e", "2.66e+1", + "%08E", "02.66E+1", + "%g", "26.6", + "%3G", "26.6", + ), + }, { + desc: "strings", + in: `"string"`, + out: tests( + "%v", `"string"`, + "%s", "string", + "%x", "737472696e67", + "%X", "737472696E67", + ), + }, { + desc: "multiline string", + in: `""" + foo + bar + """`, + out: tests( + "%#v", `""" + foo + bar + """`, + "%s", "foo\nbar", + "%q", `"foo\nbar"`, + ), + }, { + desc: "multiline bytes", + in: `''' + foo + bar + '''`, + out: tests( + "%#v", `''' + foo + bar + '''`, + "%s", "foo\nbar", + "%q", `"foo\nbar"`, + ), + }, { + desc: "interpolation", + in: ` + #D: { + a: string + b: "hello \(a)" + } + d: #D + d: a: "world" + x: *1 | int + `, + out: tests( + "%v", `{ + d: { + a: "world" + b: "hello world" + } + x: *1 | int +}`, + "%#v", `#D: { + a: string + b: "hello \(a)" +} +d: #D & { + a: "world" +} +x: *1 | int`, + "%+v", `{ + d: { + a: "world" + b: "hello world" + } + x: 1 +}`, + ), + }, { + desc: "indent", + in: ` +a: { + b: """ + foo + bar + """ + c: int +}`, + out: tests( + "%v", `{ + a: { + b: """ + foo + bar + """ + c: int + } +}`, + "%3v", `{ + a: { + b: """ + foo + bar + """ + c: int + } + }`, + "%.1v", `{ + a: { + b: """ + foo + bar + """ + c: int + } +}`, + "%3.1v", `{ + a: { + b: """ + foo + bar + """ + c: int + } + }`, + ), + }, { + desc: "imports", + in: ` + import "strings" + a: strings.Contains("foo") + `, + out: tests( + "%v", `{ + a: strings.Contains("foo") +}`, + "%+v", `{ + a: strings.Contains("foo") +}`, + "%#v", `import "strings" + +a: strings.Contains("foo")`, + ), + }} + ctx := cuecontext.New() + for _, tc := range testCases { + for _, test := range tc.out { + t.Run(path.Join(tc.desc, test[0]), func(t *testing.T) { + v := ctx.CompileString(tc.in) + got := fmt.Sprintf(test[0], v) + if got != test[1] { + t.Errorf(" got: %s\nwant: %s", got, test[1]) + } + }) + } + } +} diff --git a/cue/marshal_test.go b/cue/marshal_test.go index 64775aa64..502dcc0af 100644 --- a/cue/marshal_test.go +++ b/cue/marshal_test.go @@ -148,9 +148,9 @@ func TestMarshalMultiPackage(t *testing.T) { `package test import pkg2 "example.com/foo/pkg1" - pkg1: pkg2.Object + "Hello \(pkg1)!" - "Hello \(pkg1)!"`), + pkg1: pkg2.Object`), }), `"Hello World!"`, }, { diff --git a/cue/types.go b/cue/types.go index 2629a8a31..6421c0a78 100644 --- a/cue/types.go +++ b/cue/types.go @@ -29,7 +29,6 @@ import ( "cuelang.org/go/cue/ast" "cuelang.org/go/cue/ast/astutil" "cuelang.org/go/cue/errors" - "cuelang.org/go/cue/format" "cuelang.org/go/cue/literal" "cuelang.org/go/cue/token" "cuelang.org/go/internal" @@ -1828,25 +1827,6 @@ func (v Value) Equals(other Value) bool { return adt.Equal(v.ctx(), v.v, other.v, 0) } -// Format prints a debug version of a value. -func (v Value) Format(state fmt.State, verb rune) { - ctx := v.ctx() - if v.v == nil { - fmt.Fprint(state, "") - return - } - switch { - case state.Flag('#'): - _, _ = io.WriteString(state, str(ctx, v.v)) - case state.Flag('+'): - _, _ = io.WriteString(state, ctx.Str(v.v)) - default: - n, _ := export.Raw.Expr(v.idx, v.instance().ID(), v.v) - b, _ := format.Node(n) - _, _ = state.Write(b) - } -} - func (v Value) instance() *Instance { if v.v == nil { return nil diff --git a/cue/types_test.go b/cue/types_test.go index 18d52ae48..fff8dfe42 100644 --- a/cue/types_test.go +++ b/cue/types_test.go @@ -63,7 +63,7 @@ func TestAPI(t *testing.T) { res := runSpec.Unify(v) return res }, - want: "_|_(#runSpec: field ction not allowed)", + want: "_|_ // #runSpec: field ction not allowed", }, { // Issue #567 input: ` @@ -77,7 +77,7 @@ func TestAPI(t *testing.T) { res := runSpec.Unify(v) return res }, - want: "_|_(#runSpec.action: field Foo not allowed)", + want: "_|_ // #runSpec.action: field Foo not allowed", }, { input: ` #runSpec: v: {action: foo: int} @@ -91,7 +91,7 @@ func TestAPI(t *testing.T) { res := w.Unify(v) return res }, - want: "_|_(w: field ction not allowed)", + want: "_|_ // w: field ction not allowed", }} for _, tc := range testCases { if tc.skip { @@ -819,7 +819,7 @@ v: #X for _, tc := range testCases { v := inst.Lookup(tc.ref...) - if got := fmt.Sprint(v); got != tc.raw { + if got := fmt.Sprintf("%#v", v); got != tc.raw { t.Errorf("got %v; want %v", got, tc.raw) } @@ -841,7 +841,7 @@ v: #X v = fi.Value } - if got := fmt.Sprint(v); got != tc.raw { + if got := fmt.Sprintf("%#v", v); got != tc.raw { t.Errorf("got %v; want %v", got, tc.raw) } @@ -956,22 +956,20 @@ func TestFill2(t *testing.T) { t.Fatal(err) } - got := fmt.Sprint(root.Value()) - want := `{ - #Provider: { - ID: string + got := fmt.Sprintf("%#v", root.Value()) + want := `#Provider: { + ID: string + notConcrete: bool + a: int + b: int +} +providers: { + myprovider: { + ID: "12345" notConcrete: bool a: int b: int } - providers: { - myprovider: { - ID: "12345" - notConcrete: bool - a: int - b: int - } - } }` if got != want { t.Errorf("got: %s\nwant: %s", got, want) diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go index 50a962e7c..64b275393 100644 --- a/encoding/openapi/types.go +++ b/encoding/openapi/types.go @@ -63,7 +63,7 @@ func extractFormat(v cue.Value) string { if op == cue.CallOp { v = a[0] if len(a) == 2 { - arg = fmt.Sprintf(" (%s)", a[1].Eval()) + arg = fmt.Sprintf(" (%v)", a[1].Eval()) } } if inst, ref := v.Reference(); len(ref) > 0 { diff --git a/internal/diff/diff_test.go b/internal/diff/diff_test.go index 1bfec7383..1eb31a37b 100644 --- a/internal/diff/diff_test.go +++ b/internal/diff/diff_test.go @@ -114,7 +114,7 @@ func TestDiff(t *testing.T) { `, kind: Modified, diff: ` { - ls: [2,3,4] + ls: [2, 3, 4] - "foo-bar": 2 + "foo-bar": 3 s: 4 @@ -128,7 +128,7 @@ func TestDiff(t *testing.T) { lm2: [ - 6, ] -+ la: [2,3,4] ++ la: [2, 3, 4] } `, }, { @@ -302,7 +302,9 @@ a: x: "hello" `, kind: Modified, diff: ` { -- a: {x:"hello"} +- a: { +- x: "hello" +- } } `, }, { diff --git a/pkg/net/host.go b/pkg/net/host.go index 6018da4fb..5f2f2e376 100644 --- a/pkg/net/host.go +++ b/pkg/net/host.go @@ -57,7 +57,7 @@ func JoinHostPort(host, port cue.Value) (string, error) { case cue.ListKind: ipdata := netGetIP(host) if len(ipdata) != 4 && len(ipdata) != 16 { - err = fmt.Errorf("invalid host %q", host) + err = fmt.Errorf("invalid host %s", host) } hostStr = ipdata.String() case cue.BytesKind: