cue: extend Option API
- remove AllFields.
- use the new API in the eval comand
Change-Id: Ib0b0da29980e38128e68b3b90e418ec149293f35
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1785
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/eval.go b/cmd/cue/cmd/eval.go
index 938456c..76a7b96 100644
--- a/cmd/cue/cmd/eval.go
+++ b/cmd/cue/cmd/eval.go
@@ -17,6 +17,7 @@
import (
"fmt"
+ "cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/format"
"cuelang.org/go/cue/parser"
@@ -62,16 +63,26 @@
for _, inst := range instances {
// TODO: use ImportPath or some other sanitized path.
fmt.Fprintf(w, "// %s\n", inst.Dir)
+ syn := []cue.Option{
+ cue.Attributes(*attrs),
+ cue.Optional(*all || *optional),
+ }
+ if *compile {
+ syn = append(syn, cue.RequireConcrete())
+ }
+ if *hidden || *all {
+ syn = append(syn, cue.Hidden(true))
+ }
opts := []format.Option{
format.UseSpaces(4),
format.TabIndent(false),
}
if exprs == nil {
- format.Node(w, inst.Value().Syntax(), opts...)
+ format.Node(w, inst.Value().Syntax(syn...), opts...)
fmt.Fprintln(w)
}
for _, e := range exprs {
- format.Node(w, inst.Eval(e).Syntax(), opts...)
+ format.Node(w, inst.Eval(e).Syntax(syn...), opts...)
fmt.Fprintln(w)
}
}
@@ -84,8 +95,29 @@
expressions = evalCmd.Flags().StringArrayP("expression", "e", nil, "evaluate this expression only")
+ compile = evalCmd.Flags().BoolP("concrete", "c", false,
+ "require the evaluation to be concrete")
+
+ hidden = evalCmd.Flags().BoolP("show-hidden", "H", false,
+ "display hidden attributes")
+
+ optional = evalCmd.Flags().BoolP("show-optional", "O", false,
+ "display hidden attributes")
+
+ attrs = evalCmd.Flags().BoolP("attributes", "l", false,
+ "display field attributes")
+
+ all = evalCmd.Flags().BoolP("all", "a", false,
+ "show optional and hidden fields")
+
+ // TODO: Option to include comments in output.
}
var (
expressions *[]string
+ compile *bool
+ attrs *bool
+ all *bool
+ hidden *bool
+ optional *bool
)
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index f6484cc..1da1dce 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -352,7 +352,7 @@
// Build map of mixin fields.
valueMap := map[key]cue.Value{}
- for mIter, _ := in.AllFields(); mIter.Next(); {
+ for mIter, _ := in.Fields(cue.All()); mIter.Next(); {
valueMap[iterKey(mIter)] = mIter.Value()
}
@@ -360,7 +360,7 @@
// Process fields.
rm := []ast.Node{}
- for iter, _ := v.AllFields(); iter.Next(); {
+ for iter, _ := v.Fields(cue.All()); iter.Next(); {
mSub := valueMap[iterKey(iter)]
if fn != nil {
mSub = mSub.Unify(fn(iter.Label()))
diff --git a/cue/export.go b/cue/export.go
index 3622e4c..cd4e63c 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -26,22 +26,18 @@
"cuelang.org/go/cue/token"
)
-type exportMode int
+func doEval(m options) bool {
+ return !m.raw
+}
-const (
- exportEval exportMode = 1 << iota
- exportAttrs
- exportRaw exportMode = 0
-)
-
-func export(ctx *context, v value, m exportMode) ast.Expr {
+func export(ctx *context, v value, m options) ast.Expr {
e := exporter{ctx, m, nil}
return e.expr(v)
}
type exporter struct {
ctx *context
- mode exportMode
+ mode options
stack []remap
}
@@ -112,10 +108,10 @@
}
func (p *exporter) expr(v value) ast.Expr {
- if p.mode == exportEval {
+ if doEval(p.mode) {
x := p.ctx.manifest(v)
if isIncomplete(x) {
- p = &exporter{p.ctx, exportRaw, p.stack}
+ p = &exporter{p.ctx, options{raw: true}, p.stack}
return p.expr(v)
}
v = x
@@ -228,7 +224,7 @@
case *structLit:
obj := &ast.StructLit{}
- if p.mode == exportEval {
+ if doEval(p.mode) {
for _, a := range x.arcs {
p.stack = append(p.stack, remap{
key: x,
@@ -242,7 +238,7 @@
if x.emit != nil {
obj.Elts = append(obj.Elts, &ast.EmitDecl{Expr: p.expr(x.emit)})
}
- if p.mode != exportEval && x.template != nil {
+ if !doEval(p.mode) && x.template != nil {
l, ok := x.template.evalPartial(p.ctx).(*lambdaExpr)
if ok {
obj.Elts = append(obj.Elts, &ast.Field{
@@ -257,15 +253,30 @@
f := &ast.Field{
Label: p.label(a.feature),
}
- if p.mode != exportEval {
+ // TODO: allow the removal of hidden fields. However, hidden fields
+ // that still used in incomplete expressions should not be removed
+ // (unless RequireConcrete is requested).
+ if a.optional {
+ // Optional fields are almost never concrete. We omit them in
+ // concrete mode to allow the user to use the -a option in eval
+ // without getting many errors.
+ if p.mode.omitOptional || p.mode.concrete {
+ continue
+ }
+ f.Optional = 1
+ }
+ if a.feature&hidden != 0 && p.mode.concrete && p.mode.omitHidden {
+ continue
+ }
+ if !doEval(p.mode) {
f.Value = p.expr(a.v)
- } else if v := p.ctx.manifest(x.at(p.ctx, i)); isIncomplete(v) {
- p := &exporter{p.ctx, exportRaw, p.stack}
+ } else if v := p.ctx.manifest(x.at(p.ctx, i)); isIncomplete(v) && !p.mode.concrete {
+ p := &exporter{p.ctx, options{raw: true}, p.stack}
f.Value = p.expr(a.v)
} else {
f.Value = p.expr(v)
}
- if a.attrs != nil { // TODO: && p.mode&exportAttrs != 0 {
+ if a.attrs != nil && !p.mode.omitAttrs {
for _, at := range a.attrs.attr {
f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
}
diff --git a/cue/export_test.go b/cue/export_test.go
index c77f59a..dad7d3a 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -26,8 +26,8 @@
func TestExport(t *testing.T) {
testCases := []struct {
- raw bool
- mode exportMode
+ raw bool // skip evaluation the root, fully raw
+ eval bool // evaluate the full export
in, out string
}{{
in: `"hello"`,
@@ -164,7 +164,7 @@
}`),
}, {
raw: true,
- mode: exportEval,
+ eval: true,
in: `{
b: {
idx: a[str]
@@ -186,7 +186,7 @@
}`),
}, {
raw: true,
- mode: exportEval,
+ eval: true,
in: `{
job <Name>: {
name: Name
@@ -218,7 +218,7 @@
}`),
}, {
raw: true,
- mode: exportEval,
+ eval: true,
in: `{
b: [{
<X>: int
@@ -252,7 +252,8 @@
v := newValueRoot(ctx, n)
buf := &bytes.Buffer{}
- err := format.Node(buf, export(ctx, v.eval(ctx), tc.mode))
+ opts := options{raw: !tc.eval}
+ err := format.Node(buf, export(ctx, v.eval(ctx), opts))
if err != nil {
log.Fatal(err)
}
diff --git a/cue/types.go b/cue/types.go
index c11fb73..df76119 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -170,12 +170,16 @@
return i.ctx.labelStr(i.f)
}
-// IsHidden reports if a field is hidden from the data model. This may only
-// be true if the field was obtained using AllFields.
+// IsHidden reports if a field is hidden from the data model.
func (i *Iterator) IsHidden() bool {
return i.f&hidden != 0
}
+// IsOptional reports if a field is optional.
+func (i *Iterator) IsOptional() bool {
+ return i.cur.path.arc.optional
+}
+
// marshalJSON iterates over the list and generates JSON output. HasNext
// will return false after this operation.
func marshalList(l *Iterator) (b []byte, err error) {
@@ -481,7 +485,8 @@
// Label reports he label used to obtain this value from the enclosing struct.
//
-// TODO: get rid of this somehow. Maybe by passing it to walk
+// TODO: get rid of this somehow. Probably by including a FieldInfo struct
+// or the like.
func (v Value) Label() (string, bool) {
if v.path.feature == 0 {
return "", false
@@ -583,12 +588,12 @@
// Syntax converts the possibly partially evaluated value into syntax. This
// can use used to print the value with package format.
-func (v Value) Syntax() ast.Expr {
+func (v Value) Syntax(opts ...Option) ast.Expr {
if v.path == nil || v.path.cache == nil {
return nil
}
ctx := v.ctx()
- return export(ctx, v.eval(ctx), exportEval)
+ return export(ctx, v.eval(ctx), getOptions(opts))
}
// Decode initializes x with Value v. If x is a struct, it will validate the
@@ -791,6 +796,14 @@
// structVal returns an structVal or an error if v is not a struct.
func (v Value) structVal(ctx *context) (structValue, error) {
+ return v.structValOpts(ctx, options{
+ omitHidden: true,
+ omitOptional: true,
+ })
+}
+
+// structVal returns an structVal or an error if v is not a struct.
+func (v Value) structValOpts(ctx *context, o options) (structValue, error) {
if err := v.checkKind(ctx, structKind); err != nil {
return structValue{}, err
}
@@ -799,15 +812,20 @@
// TODO: This is expansion appropriate?
obj = obj.expandFields(ctx) // expand comprehensions
- // check if any labels are hidden
- hasOptional := false
- f := label(0)
- for _, a := range obj.arcs {
- f |= a.feature
- hasOptional = hasOptional || a.optional
+ // check if any fields can be omitted
+ needFilter := false
+ if o.omitHidden || o.omitOptional {
+ f := label(0)
+ for _, a := range obj.arcs {
+ f |= a.feature
+ if o.omitOptional && a.optional {
+ needFilter = true
+ }
+ }
+ needFilter = needFilter || f&hidden != 0
}
- if f&hidden != 0 || hasOptional {
+ if needFilter {
arcs := make([]arc, len(obj.arcs))
k := 0
for _, a := range obj.arcs {
@@ -829,32 +847,13 @@
return structValue{ctx, v.path, obj}, nil
}
-func (v Value) structValWithHidden(ctx *context) (structValue, error) {
- if err := v.checkKind(ctx, structKind); err != nil {
- return structValue{}, err
- }
- obj := v.eval(ctx).(*structLit)
- obj = obj.expandFields(ctx)
-
- return structValue{ctx, v.path, obj}, nil
-}
-
// Fields creates an iterator over v's fields if v is a struct or an error
// otherwise.
-func (v Value) Fields() (Iterator, error) {
+func (v Value) Fields(opts ...Option) (Iterator, error) {
+ o := options{omitHidden: true, omitOptional: true}
+ o.updateOptions(opts)
ctx := v.ctx()
- obj, err := v.structVal(ctx)
- if err != nil {
- return Iterator{ctx: ctx}, err
- }
- return Iterator{ctx: ctx, val: v, iter: obj.n, len: len(obj.n.arcs)}, nil
-}
-
-// AllFields creates an iterator over all of v's fields, including the hidden
-// ones, if v is a struct or an error otherwise.
-func (v Value) AllFields() (Iterator, error) {
- ctx := v.ctx()
- obj, err := v.structValWithHidden(ctx)
+ obj, err := v.structValOpts(ctx, o)
if err != nil {
return Iterator{ctx: ctx}, err
}
@@ -989,32 +988,83 @@
}
type options struct {
- concrete bool
+ concrete bool // enforce that values are concrete
+ raw bool // show original values
+ hasHidden bool
+ omitHidden bool
+ omitOptional bool
+ omitAttrs bool
}
// An Option defines modes of evaluation.
-type Option func(p *options)
+type Option option
-// Used in Validate, Subsume?, Fields()
+type option func(p *options)
+
+// Used in Iter, Validate, Subsume?, Fields() Syntax, Export
// TODO: could also be used for subsumption.
-// RequireConcrete verifies that all values in a tree are concrete.
+// RequireConcrete ensures that all values are concrete.
+//
+// For Validate this means it returns an error if this is not the case.
+// In other cases a non-concrete value will be replaced with an error.
func RequireConcrete() Option {
- return func(p *options) { p.concrete = true }
+ return func(p *options) {
+ p.concrete = true
+ if !p.hasHidden {
+ p.omitHidden = true
+ }
+ }
}
-// VisitHidden(visit bool)
+// All indicates that all fields and values should be included in processing
+// even if they can be elided or omitted.
+func All() Option {
+ return func(p *options) {
+ p.omitAttrs = false
+ p.omitHidden = false
+ p.omitOptional = false
+ }
+}
+
+// Hidden indicates that hidden fields should be included.
//
+// Hidden fields may still be included if include is false,
+// even if a value is not concrete.
+func Hidden(include bool) Option {
+ return func(p *options) {
+ p.hasHidden = true
+ p.omitHidden = !include
+ }
+}
+
+// Optional indicates that optional fields should be included.
+func Optional(include bool) Option {
+ return func(p *options) { p.omitOptional = !include }
+}
+
+// Attributes indicates that attributes should be included.
+func Attributes(include bool) Option {
+ return func(p *options) { p.omitAttrs = !include }
+}
+
+func getOptions(opts []Option) (o options) {
+ o.updateOptions(opts)
+ return
+}
+
+func (o *options) updateOptions(opts []Option) {
+ for _, fn := range opts {
+ fn(o)
+ }
+}
// Validate reports any errors, recursively. The returned error may be an
// errors.List reporting multiple errors, where the total number of errors
// reported may be less than the actual number.
func (v Value) Validate(opts ...Option) error {
- var o options
- for _, fn := range opts {
- fn(&o)
- }
+ o := getOptions(opts)
list := errors.List{}
v.Walk(func(v Value) bool {
if err := v.Err(); err != nil {
diff --git a/cue/types_test.go b/cue/types_test.go
index bb33193..93fe468 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -630,7 +630,7 @@
t.Run(tc.value, func(t *testing.T) {
obj := getInstance(t, tc.value).Value()
- iter, err := obj.AllFields()
+ iter, err := obj.Fields(All())
checkFatal(t, err, tc.err, "init")
buf := []byte{'{'}
diff --git a/cue/value.go b/cue/value.go
index f15d7cf..976cd40 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -15,7 +15,6 @@
package cue
import (
- "fmt"
"math/big"
"sort"
"strconv"