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.

Reply via email to