This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository ego.
View the commit online.
commit 8b714599173f7f1ea03087c2851c8173cf03d5f1
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 11:00:24 2026 -0600
feat(efl): add Value container API for slices and maps
Add NewValueSlice[T], ValueSlice[T], NewValueMap[T], ValueMap[T]
with support for scalar elements and nested *Value for arbitrary
depth containers.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
efl/value.go | 6 +
efl/value_container.go | 798 +++++++++++++++++++++++++++++++++++++++++++++++++
efl/value_test.go | 85 ++++++
3 files changed, 889 insertions(+)
diff --git a/efl/value.go b/efl/value.go
index 4f1257a..3018bca 100644
--- a/efl/value.go
+++ b/efl/value.go
@@ -79,6 +79,8 @@ const (
ValueKindString
ValueKindBool
ValueKindByte
+ ValueKindArray
+ ValueKindHash
)
// ScalarType is the constraint for types that can be stored in a Value.
@@ -395,6 +397,10 @@ func (v *Value) Kind() ValueKind {
return ValueKindBool
case C.EINA_VALUE_TYPE_CHAR:
return ValueKindByte
+ case C.EINA_VALUE_TYPE_ARRAY:
+ return ValueKindArray
+ case C.EINA_VALUE_TYPE_HASH:
+ return ValueKindHash
default:
return ValueKindUnknown
}
diff --git a/efl/value_container.go b/efl/value_container.go
new file mode 100644
index 0000000..7bc1013
--- /dev/null
+++ b/efl/value_container.go
@@ -0,0 +1,798 @@
+package efl
+
+/*
+#cgo pkg-config: eina
+
+#include <Eina.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Forward declarations for helpers defined in value.go's cgo preamble. Each
+// cgo file has its own preamble scope, so we redeclare (not redefine) the
+// functions needed here.
+static Eina_Value *_ego_value_new(const Eina_Value_Type *type);
+static void _ego_value_free(Eina_Value *v);
+
+// _ego_value_array_setup initialises value as an EINA_VALUE_TYPE_ARRAY whose
+// elements have the given subtype. step 0 selects a sane default.
+static Eina_Bool _ego_value_array_setup(Eina_Value *v, const Eina_Value_Type *subtype) {
+ return eina_value_array_setup(v, subtype, 0);
+}
+
+// _ego_value_array_count returns the number of elements currently stored.
+static unsigned int _ego_value_array_count(const Eina_Value *v) {
+ return eina_value_array_count(v);
+}
+
+// _ego_value_array_pappend appends the element pointed to by ptr. For most
+// scalar subtypes ptr is a pointer to the scalar value; for STRING subtype ptr
+// must be a pointer to a (char *) (i.e. a char **).
+static Eina_Bool _ego_value_array_pappend(Eina_Value *v, const void *ptr) {
+ return eina_value_array_pappend(v, ptr);
+}
+
+// _ego_value_array_pget copies the element at position pos into the memory
+// pointed to by ptr. Same pointer-to-pointer convention applies for STRING.
+static Eina_Bool _ego_value_array_pget(const Eina_Value *v, unsigned int pos, void *ptr) {
+ return eina_value_array_pget(v, pos, ptr);
+}
+
+// _ego_value_array_append_string appends a string element. The extra level of
+// indirection (&s) is required because EINA_VALUE_TYPE_STRING stores a char*
+// and pappend/pset expect a pointer to that pointer.
+static Eina_Bool _ego_value_array_append_string(Eina_Value *v, const char *s) {
+ return eina_value_array_pappend(v, &s);
+}
+
+// _ego_value_array_get_string retrieves the string element at pos. Returns
+// the internal C string pointer owned by the array; the caller must not free
+// it. Returns NULL on failure.
+static const char *_ego_value_array_get_string(const Eina_Value *v, unsigned int pos) {
+ const char *s = NULL;
+ eina_value_array_pget(v, pos, &s);
+ return s;
+}
+
+// _ego_value_array_append_value appends a nested Eina_Value. eina_value_array
+// with subtype EINA_VALUE_TYPE_VALUE stores a copy of the Eina_Value struct,
+// so pappend expects a const Eina_Value * (pointer to the struct itself, not
+// pointer-to-pointer).
+static Eina_Bool _ego_value_array_append_value(Eina_Value *v, const Eina_Value *sub) {
+ return eina_value_array_pappend(v, sub);
+}
+
+// _ego_value_array_new_value_at allocates a new heap Eina_Value that is a
+// copy of the nested Eina_Value stored at pos in v (requires VALUE subtype).
+// When the array subtype is EINA_VALUE_TYPE_VALUE, each stored element is
+// itself an Eina_Value. eina_value_array_value_get wraps it in another
+// VALUE-typed container, so we must unwrap one level with eina_value_pget to
+// obtain the inner value before copying it to a heap allocation.
+// Returns NULL on failure. The caller must free with eina_value_free.
+static Eina_Value *_ego_value_array_new_value_at(const Eina_Value *v, unsigned int pos) {
+ // Wrapper VALUE: eina_value_array_value_get sets up wrapper with subtype
+ // EINA_VALUE_TYPE_VALUE and copies the stored Eina_Value struct into it.
+ Eina_Value wrapper;
+ memset(&wrapper, 0, sizeof(wrapper));
+ if (!eina_value_array_value_get(v, pos, &wrapper)) return NULL;
+
+ // Unwrap: extract the inner Eina_Value from the VALUE-typed wrapper.
+ // eina_value_pget for EINA_VALUE_TYPE_VALUE copies the inner Eina_Value
+ // bytes into inner_tmp.
+ Eina_Value inner_tmp;
+ memset(&inner_tmp, 0, sizeof(inner_tmp));
+ Eina_Bool ok = eina_value_pget(&wrapper, &inner_tmp);
+ eina_value_flush(&wrapper);
+ if (!ok) return NULL;
+
+ // Copy inner_tmp into a heap allocation for Go to own.
+ Eina_Value *out = eina_value_new(inner_tmp.type);
+ if (!out) {
+ eina_value_flush(&inner_tmp);
+ return NULL;
+ }
+ if (!eina_value_copy(&inner_tmp, out)) {
+ eina_value_free(out);
+ eina_value_flush(&inner_tmp);
+ return NULL;
+ }
+ eina_value_flush(&inner_tmp);
+ return out;
+}
+
+// _ego_value_hash_setup initialises value as an EINA_VALUE_TYPE_HASH whose
+// elements have the given subtype. buckets_power_size 0 selects a sane default.
+static Eina_Bool _ego_value_hash_setup(Eina_Value *v, const Eina_Value_Type *subtype) {
+ return eina_value_hash_setup(v, subtype, 0);
+}
+
+// _ego_value_hash_pset stores the element pointed to by ptr under key.
+static Eina_Bool _ego_value_hash_pset(Eina_Value *v, const char *key, const void *ptr) {
+ return eina_value_hash_pset(v, key, ptr);
+}
+
+// _ego_value_hash_set_string stores a string value under key, applying the
+// required pointer-to-pointer indirection.
+static Eina_Bool _ego_value_hash_set_string(Eina_Value *v, const char *key, const char *val) {
+ return eina_value_hash_pset(v, key, &val);
+}
+
+// _ego_value_hash_pget copies the element stored at key into ptr.
+static Eina_Bool _ego_value_hash_pget(const Eina_Value *v, const char *key, void *ptr) {
+ return eina_value_hash_pget(v, key, ptr);
+}
+
+// _ego_value_hash_get_string retrieves the string element at key. Returns the
+// internal C string pointer owned by the hash; the caller must not free it.
+static const char *_ego_value_hash_get_string(const Eina_Value *v, const char *key) {
+ const char *s = NULL;
+ eina_value_hash_pget(v, key, &s);
+ return s;
+}
+
+// _ego_value_hash_population returns the number of key-value pairs stored.
+static unsigned int _ego_value_hash_population(const Eina_Value *v) {
+ return eina_value_hash_population(v);
+}
+
+// _ego_value_hash_new_value_at allocates a new heap Eina_Value that is a copy
+// of the nested Eina_Value stored at key in v (requires VALUE subtype). The
+// semantics mirror _ego_value_array_new_value_at: eina_value_hash_pget with
+// VALUE subtype copies the inner Eina_Value bytes into a VALUE-typed wrapper,
+// which we then unwrap before copying to the heap.
+// The caller must free the returned pointer with eina_value_free.
+static Eina_Value *_ego_value_hash_new_value_at(const Eina_Value *v, const char *key) {
+ Eina_Value wrapper;
+ memset(&wrapper, 0, sizeof(wrapper));
+ if (!eina_value_hash_pget(v, key, &wrapper)) return NULL;
+
+ Eina_Value inner_tmp;
+ memset(&inner_tmp, 0, sizeof(inner_tmp));
+ Eina_Bool ok = eina_value_pget(&wrapper, &inner_tmp);
+ eina_value_flush(&wrapper);
+ if (!ok) return NULL;
+
+ Eina_Value *out = eina_value_new(inner_tmp.type);
+ if (!out) {
+ eina_value_flush(&inner_tmp);
+ return NULL;
+ }
+ if (!eina_value_copy(&inner_tmp, out)) {
+ eina_value_free(out);
+ eina_value_flush(&inner_tmp);
+ return NULL;
+ }
+ eina_value_flush(&inner_tmp);
+ return out;
+}
+
+// _EgoKeyBuf is a scratch buffer used by _ego_collect_hash_keys_cb to
+// accumulate key pointers from a hash_foreach call.
+typedef struct {
+ char **keys;
+ int count;
+ int cap;
+} _EgoKeyBuf;
+
+static Eina_Bool _ego_collect_hash_keys_cb(const Eina_Hash *hash EINA_UNUSED,
+ const void *key,
+ void *data EINA_UNUSED,
+ void *fdata) {
+ _EgoKeyBuf *buf = (_EgoKeyBuf *)fdata;
+ if (buf->count >= buf->cap) return EINA_FALSE;
+ buf->keys[buf->count++] = (char *)key;
+ return EINA_TRUE;
+}
+
+// _ego_value_hash_keys fills keys (pre-allocated with capacity cap) with the
+// internal key pointers of the hash stored in v. Returns the number of keys
+// written. The pointers are valid only while v is live and unmodified.
+static int _ego_value_hash_keys(const Eina_Value *v, char **keys, int cap) {
+ Eina_Value_Hash desc;
+ _EgoKeyBuf buf;
+
+ if (!eina_value_pget(v, &desc)) return 0;
+ if (!desc.hash) return 0;
+
+ buf.keys = keys;
+ buf.count = 0;
+ buf.cap = cap;
+ eina_hash_foreach(desc.hash, _ego_collect_hash_keys_cb, &buf);
+ return buf.count;
+}
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "unsafe"
+)
+
+// ElementType is the constraint for types that may be stored as elements of a
+// container Value. It covers all scalar types and nested *Value containers.
+type ElementType interface {
+ ScalarType | *Value
+}
+
+// scalarEinaTypeAny returns the Eina_Value_Type for any ScalarType value.
+// This avoids the generic constraint narrowing limitation by accepting any.
+func scalarEinaTypeAny(sample any) *C.Eina_Value_Type {
+ switch sample.(type) {
+ case int, int32:
+ return C.EINA_VALUE_TYPE_INT
+ case uint, uint32:
+ return C.EINA_VALUE_TYPE_UINT
+ case int64:
+ return C.EINA_VALUE_TYPE_INT64
+ case uint64:
+ return C.EINA_VALUE_TYPE_UINT64
+ case int16:
+ return C.EINA_VALUE_TYPE_SHORT
+ case uint16:
+ return C.EINA_VALUE_TYPE_USHORT
+ case float32:
+ return C.EINA_VALUE_TYPE_FLOAT
+ case float64:
+ return C.EINA_VALUE_TYPE_DOUBLE
+ case string:
+ return C.EINA_VALUE_TYPE_STRING
+ case bool:
+ return C.EINA_VALUE_TYPE_BOOL
+ case byte:
+ return C.EINA_VALUE_TYPE_CHAR
+ default:
+ return nil
+ }
+}
+
+// appendAnyToArray appends val (a scalar interface{} value) to the array
+// Eina_Value v. The underlying concrete type of val must be one of the
+// ScalarType cases.
+func appendAnyToArray(v *C.Eina_Value, val any) error {
+ switch u := val.(type) {
+ case int:
+ cv := C.int(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(INT) failed")
+ }
+ case int32:
+ cv := C.int(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(INT) failed")
+ }
+ case uint:
+ cv := C.uint(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(UINT) failed")
+ }
+ case uint32:
+ cv := C.uint(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(UINT) failed")
+ }
+ case int64:
+ cv := C.int64_t(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(INT64) failed")
+ }
+ case uint64:
+ cv := C.uint64_t(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(UINT64) failed")
+ }
+ case int16:
+ cv := C.short(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(SHORT) failed")
+ }
+ case uint16:
+ cv := C.ushort(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(USHORT) failed")
+ }
+ case float32:
+ cv := C.float(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(FLOAT) failed")
+ }
+ case float64:
+ cv := C.double(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(DOUBLE) failed")
+ }
+ case string:
+ cs := C.CString(u)
+ defer C.free(unsafe.Pointer(cs))
+ if C._ego_value_array_append_string(v, cs) == 0 {
+ return fmt.Errorf("efl: array pappend(STRING) failed")
+ }
+ case bool:
+ var cv C.uchar
+ if u {
+ cv = 1
+ }
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(BOOL) failed")
+ }
+ case byte: // byte == uint8; handled before uint8 would be matched
+ cv := C.char(u)
+ if C._ego_value_array_pappend(v, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: array pappend(CHAR) failed")
+ }
+ default:
+ return fmt.Errorf("efl: unsupported array element type %T", val)
+ }
+ return nil
+}
+
+// getAnyFromArray reads element at pos from the array Value and returns it as
+// an any. The concrete type matches the stored Eina subtype and must be
+// asserted back to T by the caller.
+func getAnyFromArray(v *C.Eina_Value, pos C.uint, sample any) (any, error) {
+ switch sample.(type) {
+ case int:
+ var cv C.int
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(INT) pos %d failed", pos)
+ }
+ return int(cv), nil
+ case int32:
+ var cv C.int
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(INT) pos %d failed", pos)
+ }
+ return int32(cv), nil
+ case uint:
+ var cv C.uint
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(UINT) pos %d failed", pos)
+ }
+ return uint(cv), nil
+ case uint32:
+ var cv C.uint
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(UINT) pos %d failed", pos)
+ }
+ return uint32(cv), nil
+ case int64:
+ var cv C.int64_t
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(INT64) pos %d failed", pos)
+ }
+ return int64(cv), nil
+ case uint64:
+ var cv C.uint64_t
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(UINT64) pos %d failed", pos)
+ }
+ return uint64(cv), nil
+ case int16:
+ var cv C.short
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(SHORT) pos %d failed", pos)
+ }
+ return int16(cv), nil
+ case uint16:
+ var cv C.ushort
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(USHORT) pos %d failed", pos)
+ }
+ return uint16(cv), nil
+ case float32:
+ var cv C.float
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(FLOAT) pos %d failed", pos)
+ }
+ return float32(cv), nil
+ case float64:
+ var cv C.double
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(DOUBLE) pos %d failed", pos)
+ }
+ return float64(cv), nil
+ case string:
+ cs := C._ego_value_array_get_string(v, pos)
+ if cs == nil {
+ return nil, fmt.Errorf("efl: array get_string pos %d returned nil", pos)
+ }
+ return C.GoString(cs), nil
+ case bool:
+ var cv C.uchar
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(BOOL) pos %d failed", pos)
+ }
+ return cv != 0, nil
+ case byte:
+ var cv C.char
+ if C._ego_value_array_pget(v, pos, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: array pget(CHAR) pos %d failed", pos)
+ }
+ return byte(cv), nil
+ }
+ return nil, fmt.Errorf("efl: unsupported array element type %T", sample)
+}
+
+// setAnyInHash stores val (a scalar interface{} value) under key in the hash
+// Eina_Value v.
+func setAnyInHash(v *C.Eina_Value, key string, val any) error {
+ ck := C.CString(key)
+ defer C.free(unsafe.Pointer(ck))
+
+ switch u := val.(type) {
+ case int:
+ cv := C.int(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(INT) key %q failed", key)
+ }
+ case int32:
+ cv := C.int(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(INT) key %q failed", key)
+ }
+ case uint:
+ cv := C.uint(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(UINT) key %q failed", key)
+ }
+ case uint32:
+ cv := C.uint(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(UINT) key %q failed", key)
+ }
+ case int64:
+ cv := C.int64_t(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(INT64) key %q failed", key)
+ }
+ case uint64:
+ cv := C.uint64_t(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(UINT64) key %q failed", key)
+ }
+ case int16:
+ cv := C.short(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(SHORT) key %q failed", key)
+ }
+ case uint16:
+ cv := C.ushort(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(USHORT) key %q failed", key)
+ }
+ case float32:
+ cv := C.float(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(FLOAT) key %q failed", key)
+ }
+ case float64:
+ cv := C.double(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(DOUBLE) key %q failed", key)
+ }
+ case string:
+ cs := C.CString(u)
+ defer C.free(unsafe.Pointer(cs))
+ if C._ego_value_hash_set_string(v, ck, cs) == 0 {
+ return fmt.Errorf("efl: hash set_string key %q failed", key)
+ }
+ case bool:
+ var cv C.uchar
+ if u {
+ cv = 1
+ }
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(BOOL) key %q failed", key)
+ }
+ case byte:
+ cv := C.char(u)
+ if C._ego_value_hash_pset(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return fmt.Errorf("efl: hash pset(CHAR) key %q failed", key)
+ }
+ default:
+ return fmt.Errorf("efl: unsupported hash value type %T", val)
+ }
+ return nil
+}
+
+// getAnyFromHash reads the element stored at key in the hash Value and returns
+// it as an any. The concrete type matches the stored Eina subtype.
+func getAnyFromHash(v *C.Eina_Value, key string, sample any) (any, error) {
+ ck := C.CString(key)
+ defer C.free(unsafe.Pointer(ck))
+
+ switch sample.(type) {
+ case int:
+ var cv C.int
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(INT) key %q failed", key)
+ }
+ return int(cv), nil
+ case int32:
+ var cv C.int
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(INT) key %q failed", key)
+ }
+ return int32(cv), nil
+ case uint:
+ var cv C.uint
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(UINT) key %q failed", key)
+ }
+ return uint(cv), nil
+ case uint32:
+ var cv C.uint
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(UINT) key %q failed", key)
+ }
+ return uint32(cv), nil
+ case int64:
+ var cv C.int64_t
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(INT64) key %q failed", key)
+ }
+ return int64(cv), nil
+ case uint64:
+ var cv C.uint64_t
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(UINT64) key %q failed", key)
+ }
+ return uint64(cv), nil
+ case int16:
+ var cv C.short
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(SHORT) key %q failed", key)
+ }
+ return int16(cv), nil
+ case uint16:
+ var cv C.ushort
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(USHORT) key %q failed", key)
+ }
+ return uint16(cv), nil
+ case float32:
+ var cv C.float
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(FLOAT) key %q failed", key)
+ }
+ return float32(cv), nil
+ case float64:
+ var cv C.double
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(DOUBLE) key %q failed", key)
+ }
+ return float64(cv), nil
+ case string:
+ cs := C._ego_value_hash_get_string(v, ck)
+ if cs == nil {
+ return nil, fmt.Errorf("efl: hash get_string key %q returned nil", key)
+ }
+ return C.GoString(cs), nil
+ case bool:
+ var cv C.uchar
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(BOOL) key %q failed", key)
+ }
+ return cv != 0, nil
+ case byte:
+ var cv C.char
+ if C._ego_value_hash_pget(v, ck, unsafe.Pointer(&cv)) == 0 {
+ return nil, fmt.Errorf("efl: hash pget(CHAR) key %q failed", key)
+ }
+ return byte(cv), nil
+ }
+ return nil, fmt.Errorf("efl: unsupported hash value type %T", sample)
+}
+
+// NewValueSlice creates an Eina_Value of type ARRAY whose elements have the
+// subtype inferred from T. For *Value elements the subtype is
+// EINA_VALUE_TYPE_VALUE and each element is stored as a copy inside the array.
+// The returned Value is owned and must be freed by calling Free when no longer
+// needed.
+//
+// Returns nil if EFL fails to allocate or set up the array.
+func NewValueSlice[T ElementType](vals []T) *Value {
+ var zero T
+
+ if _, isValue := any(zero).(*Value); isValue {
+ // Nested Eina_Value elements.
+ raw := C._ego_value_new(C.EINA_VALUE_TYPE_ARRAY)
+ if raw == nil {
+ return nil
+ }
+ if C._ego_value_array_setup(raw, C.EINA_VALUE_TYPE_VALUE) == 0 {
+ C._ego_value_free(raw)
+ return nil
+ }
+ for _, el := range vals {
+ sub, _ := any(el).(*Value)
+ if sub == nil || sub.ptr == nil {
+ C._ego_value_free(raw)
+ return nil
+ }
+ if C._ego_value_array_append_value(raw, sub.ptr) == 0 {
+ C._ego_value_free(raw)
+ return nil
+ }
+ }
+ return &Value{ptr: raw, owned: true}
+ }
+
+ // Scalar elements: determine Eina subtype from the zero value of T.
+ st := scalarEinaTypeAny(any(zero))
+ if st == nil {
+ return nil
+ }
+ raw := C._ego_value_new(C.EINA_VALUE_TYPE_ARRAY)
+ if raw == nil {
+ return nil
+ }
+ if C._ego_value_array_setup(raw, st) == 0 {
+ C._ego_value_free(raw)
+ return nil
+ }
+ for _, el := range vals {
+ if err := appendAnyToArray(raw, any(el)); err != nil {
+ C._ego_value_free(raw)
+ return nil
+ }
+ }
+ return &Value{ptr: raw, owned: true}
+}
+
+// ValueSlice reads the elements of the array Value v and returns them as a Go
+// slice of type T. Returns an error if v is nil, not an array, or if any
+// element cannot be converted to T.
+//
+// For *Value elements each returned *Value is an owned copy that must be freed
+// by the caller.
+func ValueSlice[T ElementType](v *Value) ([]T, error) {
+ if v == nil || v.ptr == nil {
+ return nil, errors.New("efl: ValueSlice called on nil Value")
+ }
+ if v.Kind() != ValueKindArray {
+ return nil, fmt.Errorf("efl: ValueSlice: value is not an array (Kind=%v)", v.Kind())
+ }
+
+ n := int(C._ego_value_array_count(v.ptr))
+ result := make([]T, n)
+ var zero T
+
+ if _, isValue := any(zero).(*Value); isValue {
+ for i := range n {
+ out := C._ego_value_array_new_value_at(v.ptr, C.uint(i))
+ if out == nil {
+ for j := range i {
+ if pv, _ := any(result[j]).(*Value); pv != nil {
+ pv.Free()
+ }
+ }
+ return nil, fmt.Errorf("efl: ValueSlice: new_value_at failed at index %d", i)
+ }
+ result[i] = any(&Value{ptr: out, owned: true}).(T) //nolint:forcetypeassert
+ }
+ return result, nil
+ }
+
+ // Scalar path: use the zero value as the type discriminator.
+ for i := range n {
+ raw, err := getAnyFromArray(v.ptr, C.uint(i), any(zero))
+ if err != nil {
+ return nil, err
+ }
+ result[i] = raw.(T) //nolint:forcetypeassert
+ }
+ return result, nil
+}
+
+// NewValueMap creates an Eina_Value of type HASH whose values have the subtype
+// inferred from T. Keys are always C strings. For *Value elements the subtype
+// is EINA_VALUE_TYPE_VALUE and each element is copied into the hash.
+// The returned Value is owned and must be freed by calling Free.
+//
+// Returns nil if EFL fails to set up the hash.
+func NewValueMap[T ElementType](vals map[string]T) *Value {
+ var zero T
+
+ if _, isValue := any(zero).(*Value); isValue {
+ raw := C._ego_value_new(C.EINA_VALUE_TYPE_HASH)
+ if raw == nil {
+ return nil
+ }
+ if C._ego_value_hash_setup(raw, C.EINA_VALUE_TYPE_VALUE) == 0 {
+ C._ego_value_free(raw)
+ return nil
+ }
+ for k, el := range vals {
+ sub, _ := any(el).(*Value)
+ if sub == nil || sub.ptr == nil {
+ C._ego_value_free(raw)
+ return nil
+ }
+ ck := C.CString(k)
+ // For EINA_VALUE_TYPE_VALUE subtype, pset expects a pointer to
+ // the Eina_Value struct (not pointer-to-pointer).
+ ok := C._ego_value_hash_pset(raw, ck, unsafe.Pointer(sub.ptr)) != 0
+ C.free(unsafe.Pointer(ck))
+ if !ok {
+ C._ego_value_free(raw)
+ return nil
+ }
+ }
+ return &Value{ptr: raw, owned: true}
+ }
+
+ // Scalar path.
+ st := scalarEinaTypeAny(any(zero))
+ if st == nil {
+ return nil
+ }
+ raw := C._ego_value_new(C.EINA_VALUE_TYPE_HASH)
+ if raw == nil {
+ return nil
+ }
+ if C._ego_value_hash_setup(raw, st) == 0 {
+ C._ego_value_free(raw)
+ return nil
+ }
+ for k, el := range vals {
+ if err := setAnyInHash(raw, k, any(el)); err != nil {
+ C._ego_value_free(raw)
+ return nil
+ }
+ }
+ return &Value{ptr: raw, owned: true}
+}
+
+// ValueMap reads all key-value pairs from the hash Value v and returns them as
+// a Go map with string keys. Returns an error if v is nil or not a hash Value.
+//
+// For *Value elements each returned *Value is an owned copy that must be freed
+// by the caller.
+func ValueMap[T ElementType](v *Value) (map[string]T, error) {
+ if v == nil || v.ptr == nil {
+ return nil, errors.New("efl: ValueMap called on nil Value")
+ }
+ if v.Kind() != ValueKindHash {
+ return nil, fmt.Errorf("efl: ValueMap: value is not a hash (Kind=%v)", v.Kind())
+ }
+
+ n := int(C._ego_value_hash_population(v.ptr))
+ if n == 0 {
+ return map[string]T{}, nil
+ }
+
+ // Collect internal key pointers — valid for the lifetime of v.
+ ckeys := make([]*C.char, n)
+ got := int(C._ego_value_hash_keys(v.ptr, &ckeys[0], C.int(n)))
+
+ result := make(map[string]T, got)
+ var zero T
+
+ if _, isValue := any(zero).(*Value); isValue {
+ for _, ck := range ckeys[:got] {
+ key := C.GoString(ck)
+ out := C._ego_value_hash_new_value_at(v.ptr, ck)
+ if out == nil {
+ for _, pv := range result {
+ if w, _ := any(pv).(*Value); w != nil {
+ w.Free()
+ }
+ }
+ return nil, fmt.Errorf("efl: ValueMap: new_value_at failed for key %q", key)
+ }
+ result[key] = any(&Value{ptr: out, owned: true}).(T) //nolint:forcetypeassert
+ }
+ return result, nil
+ }
+
+ // Scalar path.
+ for _, ck := range ckeys[:got] {
+ key := C.GoString(ck)
+ raw, err := getAnyFromHash(v.ptr, key, any(zero))
+ if err != nil {
+ return nil, err
+ }
+ result[key] = raw.(T) //nolint:forcetypeassert
+ }
+ return result, nil
+}
diff --git a/efl/value_test.go b/efl/value_test.go
index 2bf4b0e..a671311 100644
--- a/efl/value_test.go
+++ b/efl/value_test.go
@@ -361,6 +361,91 @@ func TestValueGetNil(t *testing.T) {
}
}
+// TestNewValueSliceInt verifies that an int slice survives a NewValueSlice /
+// ValueSlice round-trip.
+func TestNewValueSliceInt(t *testing.T) {
+ v := NewValueSlice([]int{10, 20, 30})
+ if v == nil {
+ t.Fatal("nil")
+ }
+ defer v.Free()
+ if v.Kind() != ValueKindArray {
+ t.Errorf("Kind=%v", v.Kind())
+ }
+ got, err := ValueSlice[int](v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(got) != 3 || got[0] != 10 || got[1] != 20 || got[2] != 30 {
+ t.Errorf("got %v", got)
+ }
+}
+
+// TestNewValueSliceString verifies that a string slice survives a
+// NewValueSlice / ValueSlice round-trip.
+func TestNewValueSliceString(t *testing.T) {
+ v := NewValueSlice([]string{"a", "b", "c"})
+ if v == nil {
+ t.Fatal("nil")
+ }
+ defer v.Free()
+ got, err := ValueSlice[string](v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "c" {
+ t.Errorf("got %v", got)
+ }
+}
+
+// TestNewValueMapString verifies that a string map survives a NewValueMap /
+// ValueMap round-trip.
+func TestNewValueMapString(t *testing.T) {
+ v := NewValueMap(map[string]string{"key": "val"})
+ if v == nil {
+ t.Fatal("nil")
+ }
+ defer v.Free()
+ if v.Kind() != ValueKindHash {
+ t.Errorf("Kind=%v", v.Kind())
+ }
+ got, err := ValueMap[string](v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got["key"] != "val" {
+ t.Errorf("got[key]=%q", got["key"])
+ }
+}
+
+// TestNewValueSliceNested verifies that a slice of *Value (nested arrays)
+// survives a NewValueSlice / ValueSlice round-trip.
+func TestNewValueSliceNested(t *testing.T) {
+ inner1 := NewValueSlice([]int{1, 2})
+ inner2 := NewValueSlice([]int{3, 4})
+ outer := NewValueSlice([]*Value{inner1, inner2})
+ if outer == nil {
+ t.Fatal("nil")
+ }
+ defer outer.Free()
+ got, err := ValueSlice[*Value](outer)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(got) != 2 {
+ t.Fatalf("len=%d", len(got))
+ }
+ defer got[0].Free()
+ defer got[1].Free()
+ inner, err := ValueSlice[int](got[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(inner) != 2 || inner[0] != 1 || inner[1] != 2 {
+ t.Errorf("inner=%v", inner)
+ }
+}
+
// TestWrapValue verifies that a non-owned wrapper over an existing Eina_Value
// pointer can read back the stored value and that Free on it does not touch
// the underlying allocation.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.