mirror of
https://github.com/lxc/incus.git
synced 2026-02-05 09:46:19 +01:00
268 lines
6.2 KiB
Go
268 lines
6.2 KiB
Go
package scriptlet
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"go.starlark.net/starlark"
|
|
)
|
|
|
|
// starlarkObject wraps a starlark.Dict and is used to provide custom object types to the Starlark scriptlets.
|
|
// This implements the starlark.HasAttrs interface.
|
|
type starlarkObject struct {
|
|
d *starlark.Dict
|
|
typeName string
|
|
}
|
|
|
|
// Type is a starlark object type.
|
|
func (s *starlarkObject) Type() string {
|
|
return s.typeName
|
|
}
|
|
|
|
// String is a starlark object string.
|
|
func (s *starlarkObject) String() string {
|
|
return s.d.String()
|
|
}
|
|
|
|
// Freeze freezes the starlark object.
|
|
func (s *starlarkObject) Freeze() {
|
|
}
|
|
|
|
// Hash returns a hash of the starlark object.
|
|
func (s *starlarkObject) Hash() (uint32, error) {
|
|
return 0, fmt.Errorf("Unhashable type %s", s.Type())
|
|
}
|
|
|
|
// Truth returns whether the starlark object is true.
|
|
func (s *starlarkObject) Truth() starlark.Bool {
|
|
return starlark.True
|
|
}
|
|
|
|
// AttrNames returns the attribute names of the starlark object.
|
|
func (s *starlarkObject) AttrNames() []string {
|
|
keys := s.d.Keys()
|
|
keyNames := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
keyNames = append(keyNames, k.String())
|
|
}
|
|
|
|
return keyNames
|
|
}
|
|
|
|
// Attr gets an attribute of the starlark object.
|
|
func (s *starlarkObject) Attr(name string) (starlark.Value, error) {
|
|
field, found, err := s.d.Get(starlark.String(name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !found {
|
|
return nil, fmt.Errorf("Invalid field %q", name)
|
|
}
|
|
|
|
return field, nil
|
|
}
|
|
|
|
// StarlarkMarshal converts input to a starlark Value.
|
|
// It only includes exported struct fields, and uses the "json" tag for field names.
|
|
func StarlarkMarshal(input any) (starlark.Value, error) {
|
|
return starlarkMarshal(input, nil)
|
|
}
|
|
|
|
// starlarkMarshal converts input to a starlark Value.
|
|
// It only includes exported struct fields, and uses the "json" tag for field names.
|
|
// Takes optional parent Starlark dictionary which will be used to set fields from anonymous (embedded) structs
|
|
// in to the parent struct.
|
|
func starlarkMarshal(input any, parent *starlark.Dict) (starlark.Value, error) {
|
|
if input == nil {
|
|
return starlark.None, nil
|
|
}
|
|
|
|
sv, ok := input.(starlark.Value)
|
|
if ok {
|
|
return sv, nil
|
|
}
|
|
|
|
var err error
|
|
|
|
v := reflect.ValueOf(input)
|
|
|
|
switch v.Type().Kind() {
|
|
case reflect.String:
|
|
sv = starlark.String(v.String())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
sv = starlark.MakeInt(int(v.Int()))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
sv = starlark.MakeUint(uint(v.Uint()))
|
|
case reflect.Float32, reflect.Float64:
|
|
sv = starlark.Float(v.Float())
|
|
case reflect.Bool:
|
|
sv = starlark.Bool(v.Bool())
|
|
case reflect.Array, reflect.Slice:
|
|
vlen := v.Len()
|
|
listElems := make([]starlark.Value, 0, vlen)
|
|
|
|
for i := range vlen {
|
|
lv, err := StarlarkMarshal(v.Index(i).Interface())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
listElems = append(listElems, lv)
|
|
}
|
|
|
|
sv = starlark.NewList(listElems)
|
|
case reflect.Map:
|
|
mKeys := v.MapKeys()
|
|
d := starlark.NewDict(len(mKeys))
|
|
|
|
if v.Type().Key().Kind() != reflect.String {
|
|
return nil, fmt.Errorf("Only string keys are supported, found %s", v.Type().Key().Kind())
|
|
}
|
|
|
|
sort.Slice(mKeys, func(i, j int) bool {
|
|
return mKeys[i].String() < mKeys[j].String()
|
|
})
|
|
|
|
for _, k := range mKeys {
|
|
mv := v.MapIndex(k)
|
|
dv, err := StarlarkMarshal(mv.Interface())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = d.SetKey(starlark.String(k.String()), dv)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed setting map key %q to %v: %w", k.String(), dv, err)
|
|
}
|
|
}
|
|
|
|
sv = d
|
|
case reflect.Struct:
|
|
fieldCount := v.Type().NumField()
|
|
|
|
d := parent
|
|
if d == nil {
|
|
d = starlark.NewDict(fieldCount)
|
|
}
|
|
|
|
for i := range fieldCount {
|
|
field := v.Type().Field(i)
|
|
fieldValue := v.Field(i)
|
|
|
|
if !field.IsExported() {
|
|
continue
|
|
}
|
|
|
|
if field.Anonymous && fieldValue.Kind() == reflect.Struct {
|
|
// If anonymous struct field's value is another struct then pass the the current
|
|
// starlark dictionary to starlarkMarshal so its fields will be set on the parent.
|
|
_, err = starlarkMarshal(fieldValue.Interface(), d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
dv, err := StarlarkMarshal(fieldValue.Interface())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key, _, _ := strings.Cut(field.Tag.Get("json"), ",")
|
|
if key == "" {
|
|
key = field.Name
|
|
}
|
|
|
|
err = d.SetKey(starlark.String(key), dv)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed setting struct field %q to %v: %w", key, dv, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only convert the top-level struct to a Starlark object.
|
|
if parent == nil {
|
|
ss := starlarkObject{
|
|
d: d,
|
|
typeName: v.Type().Name(),
|
|
}
|
|
|
|
sv = &ss
|
|
} else {
|
|
sv = d
|
|
}
|
|
|
|
case reflect.Pointer:
|
|
if v.IsZero() {
|
|
sv = starlark.None
|
|
} else {
|
|
sv, err = StarlarkMarshal(v.Elem().Interface())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("Unrecognised type %v for value %+v", v.Type(), v.Interface())
|
|
}
|
|
|
|
return sv, nil
|
|
}
|
|
|
|
// StarlarkUnmarshal converts a Starlark value into a Go value.
|
|
// Only NoneType, Bool, Int, Float, String, List and Dict are supported.
|
|
func StarlarkUnmarshal(input starlark.Value) (any, error) {
|
|
switch v := input.(type) {
|
|
case starlark.NoneType:
|
|
return nil, nil
|
|
case starlark.Bool:
|
|
return bool(v), nil
|
|
case starlark.Int:
|
|
result, _ := v.Int64()
|
|
return result, nil
|
|
case starlark.Float:
|
|
return float64(v), nil
|
|
case starlark.String:
|
|
return string(v), nil
|
|
case *starlark.List:
|
|
length := v.Len()
|
|
result := make([]any, length)
|
|
|
|
// Iterate over the Starlark List
|
|
for i := range length {
|
|
value, err := StarlarkUnmarshal(v.Index(i))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result[i] = value
|
|
}
|
|
|
|
return result, nil
|
|
case *starlark.Dict:
|
|
result := make(map[string]any)
|
|
|
|
// Iterate over the Starlark Dict
|
|
for _, kv := range v.Items() {
|
|
dictKey, dictValue := kv[0], kv[1]
|
|
|
|
key, ok := starlark.AsString(dictKey)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Only string keys are supported, found %s", dictKey.Type())
|
|
}
|
|
|
|
value, err := StarlarkUnmarshal(dictValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result[key] = value
|
|
}
|
|
|
|
return result, nil
|
|
default:
|
|
return nil, fmt.Errorf("Unsupported type: %T", v)
|
|
}
|
|
}
|