cleanup: move packages to top level, change vanity URL
Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
78
compiler/build.go
Normal file
78
compiler/build.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
cueerrors "cuelang.org/go/cue/errors"
|
||||
cueload "cuelang.org/go/cue/load"
|
||||
)
|
||||
|
||||
// Build a cue configuration tree from the files in fs.
|
||||
func Build(sources map[string]fs.FS, args ...string) (*Value, error) {
|
||||
c := DefaultCompiler
|
||||
|
||||
buildConfig := &cueload.Config{
|
||||
// The CUE overlay needs to be prefixed by a non-conflicting path with the
|
||||
// local filesystem, otherwise Cue will merge the Overlay with whatever Cue
|
||||
// files it finds locally.
|
||||
Dir: "/config",
|
||||
Overlay: map[string]cueload.Source{},
|
||||
}
|
||||
|
||||
// Map the source files into the overlay
|
||||
for mnt, f := range sources {
|
||||
f := f
|
||||
mnt := mnt
|
||||
err := fs.WalkDir(f, ".", func(p string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !entry.Type().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if filepath.Ext(entry.Name()) != ".cue" {
|
||||
return nil
|
||||
}
|
||||
|
||||
contents, err := fs.ReadFile(f, p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", p, err)
|
||||
}
|
||||
|
||||
overlayPath := path.Join(buildConfig.Dir, mnt, p)
|
||||
buildConfig.Overlay[overlayPath] = cueload.FromBytes(contents)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
instances := cueload.Instances(args, buildConfig)
|
||||
if len(instances) != 1 {
|
||||
return nil, errors.New("only one package is supported at a time")
|
||||
}
|
||||
for _, value := range instances {
|
||||
if value.Err != nil {
|
||||
return nil, value.Err
|
||||
}
|
||||
}
|
||||
v, err := c.Context.BuildInstances(instances)
|
||||
if err != nil {
|
||||
return nil, errors.New(cueerrors.Details(err, &cueerrors.Config{}))
|
||||
}
|
||||
for _, value := range v {
|
||||
if value.Err() != nil {
|
||||
return nil, value.Err()
|
||||
}
|
||||
}
|
||||
if len(v) != 1 {
|
||||
return nil, errors.New("internal: wrong number of values")
|
||||
}
|
||||
return Wrap(v[0]), nil
|
||||
}
|
131
compiler/compiler.go
Normal file
131
compiler/compiler.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
cueerrors "cuelang.org/go/cue/errors"
|
||||
cuejson "cuelang.org/go/encoding/json"
|
||||
cueyaml "cuelang.org/go/encoding/yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultCompiler is the default Compiler and is used by Compile
|
||||
DefaultCompiler = New()
|
||||
)
|
||||
|
||||
func Compile(name string, src string) (*Value, error) {
|
||||
return DefaultCompiler.Compile(name, src)
|
||||
}
|
||||
|
||||
func NewValue() *Value {
|
||||
return DefaultCompiler.NewValue()
|
||||
}
|
||||
|
||||
// FIXME can be refactored away now?
|
||||
func Wrap(v cue.Value) *Value {
|
||||
return DefaultCompiler.Wrap(v)
|
||||
}
|
||||
|
||||
func Err(err error) error {
|
||||
return DefaultCompiler.Err(err)
|
||||
}
|
||||
|
||||
func DecodeJSON(path string, data []byte) (*Value, error) {
|
||||
return DefaultCompiler.DecodeJSON(path, data)
|
||||
}
|
||||
|
||||
func DecodeYAML(path string, data []byte) (*Value, error) {
|
||||
return DefaultCompiler.DecodeYAML(path, data)
|
||||
}
|
||||
|
||||
// Polyfill for a cue runtime
|
||||
// (we call it compiler to avoid confusion with dagger runtime)
|
||||
// Use this instead of cue.Runtime
|
||||
type Compiler struct {
|
||||
l sync.RWMutex
|
||||
*cue.Context
|
||||
}
|
||||
|
||||
func New() *Compiler {
|
||||
return &Compiler{
|
||||
Context: cuecontext.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) lock() {
|
||||
c.l.Lock()
|
||||
}
|
||||
|
||||
func (c *Compiler) unlock() {
|
||||
c.l.Unlock()
|
||||
}
|
||||
|
||||
func (c *Compiler) rlock() {
|
||||
c.l.RLock()
|
||||
}
|
||||
|
||||
func (c *Compiler) runlock() {
|
||||
c.l.RUnlock()
|
||||
}
|
||||
|
||||
// Compile an empty value
|
||||
func (c *Compiler) NewValue() *Value {
|
||||
empty, err := c.Compile("", "_")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return empty
|
||||
}
|
||||
|
||||
func (c *Compiler) Compile(name string, src string) (*Value, error) {
|
||||
c.lock()
|
||||
defer c.unlock()
|
||||
|
||||
v := c.Context.CompileString(src, cue.Filename(name))
|
||||
if v.Err() != nil {
|
||||
// FIXME: cleaner way to unwrap cue error details?
|
||||
return nil, Err(v.Err())
|
||||
}
|
||||
return c.Wrap(v), nil
|
||||
}
|
||||
|
||||
func (c *Compiler) DecodeJSON(path string, data []byte) (*Value, error) {
|
||||
expr, err := cuejson.Extract(path, data)
|
||||
if err != nil {
|
||||
return nil, Err(err)
|
||||
}
|
||||
v := c.Context.BuildExpr(expr, cue.Filename(path))
|
||||
if err := v.Err(); err != nil {
|
||||
return nil, Err(err)
|
||||
}
|
||||
return c.Wrap(v), nil
|
||||
}
|
||||
|
||||
func (c *Compiler) DecodeYAML(path string, data []byte) (*Value, error) {
|
||||
f, err := cueyaml.Extract(path, data)
|
||||
if err != nil {
|
||||
return nil, Err(err)
|
||||
}
|
||||
v := c.Context.BuildFile(f, cue.Filename(path))
|
||||
if err := v.Err(); err != nil {
|
||||
return nil, Err(err)
|
||||
}
|
||||
return c.Wrap(v), nil
|
||||
}
|
||||
|
||||
func (c *Compiler) Wrap(v cue.Value) *Value {
|
||||
return &Value{
|
||||
val: v,
|
||||
cc: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) Err(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(cueerrors.Details(err, &cueerrors.Config{}))
|
||||
}
|
35
compiler/compiler_test.go
Normal file
35
compiler/compiler_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test that a non-existing field is detected correctly
|
||||
func TestFieldNotExist(t *testing.T) {
|
||||
c := New()
|
||||
root, err := c.Compile("test.cue", `foo: "bar"`)
|
||||
require.NoError(t, err)
|
||||
require.True(t, root.Lookup("foo").Exists())
|
||||
require.False(t, root.Lookup("bar").Exists())
|
||||
}
|
||||
|
||||
// Test that a non-existing definition is detected correctly
|
||||
func TestDefNotExist(t *testing.T) {
|
||||
c := New()
|
||||
root, err := c.Compile("test.cue", `foo: #bla: "bar"`)
|
||||
require.NoError(t, err)
|
||||
require.True(t, root.Lookup("foo.#bla").Exists())
|
||||
require.False(t, root.Lookup("foo.#nope").Exists())
|
||||
}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
c := New()
|
||||
v, err := c.Compile("", `foo: hello: "world"`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `{"foo":{"hello":"world"}}`, string(v.JSON()))
|
||||
|
||||
// Reproduce a bug where Value.Lookup().JSON() ignores Lookup()
|
||||
require.Equal(t, `{"hello":"world"}`, string(v.Lookup("foo").JSON()))
|
||||
}
|
116
compiler/json.go
Normal file
116
compiler/json.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/KromDaniel/jonson"
|
||||
)
|
||||
|
||||
type JSON []byte
|
||||
|
||||
func (s JSON) Get(path ...string) ([]byte, error) {
|
||||
if s == nil {
|
||||
s = []byte("{}")
|
||||
}
|
||||
var (
|
||||
root *jonson.JSON
|
||||
)
|
||||
root, err := jonson.Parse(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse root json: %w", err)
|
||||
}
|
||||
pointer := root
|
||||
for _, key := range path {
|
||||
// FIXME: we can traverse maps but not arrays (need to handle int keys)
|
||||
pointer = pointer.At(key)
|
||||
}
|
||||
// FIXME: use indent function from stdlib
|
||||
return pointer.ToJSON()
|
||||
}
|
||||
|
||||
func (s JSON) Unset(path ...string) (JSON, error) {
|
||||
if s == nil {
|
||||
s = []byte("{}")
|
||||
}
|
||||
var (
|
||||
root *jonson.JSON
|
||||
)
|
||||
root, err := jonson.Parse(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unset: parse root json: %w", err)
|
||||
}
|
||||
var (
|
||||
pointer = root
|
||||
pathDir []string
|
||||
)
|
||||
if len(path) > 0 {
|
||||
pathDir = path[:len(path)-1]
|
||||
}
|
||||
for _, key := range pathDir {
|
||||
pointer = pointer.At(key)
|
||||
}
|
||||
if len(path) == 0 {
|
||||
pointer.Set(nil)
|
||||
} else {
|
||||
key := path[len(path)-1]
|
||||
pointer.DeleteMapKey(key)
|
||||
}
|
||||
return root.ToJSON()
|
||||
}
|
||||
|
||||
func (s JSON) Set(valueJSON []byte, path ...string) (JSON, error) {
|
||||
if s == nil {
|
||||
s = []byte("{}")
|
||||
}
|
||||
var (
|
||||
root *jonson.JSON
|
||||
value *jonson.JSON
|
||||
)
|
||||
root, err := jonson.Parse(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse root json: %w", err)
|
||||
}
|
||||
value, err = jonson.Parse(valueJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SetJSON: parse value json: |%s|: %w", valueJSON, err)
|
||||
}
|
||||
var (
|
||||
pointer = root
|
||||
pathDir []string
|
||||
)
|
||||
if len(path) > 0 {
|
||||
pathDir = path[:len(path)-1]
|
||||
}
|
||||
for _, key := range pathDir {
|
||||
if !pointer.ObjectKeyExists(key) {
|
||||
pointer.MapSet(key, jonson.NewEmptyJSONMap())
|
||||
}
|
||||
pointer = pointer.At(key)
|
||||
}
|
||||
if len(path) == 0 {
|
||||
pointer.Set(value)
|
||||
} else {
|
||||
key := path[len(path)-1]
|
||||
pointer.MapSet(key, value)
|
||||
}
|
||||
return root.ToJSON()
|
||||
}
|
||||
|
||||
func (s JSON) String() string {
|
||||
if s == nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s JSON) PrettyString() string {
|
||||
raw := s.String()
|
||||
b := &bytes.Buffer{}
|
||||
// If indenting fails, return raw string
|
||||
if err := json.Indent(b, []byte(raw), "", " "); err != nil {
|
||||
return raw
|
||||
}
|
||||
return fmt.Sprintf("%s\n", b.String())
|
||||
}
|
272
compiler/value.go
Normal file
272
compiler/value.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
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() ([]Field, error) {
|
||||
it, err := v.val.Fields()
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user