1
1
Fork 0
mirror of https://github.com/mcuadros/ascode synced 2024-05-09 00:56:14 +02:00
ascode/starlark/types/value.go

276 lines
5.9 KiB
Go

package types
import (
"fmt"
"sort"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/zclconf/go-cty/cty"
"go.starlark.net/starlark"
)
// Value is helper to manipulate and transform starlark.Value to go types and
// cty.Value.
type Value struct {
t Type
v starlark.Value
}
// MustValue returns a Value from a starlark.Value, it panics if error.
func MustValue(v starlark.Value) *Value {
value, err := NewValue(v)
if err != nil {
panic(err)
}
return value
}
// NewValue returns a Value from a starlark.Value.
func NewValue(v starlark.Value) (*Value, error) {
t, err := NewTypeFromStarlark(v.Type())
if err != nil {
return nil, err
}
return &Value{t: *t, v: v}, nil
}
// Starlark returns the starlark.Value.
func (v *Value) Starlark() starlark.Value {
return v.v
}
// Type returns the Type of the value.
func (v *Value) Type() *Type {
return &v.t
}
// Cty returns the cty.Value.
func (v *Value) Cty() cty.Value {
switch v.t.Starlark() {
case "string":
return cty.StringVal(v.Interface().(string))
case "int":
return cty.NumberIntVal(v.Interface().(int64))
case "float":
return cty.NumberFloatVal(v.Interface().(float64))
case "bool":
return cty.BoolVal(v.Interface().(bool))
case "list":
list := v.v.(*starlark.List)
if list.Len() == 0 {
return cty.ListValEmpty(v.t.Cty())
}
values := make([]cty.Value, list.Len())
for i := 0; i < list.Len(); i++ {
values[i] = MustValue(list.Index(i)).Cty()
}
return cty.ListVal(values)
case "dict":
dict := v.v.(*starlark.Dict)
values := make(map[string]cty.Value)
for _, t := range dict.Items() {
key := fmt.Sprintf("%s", MustValue(t.Index(0)).Interface())
values[key] = MustValue(t.Index(1)).Cty()
}
return cty.MapVal(values)
case "Attribute":
return cty.StringVal(v.v.(*Attribute).GoString())
default:
panic(fmt.Sprintf("unhandled: %s", v.t.Starlark()))
}
}
// Interface returns the value as a Go value.
func (v *Value) Interface() interface{} {
switch cast := v.v.(type) {
case starlark.Bool:
return bool(cast)
case starlark.String:
return string(cast)
case starlark.Int:
i, _ := cast.Int64()
return i
case starlark.Float:
return float64(cast)
case *ResourceCollection:
return MustValue(cast.List).Interface()
case *starlark.List:
out := make([]interface{}, cast.Len())
for i := 0; i < cast.Len(); i++ {
out[i] = MustValue(cast.Index(i)).Interface()
}
return out
case *starlark.Dict:
values := make(map[string]interface{})
for _, t := range cast.Items() {
key := fmt.Sprintf("%s", MustValue(t.Index(0)).Interface())
values[key] = MustValue(t.Index(1)).Interface()
}
return values
default:
return v
}
}
// Hash honors the starlark.Value interface.
func (v *Value) Hash() (uint32, error) {
switch value := v.v.(type) {
case *starlark.List:
// Use same algorithm as Python.
var x, mult uint32 = 0x345678, 1000003
for i := 0; i < value.Len(); i++ {
y, err := value.Index(i).Hash()
if err != nil {
return 0, err
}
x = x ^ y*mult
mult += 82520 + uint32(value.Len()+value.Len())
}
return x, nil
default:
return value.Hash()
}
}
// NamedValue represents a Value with a given name.
type NamedValue struct {
Name string
*Value
}
// Values is a list of NamedValues.
type Values struct {
names sort.StringSlice
values map[string]*NamedValue
}
// NewValues return a new instance of Values
func NewValues() *Values {
return &Values{values: make(map[string]*NamedValue)}
}
// Set sets a name and a value and returns it as a NamedValue.
func (a *Values) Set(name string, v *Value) *NamedValue {
if e, ok := a.values[name]; ok {
e.Value = v
return e
}
e := &NamedValue{Name: name, Value: v}
a.values[name] = e
a.names = append(a.names, name)
return e
}
// Has returns true if Values contains a NamedValue with this name.
func (a Values) Has(name string) bool {
_, ok := a.values[name]
return ok
}
// Get returns the NamedValue with the given name, if any.
func (a Values) Get(name string) *NamedValue {
if e, ok := a.values[name]; ok {
return e
}
return nil
}
// Hash honors the starlark.Value interface.
func (a Values) Hash() (uint32, error) {
// Same algorithm as Tuple.hash, but with different primes.
var x, m uint32 = 9199, 7207
err := a.ForEach(func(v *NamedValue) error {
namehash, _ := starlark.String(v.Name).Hash()
x = x ^ 3*namehash
y, err := v.Hash()
if err != nil {
return err
}
x = x ^ y*m
m += 6203
return nil
})
if err != nil {
return 0, err
}
return x, nil
}
// ToStringDict adds a name/value entry to d for each field of the struct.
func (a Values) ToStringDict(d starlark.StringDict) {
sort.Sort(a.names) // we sort the list before hash it.
for _, name := range a.names {
d[name] = a.values[name].Starlark()
}
}
// ForEach call cb for each value on Values, it stop the iteration an error
// is returned.
func (a Values) ForEach(cb func(*NamedValue) error) error {
sort.Sort(a.names) // we sort the list before hash it.
for _, name := range a.names {
if err := cb(a.values[name]); err != nil {
return err
}
}
return nil
}
// List return a list of NamedValues sorted by name.
func (a Values) List() []*NamedValue {
sort.Sort(a.names) // we sort the list before hash it.
list := make([]*NamedValue, len(a.names))
for i, name := range a.names {
list[i] = a.values[name]
}
return list
}
// Len return the length.
func (a Values) Len() int {
return len(a.values)
}
// Cty returns the cty.Value based on a given schema.
func (a Values) Cty(schema *configschema.Block) cty.Value {
values := make(map[string]cty.Value)
for key, value := range schema.Attributes {
v := value.EmptyValue()
if a.Has(key) {
v = a.Get(key).Cty()
}
values[key] = v
}
return cty.ObjectVal(values)
}
// Dict is a starlark.Dict HCLCompatible.
type Dict struct {
*starlark.Dict
}
// NewDict returns a new empty Dict.
func NewDict() *Dict {
return &Dict{starlark.NewDict(0)}
}