Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
cue: clean up Format
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
mpvl committed Apr 23, 2021
1 parent b937727 commit 17d4e16
Show file tree
Hide file tree
Showing 11 changed files with 558 additions and 65 deletions.
2 changes: 1 addition & 1 deletion cmd/cue/cmd/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
35 changes: 20 additions & 15 deletions cue/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'`,
Expand All @@ -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"`,
Expand All @@ -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`,
Expand All @@ -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"`,
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 5 additions & 5 deletions cue/examplecompile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
201 changes: 201 additions & 0 deletions cue/format.go
Original file line number Diff line number Diff line change
@@ -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 <width> tab stops (e.g. %2v)
// precision convert tabs to <precision> 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, "<nil>")
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)
}
Loading

0 comments on commit 17d4e16

Please sign in to comment.