package compiler import ( "errors" "path" "path/filepath" "sort" "strconv" "strings" "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() } // FillFields fills multiple fields, in place func (v *Value) FillFields(values map[string]interface{}) (*Value, error) { for p, x := range values { if err := v.FillPath(cue.ParsePath(p), x); err != nil { return nil, err } } return v, nil } // Fill updates a value, in place func (v *Value) Fill(value interface{}) (*Value, error) { if err := v.FillPath(cue.MakePath(), value); err != nil { return nil, err } return v, nil } // 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() } // Proxy function to the underlying cue.Value func (v *Value) IncompleteKind() cue.Kind { return v.Cue().IncompleteKind() } // 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) Bool() (bool, error) { return v.val.Bool() } // 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 } func (v *Value) IsConcrete() bool { return v.val.IsConcrete() } // Recursive concreteness check. func (v *Value) IsConcreteR(opts ...cue.Option) error { o := []cue.Option{ cue.All(), cue.Concrete(true), cue.Hidden(true), } o = append(o, opts...) return v.val.Validate(o...) } func (v *Value) Walk(before func(*Value) bool, after func(*Value)) { // FIXME: this is a long lock v.cc.rlock() defer v.cc.runlock() 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) 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 } // Filename returns the CUE filename where the value was defined func (v *Value) Filename() (string, error) { pos := v.Cue().Pos() if !pos.IsValid() { return "", errors.New("invalid token position") } return pos.Filename(), nil } // Dirname returns the CUE dirname where the value was defined func (v *Value) Dirname() (string, error) { f, err := v.Filename() if err != nil { return "", err } return filepath.Dir(f), nil } // AbsPath returns an absolute path contained in Value // Paths are relative to the CUE file they were declared in. func (v *Value) AbsPath() (string, error) { p, err := v.String() if err != nil { return "", nil } if filepath.IsAbs(p) { return p, nil } d, err := v.Dirname() if err != nil { return "", err } return path.Join(d, p), nil } 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() } // DocSummary returns the first line of documentation func (v *Value) DocSummary() string { doc := "" for _, c := range v.Doc() { doc += c.Text() } doc = strings.TrimSpace(doc) doc = strings.SplitN(doc, "\n", 2)[0] return doc }