This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
dagger/compiler/value.go
Andrea Luzzardi ae4e61aaa1 dagger do: Improve help message
- Only print first line of comment
- Add "Available Options" section header

Fixes #1777

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
2022-03-11 12:55:03 -08:00

363 lines
7.5 KiB
Go

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
}