cue: support export package clause and docs
Change-Id: I5db5b8e8bc6180061334c230770a20c4cadb6949
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5092
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast_test.go b/cue/ast_test.go
index bc8acd1..bb2d77e 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -610,7 +610,7 @@
ctx := r.index().newContext()
- n, _ := export(ctx, inst.rootStruct, options{
+ n, _ := export(ctx, inst, inst.rootStruct, options{
raw: true,
})
got := internal.DebugStr(n)
diff --git a/cue/export.go b/cue/export.go
index 80d2255..4767b83 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -34,7 +34,7 @@
return !m.raw
}
-func export(ctx *context, v value, m options) (n ast.Node, imports []string) {
+func export(ctx *context, inst *Instance, v value, m options) (n ast.Node, imports []string) {
e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}, false, nil}
top, ok := v.evalPartial(ctx).(*structLit)
if ok {
@@ -49,32 +49,49 @@
}
value := e.expr(v)
- if len(e.imports) == 0 {
+ if len(e.imports) == 0 && inst == nil {
// TODO: unwrap structs?
return value, nil
}
+
+ file := &ast.File{}
+ if inst != nil {
+ if inst.Name != "" {
+ p := &ast.Package{Name: ast.NewIdent(inst.Name)}
+ file.Decls = append(file.Decls, p)
+ if m.docs {
+ for _, d := range inst.Doc() {
+ p.AddComment(d)
+ break
+ }
+ }
+ }
+ }
+
imports = make([]string, 0, len(e.imports))
for k := range e.imports {
imports = append(imports, k)
}
sort.Strings(imports)
- importDecl := &ast.ImportDecl{}
- file := &ast.File{Decls: []ast.Decl{importDecl}}
+ if len(imports) > 0 {
+ importDecl := &ast.ImportDecl{}
+ file.Decls = append(file.Decls, importDecl)
- for _, k := range imports {
- info := e.imports[k]
- ident := (*ast.Ident)(nil)
- if info.name != "" {
- ident = ast.NewIdent(info.name)
+ for _, k := range imports {
+ info := e.imports[k]
+ ident := (*ast.Ident)(nil)
+ if info.name != "" {
+ ident = ast.NewIdent(info.name)
+ }
+ if info.alias != "" {
+ file.Decls = append(file.Decls, &ast.Alias{
+ Ident: ast.NewIdent(info.alias),
+ Expr: ast.NewIdent(info.short),
+ })
+ }
+ importDecl.Specs = append(importDecl.Specs, ast.NewImport(ident, k))
}
- if info.alias != "" {
- file.Decls = append(file.Decls, &ast.Alias{
- Ident: ast.NewIdent(info.alias),
- Expr: ast.NewIdent(info.short),
- })
- }
- importDecl.Specs = append(importDecl.Specs, ast.NewImport(ident, k))
}
if obj, ok := value.(*ast.StructLit); ok {
@@ -859,6 +876,12 @@
f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
}
}
+ if p.mode.docs {
+ for _, d := range a.docs.appendDocs(nil) {
+ ast.AddComment(f, d)
+ break
+ }
+ }
obj.Elts = append(obj.Elts, f)
}
diff --git a/cue/export_test.go b/cue/export_test.go
index 2f8a27a..62f1b85 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -567,7 +567,7 @@
v := newValueRoot(ctx, n)
opts := options{raw: !tc.eval, omitOptional: tc.noOpt}
- node, _ := export(ctx, v.eval(ctx), opts)
+ node, _ := export(ctx, nil, v.eval(ctx), opts)
b, err := format.Node(node, format.Simplify())
if err != nil {
log.Fatal(err)
@@ -585,22 +585,36 @@
in, out string
opts []Option
}{{
+ eval: true,
+ opts: []Option{Docs(true)},
in: `
+ // Hello foo
+ package foo
+
import "strings"
a: strings.ContainsAny("c")
`,
out: unindent(`
+ // Hello foo
+ package foo
+
import "strings"
a: strings.ContainsAny("c")`),
}, {
+ eval: true,
in: `
+ // Drop this comment
+ package foo
+
import "time"
a: time.Time
`,
out: unindent(`
+ package foo
+
import "time"
a: time.Time`),
@@ -669,10 +683,8 @@
b: a + 100
`,
out: unindent(`
- {
- a: b - 100
- b: a + 100
- }`),
+ a: b - 100
+ b: a + 100`),
}, {
in: `A: {
[_]: B
@@ -685,22 +697,20 @@
D :: string
`,
out: unindent(`
- {
- A: {
- [string]: B
- } @protobuf(1,"test")
- B: {
- } & ({
- a: int
- } | {
- b: int
- })
- C :: {
- [D]: int
- [D]: int
- }
- D :: string
- }`),
+ A: {
+ [string]: B
+ } @protobuf(1,"test")
+ B: {
+ } & ({
+ a: int
+ } | {
+ b: int
+ })
+ C :: {
+ [D]: int
+ [D]: int
+ }
+ D :: string`),
}, {
in: `
import "time"
@@ -726,16 +736,14 @@
B :: a
`,
out: unindent(`
- {
- A :: {
- b: int
- }
- a: close({
- b: <10
- })
- B :: {
- b: <10
- }
+ A :: {
+ b: int
+ }
+ a: close({
+ b: <10
+ })
+ B :: {
+ b: <10
}`),
}, {
eval: true,
@@ -776,18 +784,16 @@
x: X
`,
out: unindent(`
- {
- T :: {
- [string]: int64
- }
- x: {
- [string]: int64
- x: int64
- }
- X :: {
- [string]: int64
- x: int64
- }
+ T :: {
+ [string]: int64
+ }
+ x: {
+ [string]: int64
+ x: int64
+ }
+ X :: {
+ [string]: int64
+ x: int64
}`),
}, {
eval: true,
@@ -802,13 +808,11 @@
x: X
`,
out: unindent(`
- {
- T :: {
- }
- x: x: int64
- X :: {
- x: int64
- }
+ T :: {
+ }
+ x: x: int64
+ X :: {
+ x: int64
}`),
}, {
eval: true,
@@ -868,11 +872,9 @@
c?: 3
c: 3`,
out: unindent(`
- {
- a?: 1
- b?: 2
- c: 3
- }`),
+ a?: 1
+ b?: 2
+ c: 3`),
}, {
eval: true,
opts: []Option{ResolveReferences(true)},
@@ -888,30 +890,51 @@
`,
// TODO: the outer close of C could be optimized away.
out: unindent(`
- {
- A :: {
+ A :: {
+ [=~"^[a-s]*$"]: int
+ }
+ B :: {
+ [=~"^[m-z]+"]: int
+ }
+ C: close({
+ close({
[=~"^[a-s]*$"]: int
- }
- B :: {
+ }) & close({
[=~"^[m-z]+"]: int
- }
- C: close({
- close({
- [=~"^[a-s]*$"]: int
- }) & close({
- [=~"^[m-z]+"]: int
- })
})
- D :: {
- close({
- [=~"^[a-s]*$"]: int
- }) & close({
- [=~"^[m-z]+"]: int
- })
- }
+ })
+ D :: {
+ close({
+ [=~"^[a-s]*$"]: int
+ }) & close({
+ [=~"^[m-z]+"]: int
+ })
}`),
}, {
eval: true,
+ opts: []Option{Docs(true)},
+ in: `
+ // Definition
+ A :: {
+ // TODO: support
+ [string]: int
+ }
+ // Field
+ a: A
+
+ // Pick first comment only.
+ a: _
+ `,
+ out: unindent(`
+ // Definition
+ A :: {
+ [string]: int
+ }
+
+ // Field
+ a: A`),
+ }, {
+ eval: true,
in: `
x: [string]: int
a: [P=string]: {
@@ -921,13 +944,11 @@
}
`,
out: unindent(`
- {
- x: [string]: int
- a: [P=string]: {
- b: x[P]
- c: P
- e: len(P)
- }
+ x: [string]: int
+ a: [P=string]: {
+ b: x[P]
+ c: P
+ e: len(P)
}`),
}, {
eval: true,
@@ -937,10 +958,8 @@
foo: int
`,
out: unindent(`
- {
- list: [...string]
- foo: 1 | 2 | *3
- }`),
+ list: [...string]
+ foo: 1 | 2 | *3`),
}, {
eval: true,
opts: []Option{Final()},
diff --git a/cue/load/loader_test.go b/cue/load/loader_test.go
index 6071db3..b0090d8 100644
--- a/cue/load/loader_test.go
+++ b/cue/load/loader_test.go
@@ -330,13 +330,13 @@
t.Error(inst.Err)
continue
}
- b, err := format.Node(inst.Value().Syntax())
+ b, err := format.Node(inst.Value().Syntax(cue.Final()))
if err != nil {
t.Error(err)
continue
}
if got := string(bytes.Map(rmSpace, b)); got != want[i] {
- t.Errorf("%s: got %s; want %s", inst.Dir, got, want)
+ t.Errorf("%s: got %s; want %s", inst.Dir, got, want[i])
}
}
}
diff --git a/cue/marshal.go b/cue/marshal.go
index 885bbb1..e8cc7f3 100644
--- a/cue/marshal.go
+++ b/cue/marshal.go
@@ -137,7 +137,8 @@
if p, ok := done[i.ImportPath]; ok {
return p
}
- n, imports := export(ctx, i.rootValue, options{raw: true})
+ // TODO: support exporting instance
+ n, imports := export(ctx, nil, i.rootValue, options{raw: true})
file, ok := n.(*ast.File)
if !ok {
diff --git a/cue/types.go b/cue/types.go
index 79024cc..a013eb0 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -839,11 +839,15 @@
}
ctx := v.ctx()
o := getOptions(opts)
+ var inst *Instance
+ if !o.final && !o.concrete {
+ inst = v.instance()
+ }
if o.raw {
- n, _ := export(ctx, v.path.v, o)
+ n, _ := export(ctx, inst, v.path.v, o)
return n
}
- n, _ := export(ctx, v.path.cache, o)
+ n, _ := export(ctx, inst, v.path.cache, o)
return n
}
@@ -1488,6 +1492,13 @@
_, _ = io.WriteString(state, ctx.str(v.path.cache))
}
+func (v Value) instance() *Instance {
+ if v.path == nil {
+ return nil
+ }
+ return v.ctx().getImportFromNode(v.path.v)
+}
+
// Reference returns the instance and path referred to by this value such that
// inst.Lookup(path) resolves to the same value, or no path if this value is not
// a reference. If a reference contains index selection (foo[bar]), it will
@@ -1645,6 +1656,7 @@
omitAttrs bool
resolveReferences bool
final bool
+ docs bool
disallowCycles bool // implied by concrete
}
@@ -1709,6 +1721,11 @@
}
}
+// Docs indicates whether docs should be included.
+func Docs(include bool) Option {
+ return func(p *options) { p.docs = true }
+}
+
// Definitions indicates whether definitions should be included.
//
// Definitions may still be included for certain functions if they are referred
diff --git a/cue/types_test.go b/cue/types_test.go
index 075f468..3d2ae62 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -30,6 +30,7 @@
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/errors"
+ "cuelang.org/go/internal"
)
func getInstance(t *testing.T, body ...string) *Instance {
@@ -697,7 +698,7 @@
x: int
}
X :: {
- <_>: int64
+ [string]: int64
} & V
v: X
`)
@@ -725,7 +726,8 @@
t.Errorf("got %v; want %v", got, tc.raw)
}
- if got := fmt.Sprint(v.Eval().Syntax()); got != tc.eval {
+ got := fmt.Sprint(internal.DebugStr(v.Eval().Syntax()))
+ if got != tc.eval {
t.Errorf("got %v; want %v", got, tc.eval)
}
@@ -746,7 +748,8 @@
t.Errorf("got %v; want %v", got, tc.raw)
}
- if got := fmt.Sprint(v.Eval().Syntax()); got != tc.eval {
+ got = fmt.Sprint(internal.DebugStr(v.Eval().Syntax()))
+ if got != tc.eval {
t.Errorf("got %v; want %v", got, tc.eval)
}
}
diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go
index 33cb400..56fd6cc 100644
--- a/encoding/jsonschema/decode.go
+++ b/encoding/jsonschema/decode.go
@@ -123,7 +123,7 @@
}
func (d *decoder) number(n cue.Value) ast.Expr {
- return n.Syntax().(ast.Expr)
+ return n.Syntax(cue.Final()).(ast.Expr)
}
func (d *decoder) uint(n cue.Value) ast.Expr {
@@ -131,11 +131,11 @@
if err != nil {
d.errf(n, "invalid uint")
}
- return n.Syntax().(ast.Expr)
+ return n.Syntax(cue.Final()).(ast.Expr)
}
func (d *decoder) bool(n cue.Value) ast.Expr {
- return n.Syntax().(ast.Expr)
+ return n.Syntax(cue.Final()).(ast.Expr)
}
func (d *decoder) boolValue(n cue.Value) bool {
@@ -147,7 +147,7 @@
}
func (d *decoder) string(n cue.Value) ast.Expr {
- return n.Syntax().(ast.Expr)
+ return n.Syntax(cue.Final()).(ast.Expr)
}
func (d *decoder) strValue(n cue.Value) (s string, ok bool) {
@@ -343,7 +343,7 @@
if !n.IsConcrete() {
s.errf(n, "invalid non-concerte value")
}
- return n.Syntax().(ast.Expr)
+ return n.Syntax(cue.Final()).(ast.Expr)
}
}
diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go
index 209dda6..9e2b07d 100644
--- a/encoding/openapi/types.go
+++ b/encoding/openapi/types.go
@@ -51,7 +51,7 @@
default:
return ""
}
- b, err := format.Node(v.Syntax())
+ b, err := format.Node(v.Syntax(cue.Final()))
if err != nil {
return ""
}
diff --git a/tools/trim/trim.go b/tools/trim/trim.go
index ccbb197..7c632c8 100644
--- a/tools/trim/trim.go
+++ b/tools/trim/trim.go
@@ -278,7 +278,14 @@
// resolved. Attempt to complete the expression by
// evaluting it within the struct to which the template
// is applied.
- expr := v.Syntax()
+ var expr ast.Expr
+ switch x := v.Syntax().(type) {
+ case ast.Expr:
+ expr = x
+ case *ast.File:
+ expr = &ast.StructLit{Elts: x.Decls}
+ }
+
// TODO: this only resolves references contained in scope.
v = internal.EvalExpr(scope, expr).(cue.Value)
}