package compiler import ( "sort" "strconv" "cuelang.org/go/cue" "cuelang.org/go/cue/ast" cueformat "cuelang.org/go/cue/format" ) // Value is a wrapper around cue.Value. // Use instead of cue.Value and cue.Instance type Value struct { val cue.Value cc *Compiler } // FillPath fills the value in-place func (v *Value) FillPath(p cue.Path, x interface{}) error { v.cc.lock() defer v.cc.unlock() // If calling Fill() with a Value, we want to use the underlying // cue.Value to fill. if val, ok := x.(*Value); ok { v.val = v.val.FillPath(p, val.val) } else { v.val = v.val.FillPath(p, x) } return v.val.Err() } // LookupPath is a concurrency safe wrapper around cue.Value.LookupPath func (v *Value) LookupPath(p cue.Path) *Value { v.cc.rlock() defer v.cc.runlock() return v.cc.Wrap(v.val.LookupPath(p)) } // Lookup is a helper function to lookup by path parts. func (v *Value) Lookup(path string) *Value { return v.LookupPath(cue.ParsePath(path)) } func (v *Value) ReferencePath() (*Value, cue.Path) { vv, p := v.val.ReferencePath() return v.cc.Wrap(vv), p } // Proxy function to the underlying cue.Value func (v *Value) Len() cue.Value { return v.val.Len() } // Proxy function to the underlying cue.Value func (v *Value) Kind() cue.Kind { return v.val.Kind() } // Field represents a struct field type Field struct { Selector cue.Selector Value *Value } // Label returns the unquoted selector func (f Field) Label() string { l := f.Selector.String() if unquoted, err := strconv.Unquote(l); err == nil { return unquoted } return l } // Proxy function to the underlying cue.Value // Field ordering is guaranteed to be stable. func (v *Value) Fields(opts ...cue.Option) ([]Field, error) { it, err := v.val.Fields(opts...) if err != nil { return nil, err } fields := []Field{} for it.Next() { fields = append(fields, Field{ Selector: it.Selector(), Value: v.cc.Wrap(it.Value()), }) } sort.SliceStable(fields, func(i, j int) bool { return fields[i].Selector.String() < fields[j].Selector.String() }) return fields, nil } // Proxy function to the underlying cue.Value func (v *Value) Struct() (*cue.Struct, error) { return v.val.Struct() } // Proxy function to the underlying cue.Value func (v *Value) Exists() bool { return v.val.Exists() } // Proxy function to the underlying cue.Value func (v *Value) Bytes() ([]byte, error) { return v.val.Bytes() } // Proxy function to the underlying cue.Value func (v *Value) String() (string, error) { return v.val.String() } // Proxy function to the underlying cue.Value func (v *Value) Int64() (int64, error) { return v.val.Int64() } // Proxy function to the underlying cue.Value func (v *Value) Path() cue.Path { return v.val.Path() } // Proxy function to the underlying cue.Value func (v *Value) Decode(x interface{}) error { return v.val.Decode(x) } func (v *Value) List() ([]*Value, error) { l := []*Value{} it, err := v.val.List() if err != nil { return nil, err } for it.Next() { l = append(l, v.cc.Wrap(it.Value())) } return l, nil } // Recursive concreteness check. func (v *Value) IsConcreteR(opts ...cue.Option) error { o := []cue.Option{cue.Concrete(true)} o = append(o, opts...) return v.val.Validate(o...) } func (v *Value) Walk(before func(*Value) bool, after func(*Value)) { // FIXME: lock? var ( llBefore func(cue.Value) bool llAfter func(cue.Value) ) if before != nil { llBefore = func(child cue.Value) bool { return before(v.cc.Wrap(child)) } } if after != nil { llAfter = func(child cue.Value) { after(v.cc.Wrap(child)) } } v.val.Walk(llBefore, llAfter) } // Export concrete values to JSON. ignoring non-concrete values. // Contrast with cue.Value.MarshalJSON which requires all values // to be concrete. func (v *Value) JSON() JSON { cuePathToStrings := func(p cue.Path) []string { selectors := p.Selectors() out := make([]string, len(selectors)) for i, sel := range selectors { out[i] = sel.String() } return out } var out JSON v.val.Walk( func(v cue.Value) bool { b, err := v.MarshalJSON() if err == nil { newOut, err := out.Set(b, cuePathToStrings(v.Path())...) if err == nil { out = newOut } return false } return true }, nil, ) out, _ = out.Get(cuePathToStrings(v.Path())...) return out } // Check that a value is valid. Optionally check that it matches // all the specified spec definitions.. func (v *Value) Validate() error { return v.val.Validate() } // Return cue source for this value func (v *Value) Source(opts ...cue.Option) ([]byte, error) { v.cc.rlock() defer v.cc.runlock() return cueformat.Node(v.val.Eval().Syntax(opts...), cueformat.UseSpaces(4), cueformat.TabIndent(false), ) } func (v *Value) IsEmptyStruct() bool { if st, err := v.Struct(); err == nil { if st.Len() == 0 { return true } } return false } func (v *Value) Cue() cue.Value { return v.val } // Returns true if value has a dagger attribute (eg. artifact, secret, input) func (v *Value) HasAttr(filter ...string) bool { attrs := v.val.Attributes(cue.ValueAttr) for _, attr := range attrs { name := attr.Name() // match `@dagger(...)` if name == "dagger" { // did not provide filter, match any @dagger attr if len(filter) == 0 { return true } // loop over args (CSV content in attribute) for i := 0; i < attr.NumArgs(); i++ { key, _ := attr.Arg(i) // one or several values where provided, filter for _, val := range filter { if key == val { return true } } } } } return false } func (v *Value) Dereference() *Value { dVal := cue.Dereference(v.val) return v.cc.Wrap(dVal) } func (v *Value) Default() (*Value, bool) { val, hasDef := v.val.Default() return v.cc.Wrap(val), hasDef } func (v *Value) Doc() []*ast.CommentGroup { return v.Cue().Doc() } func (v *Value) IncompleteKindString() string { return v.Cue().IncompleteKind().String() }