diff --git a/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index f52b03e4..8ccfaa9a 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -2039,6 +2039,108 @@ static const luaL_Reg proc_utils_funcs[] = { { NULL, NULL } }; +/* Properties */ + +static int +properties_new (lua_State *L) +{ + WpProperties *props; + + if (lua_type (L, 1) != LUA_TNONE && lua_type (L, 1) != LUA_TNIL) { + luaL_checktype (L, 1, LUA_TTABLE); + props = wplua_table_to_properties (L, 1); + } else { + props = wp_properties_new_empty (); + } + + wplua_pushboxed (L, WP_TYPE_PROPERTIES, props); + return 1; +} + +static int +properties_get_boolean (lua_State *L) +{ + WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES); + const char *key = luaL_checkstring (L, 2); + const char *val = wp_properties_get (props, key); + if (val) + lua_pushboolean (L, spa_atob (val)); + else + lua_pushnil (L); + return 1; +} + +static int +properties_get_int (lua_State *L) +{ + WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES); + const char *key = luaL_checkstring (L, 2); + const char *val = wp_properties_get (props, key); + if (val) { + gint64 int_val = 0; + if (spa_atoi64 (val, &int_val, 10)) + lua_pushinteger (L, int_val); + else + lua_pushnil (L); + } else { + lua_pushnil (L); + } + return 1; +} + +static int +properties_get_float (lua_State *L) +{ + WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES); + const char *key = luaL_checkstring (L, 2); + const char *val = wp_properties_get (props, key); + if (val) { + double d_val = 0; + if (spa_atod (val, &d_val)) + lua_pushnumber (L, d_val); + else + lua_pushnil (L); + } else { + lua_pushnil (L); + } + return 1; +} + +static int +properties_get_count (lua_State *L) +{ + WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES); + lua_pushinteger (L, wp_properties_get_count (props)); + return 1; +} + +static int +properties_copy (lua_State *L) +{ + WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES); + WpProperties *copy = wp_properties_copy (props); + wplua_pushboxed (L, WP_TYPE_PROPERTIES, copy); + return 1; +} + +static int +properties_parse (lua_State *L) +{ + WpProperties *props = wplua_checkboxed (L, 1, WP_TYPE_PROPERTIES); + wplua_properties_to_table (L, props); + return 1; +} + +static const luaL_Reg properties_funcs[] = { + { "get_boolean", properties_get_boolean }, + { "get_int", properties_get_int }, + { "get_float", properties_get_float }, + { "get_count", properties_get_count }, + { "copy", properties_copy }, + { "parse", properties_parse }, + { NULL, NULL } +}; + /* WpSettings */ static int @@ -3013,6 +3115,8 @@ wp_lua_scripting_api_init (lua_State *L) NULL, proc_info_funcs); wplua_register_type_methods (L, WP_TYPE_ITERATOR, NULL, iterator_funcs); + wplua_register_type_methods (L, WP_TYPE_PROPERTIES, + properties_new, properties_funcs); if (!wplua_load_uri (L, URI_API, &error) || !wplua_pcall (L, 0, 0, &error)) { diff --git a/modules/module-lua-scripting/api/api.lua b/modules/module-lua-scripting/api/api.lua index e4e41bcf..fe886bc3 100644 --- a/modules/module-lua-scripting/api/api.lua +++ b/modules/module-lua-scripting/api/api.lua @@ -217,6 +217,7 @@ SANDBOX_EXPORT = { Conf = WpConf, JsonUtils = JsonUtils, ProcUtils = ProcUtils, + Properties = WpProperties_new, SimpleEventHook = WpSimpleEventHook_new, AsyncEventHook = WpAsyncEventHook_new, } diff --git a/modules/module-lua-scripting/wplua/boxed.c b/modules/module-lua-scripting/wplua/boxed.c index 138ec1ad..c327a575 100644 --- a/modules/module-lua-scripting/wplua/boxed.c +++ b/modules/module-lua-scripting/wplua/boxed.c @@ -29,8 +29,9 @@ _wplua_gboxed___index (lua_State *L) GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed"); luaL_argcheck (L, obj_v != NULL, 1, "expected userdata storing GValue"); - const gchar *key = luaL_checkstring (L, 2); + const gchar *key = luaL_tolstring (L, 2, NULL); GType type = G_VALUE_TYPE (obj_v); + GType boxed_type = type; lua_CFunction func = NULL; GHashTable *vtables; @@ -53,6 +54,99 @@ _wplua_gboxed___index (lua_State *L) lua_pushcfunction (L, func); return 1; } + + /* If WpProperties type, just return the property value for that key */ + if (boxed_type == WP_TYPE_PROPERTIES) { + WpProperties * props = g_value_get_boxed (obj_v); + const gchar *val = wp_properties_get (props, key); + lua_pushstring (L, val); + return 1; + } + + return 0; +} + +static int +_wplua_gboxed___newindex (lua_State *L) +{ + GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed"); + luaL_argcheck (L, obj_v != NULL, 1, + "expected userdata storing GValue"); + const gchar *key = luaL_tolstring (L, 2, NULL); + GType type = G_VALUE_TYPE (obj_v); + + /* Set property value */ + if (type == WP_TYPE_PROPERTIES) { + WpProperties * props = g_value_dup_boxed (obj_v); + g_autofree gchar *val = NULL; + luaL_checkany (L, 3); + + switch (lua_type (L, 3)) { + case LUA_TNIL: + break; + case LUA_TUSERDATA: { + GValue *v = lua_touserdata (L, 3); + gpointer p = g_value_peek_pointer (v); + val = g_strdup_printf ("%p", p); + break; + } + default: + val = g_strdup (luaL_tolstring (L, 3, NULL)); + break; + } + + props = wp_properties_ensure_unique_owner (props); + wp_properties_set (props, key, val); + g_value_take_boxed (obj_v, props); + } else { + luaL_error (L, "cannot assign property '%s' to boxed type %s", + key, g_type_name (type)); + } + return 0; +} + +static int +properties_iterator_next (lua_State *L) +{ + WpIterator *it = wplua_checkboxed (L, 1, WP_TYPE_ITERATOR); + g_auto (GValue) item = G_VALUE_INIT; + if (wp_iterator_next (it, &item)) { + WpPropertiesItem *si = g_value_get_boxed (&item); + const gchar *k = wp_properties_item_get_key (si); + const gchar *v = wp_properties_item_get_value (si); + lua_pushstring (L, k); + lua_pushstring (L, v); + return 2; + } else { + lua_pushnil (L); + lua_pushnil (L); + return 2; + } +} + +static int +push_properties_wpiterator (lua_State *L, WpIterator *it) +{ + lua_pushcfunction (L, properties_iterator_next); + wplua_pushboxed (L, WP_TYPE_ITERATOR, it); + return 2; +} + +static int +_wplua_gboxed___pairs (lua_State *L) +{ + GValue *obj_v = _wplua_togvalue_userdata_named (L, 1, G_TYPE_BOXED, "GBoxed"); + luaL_argcheck (L, obj_v != NULL, 1, + "expected userdata storing GValue"); + GType type = G_VALUE_TYPE (obj_v); + + if (type == WP_TYPE_PROPERTIES) { + WpProperties * props = g_value_get_boxed (obj_v); + WpIterator *it = wp_properties_new_iterator (props); + return push_properties_wpiterator (L, it); + } else { + luaL_error (L, "cannot do pairs of boxed type %s", g_type_name (type)); + } return 0; } @@ -69,6 +163,8 @@ _wplua_init_gboxed (lua_State *L) { "__gc", _wplua_gvalue_userdata___gc }, { "__eq", _wplua_gboxed___eq }, { "__index", _wplua_gboxed___index }, + { "__newindex", _wplua_gboxed___newindex }, + { "__pairs", _wplua_gboxed___pairs }, { NULL, NULL } }; diff --git a/tests/wplua/meson.build b/tests/wplua/meson.build index 5f96ef04..5bbd5e5c 100644 --- a/tests/wplua/meson.build +++ b/tests/wplua/meson.build @@ -64,3 +64,9 @@ test( args: ['lua-api-tests', 'event-hooks.lua'], env: common_env, ) +test( + 'test-lua-properties', + script_tester, + args: ['lua-api-tests', 'properties.lua'], + env: common_env, +) diff --git a/tests/wplua/scripts/properties.lua b/tests/wplua/scripts/properties.lua new file mode 100644 index 00000000..3389863a --- /dev/null +++ b/tests/wplua/scripts/properties.lua @@ -0,0 +1,93 @@ +-- create empty properties +props = Properties () + +-- set nil +props["key-nil"] = nil +assert (props["key-nil"] == nil) + +-- set bool +props["key-bool"] = false +assert (props["key-bool"] == "false") +assert (props:get_boolean ("key-bool") == false) +props["key-bool"] = true +assert (props["key-bool"] == "true") +assert (props:get_boolean ("key-bool") == true) + +-- set int +props["key-int"] = 4 +assert (props["key-int"] == "4") +assert (props:get_int ("key-int") == 4) + +-- set float +props["key-float"] = 3.14 +val = props:get_float ("key-float") +assert (val > 3.13 and val < 3.15) + +-- set string +props["key-string"] = "value" +assert (props["key-string"] == "value") +assert (props:get_boolean ("key-string") == false) +assert (props:get_int ("key-string") == nil) +assert (props:get_float ("key-string") == nil) + +-- copy +copy = props:copy () +assert (copy["key-nil"] == nil) +assert (copy:get_boolean ("key-bool") == true) +assert (copy:get_int ("key-int") == 4) +val = copy:get_float ("key-float") +assert (val > 3.13 and val < 3.15) +assert (copy["key-string"] == "value") + +-- remove int property +props["key-int"] = nil +assert (props["key-int"] == nil) +assert (copy:get_int ("key-int") == 4) + +-- create properties from table +props = Properties { + ["key0"] = nil, + ["key1"] = false, + ["key2"] = 64, + ["key3"] = 2.71, + ["key4"] = "string", +} +assert (props["key0"] == nil) +assert (props:get_boolean ("key1") == false) +assert (props:get_int ("key2") == 64) +val = props:get_float ("key3") +assert (val > 2.70 and val < 2.72) +assert (props["key4"] == "string") + +-- count +assert (props:get_count () == 4) + +-- parse +parsed = props:parse () +assert (parsed["key0"] == nil) +assert (parsed["key1"] == "false") +assert (tonumber (parsed["key2"]) == 64) +val = tonumber (parsed["key3"]) +assert (val > 2.70 and val < 2.72) +assert (parsed["key4"] == "string") + +-- pairs +values = {} +for k, v in pairs (props) do + values [k] = v +end +assert (values["key0"] == nil) +assert (values["key1"] == "false") +assert (tonumber (values["key2"]) == 64) +val = tonumber (values["key3"]) +assert (val > 2.70 and val < 2.72) +assert (values["key4"] == "string") + +-- Make sure the reference changes are also updated +local properties = Properties () +properties["key"] = "value" +assert (properties["key"] == "value") +local properties2 = properties +properties2["key"] = "another-value" +assert (properties2["key"] == "another-value") +assert (properties["key"] == "another-value")