Switch to object-based command handling
authorMyhailo Danylenko <isbear@ukrpost.net>
Mon, 12 Apr 2010 00:29:44 +0300
changeset 109 2d2111cb5109
parent 108 dedb85093c02
child 110 bd9f24178d67
Switch to object-based command handling Hopefully, I have not introduced bugs here
config.h.in
examples/room_priv.lua
lua.c
--- a/config.h.in	Sun Apr 11 21:54:55 2010 +0300
+++ b/config.h.in	Mon Apr 12 00:29:44 2010 +0300
@@ -19,6 +19,12 @@
 #ifndef MLUA_CONFIG_H
 #define MLUA_CONFIG_H
 
+#include <mcabber/config.h>
+
+#if MCABBER_BRANCH_EXPERIMENTAL && MCABBER_API_VERSION >= 27
+#  define HAVE_CMD_ID
+#endif
+
 // define this to enable debugging output
 #cmakedefine DEBUG
 
--- a/examples/room_priv.lua	Sun Apr 11 21:54:55 2010 +0300
+++ b/examples/room_priv.lua	Mon Apr 12 00:29:44 2010 +0300
@@ -2,10 +2,11 @@
 -- ROOM NICK COMPLETION
 
 -- global
-room_cid = main.command ( 'priv',
+room_cid = main.add_category ()
+main.command ( 'priv',
 	function ( args )
 		main.run ( 'room privmsg ' .. args )
-	end, false, {} )
+	end, false, room_cid )
 
 commands_help['priv'] = "nick message\n\nSends private message to room participant. Nick completion available! ;)"
 
--- a/lua.c	Sun Apr 11 21:54:55 2010 +0300
+++ b/lua.c	Mon Apr 12 00:29:44 2010 +0300
@@ -62,7 +62,7 @@
 
 static module_info_t info_lua_experimental = {
 	.branch      = "experimental",
-	.api         = 25,
+	.api         = 27,
 	.version     = PROJECT_VERSION,
 	.description = DESCRIPTION,
 	.requires    = NULL,
@@ -802,12 +802,16 @@
 #define MLUA_YESNO_POS ( 21 )
 
 typedef struct {
-	int        reference;
+	int        cbref;
 	int        parse_args;
+#ifdef HAVE_CMD_ID
+	gpointer   cmid;
+#else
+	int        nameref;
+#endif
+	int        selfref;
 	lua_State *L;
-} lua_command_callback_t;
-
-static GSList *lua_added_commands = NULL;
+} lua_command_t;
 
 static GSList *lua_added_categories = NULL;
 
@@ -925,9 +929,9 @@
 /// Function to handle newly registered command.
 /// Argument type passed depends on how command is registered.
 /// A: string (arguments) or command arguments table
-static void lua_main_command_handler (char *args, lua_command_callback_t *cb)
+static void lua_main_command_handler (char *args, lua_command_t *cb)
 {
-	lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
+	lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->cbref);
 
 	if (cb->parse_args)
 		luaL_pushargs (cb->L, args);
@@ -1020,75 +1024,123 @@
 }
 
 /// main.command
-/// Associates or breaks association between mcabber command name and lua function.
-/// To unregister command omit function argument.
-/// If you specify a third argument, table (even empty), function will return completion group id or nothing.
-/// You can also specify a string name (see completion type) instead of table, for non-builtin, you can just pass integer id.
+/// Associates mcabber command name and lua function.
+/// As a completion you can also specify a string name (see completion type) instead of table, for non-builtin, you can just pass integer id.
 /// Note, that for now there are no way to unregister completion group, so, resources can be exausted easily.
 /// Also note, that it ignores keys in a completion table.
-/// A: string (command name), command function (optional), boolean (parse args flag, optional), table (completions, optional)/completion type (or integer comletion group id, optional)
-/// R: completion type (integer completion group id or string for builtin types, optional)
+/// A: string (command name), command function, boolean (parse args flag, optional), table (completions, optional)/completion type (or integer comletion group id, optional)
+/// R: userdata (command object)
 static int lua_main_command (lua_State *L)
 {
-	const char             *name = luaL_checkstring (L, 1); // XXX: to_utf8? looks like no :/
-	lua_command_callback_t *cb;
-	int                     top  = lua_gettop (L);
-	if (top > 1 && !(top == 2 && lua_isnil (L, 2))) { // Register
-		guint cid = 0;
-		int parse = 0;
-		luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
+	const char    *name  = luaL_checkstring (L, 1); // XXX: to_utf8? looks like no :/
+	lua_command_t *cb;
+	int            top   = lua_gettop (L);
+	guint          cid   = 0;
+	int            parse = 0;
+	luaL_argcheck (L, lua_isfunction (L, 2), 2, "function expected");
 
-		if (top > 2) { // parse flag provided
-			parse = lua_toboolean (L, 3);
+	if (top > 2) { // parse flag provided
+		parse = lua_toboolean (L, 3);
 
-			if (top > 3) { // Completions provided
-				if (lua_type (L, 4) == LUA_TTABLE) {
-					cid = compl_new_category ();
-					if (cid) {
-						lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
-						lua_pushnil (L);
-						while (lua_next (L, 4)) {
-							char *word = to_utf8 (luaL_checkstring (L, -1));
-							compl_add_category_word (cid, word);
-							lua_pop (L, 1);
-							g_free (word);
-						}
+		if (top > 3) { // Completions provided
+			if (lua_type (L, 4) == LUA_TTABLE) {
+				cid = compl_new_category ();
+				if (cid) {
+					lua_added_categories = g_slist_prepend (lua_added_categories, (gpointer) cid);
+					lua_pushnil (L);
+					while (lua_next (L, 4)) {
+						char *word = to_utf8 (luaL_checkstring (L, -1));
+						compl_add_category_word (cid, word);
+						lua_pop (L, 1);
+						g_free (word);
 					}
-				} else
-					cid = luaL_checkenum (L, 4, lua_completion_type);
-			}
-		}
-
-		cb = luaL_malloc (L, sizeof (lua_command_callback_t));
-		lua_pushvalue (L, 2);
-		cb->reference  = luaL_ref (L, LUA_REGISTRYINDEX);
-		cb->parse_args = parse;
-		cb->L          = L;
-		cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
-
-		lua_added_commands = g_slist_prepend (lua_added_commands, g_strdup (name));
-
-		if (cid) {
-			luaL_pushenum (L, cid, lua_completion_type);
-			return 1;
-		}
-	} else { // Unregister
-		GSList *el = g_slist_find_custom (lua_added_commands, name, (GCompareFunc) g_strcmp0);
-
-		cb = cmd_del (name);
-		if (cb) {
-			luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
-			luaL_free (cb->L, cb);
-		}
-
-		if (el) {
-			g_free (el->data);
-			lua_added_commands = g_slist_delete_link (lua_added_commands, el);
+				}
+			} else
+				cid = luaL_checkenum (L, 4, lua_completion_type);
 		}
 	}
+	
+	cb = lua_newuserdata (L, sizeof (lua_command_t));
+	luaL_getmetatable (L, "mcabber.command");
+	lua_setmetatable (L, -2);
+	lua_pushvalue (L, 2);
+	cb -> cbref      = luaL_ref (L, LUA_REGISTRYINDEX);
+	lua_pushvalue (L, -1);
+	cb -> selfref    = luaL_ref (L, LUA_REGISTRYINDEX);
+	cb -> parse_args = parse;
+	cb -> L          = L;
+#ifdef HAVE_CMD_ID
+	cb -> cmid       = cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
+#else
+	lua_pushvalue (L, 1);
+	cb -> nameref    = luaL_ref (L, LUA_REGISTRYINDEX);
+	cmd_add (name, "", cid, 0, (void (*) (char *p)) lua_main_command_handler, cb);
+#endif
+
+	return 1;
+}
+
+static void lua_mcabber_unregister_command (lua_State *L, lua_command_t *cb)
+{
+#ifdef HAVE_CMD_ID
+	if (cb -> cmid)
+		cmd_del (cb -> cmid);
+#else
+	const char *name;
+
+	if (cb -> nameref == LUA_NOREF)
+		return;
+
+	lua_rawgeti (L, LUA_REGISTRYINDEX, cb -> nameref);
+	name = lua_tostring (L, -1);
+	cmd_del (name);
+	lua_pop (L, 1);
+#endif
+}
+
+/// command:del
+/// Unregisters given command from mcabber. Object will be destroyed later.
+static int lua_mcabber_command_del (lua_State *L)
+{
+	lua_command_t *cb = luaL_checkudata (L, 1, "mcabber.command");
+	luaL_argcheck (L, cb != NULL, 1, "mcabber command object expected");
+	lua_mcabber_unregister_command (L, cb);
+	if (cb -> selfref != LUA_NOREF) {
+		luaL_unref (L, LUA_REGISTRYINDEX, cb -> selfref);
+		cb -> selfref = LUA_NOREF;
+	}
 	return 0;
 }
 
+static int lua_mcabber_command_gc (lua_State *L)
+{
+	lua_command_t *cb = luaL_checkudata (L, 1, "mcabber.command");
+	luaL_argcheck (L, cb != NULL, 1, "mcabber command object expected");
+	lua_mcabber_unregister_command (L, cb);
+#ifndef HAVE_CMD_ID
+	if (cb -> nameref != LUA_NOREF)
+		luaL_unref (L, LUA_REGISTRYINDEX, cb -> nameref);
+#endif
+	if (cb -> cbref != LUA_NOREF)
+		luaL_unref (L, LUA_REGISTRYINDEX, cb -> cbref);
+	return 0;
+}
+
+static const luaL_Reg lua_mcabber_command_reg_m[] = {
+	{ "del",  lua_mcabber_command_del },
+	{ "__gc", lua_mcabber_command_gc  },
+	{ NULL,   NULL                 },
+};
+
+static void lua_command_init (lua_State *L)
+{
+	luaL_newmetatable (L, "mcabber.command");
+	lua_pushvalue (L, -1);
+	lua_setfield (L, -2, "__index");
+	luaL_register (L, NULL, lua_mcabber_command_reg_m);
+	lua_pop (L, 1);
+}
+
 // TIMER
 
 typedef struct {
@@ -1700,7 +1752,8 @@
 	lua_lm_log_handler_id = g_log_set_handler ("lua-lm", G_LOG_LEVEL_MASK, (GLogFunc) lua_lm_log_handler, NULL);
 #endif
 
-	lua_hook_init (lua);
+	lua_hook_init    (lua);
+	lua_command_init (lua);
 
 	{
 		char *initfile = expand_filename (settings_opt_get ("lua_init_filename"));
@@ -1766,16 +1819,6 @@
 	g_free (xmlns);
 }
 
-static void lua_commands_destroy (char *name, gpointer ignore)
-{
-	lua_command_callback_t *cb = cmd_del (name);
-	if (cb) {
-		luaL_unref (cb->L, LUA_REGISTRYINDEX, cb->reference);
-		luaL_free (cb->L, cb);
-	}
-	g_free (name);
-}
-
 static void lua_categories_destroy (guint id, gpointer ignore)
 {
 	compl_del_category (id);
@@ -1789,7 +1832,7 @@
 		};
 		hk_run_handlers ("hook-lua-quit", args);
 
-		// hook handlers will be unregistered upon objects destruction
+		// hook handlers and commands will be unregistered upon objects destruction
 
 		g_slist_foreach (lua_bgreads, (GFunc) lua_bgreads_destroy, NULL);
 		g_slist_free (lua_bgreads);
@@ -1808,19 +1851,15 @@
 		g_slist_free (lua_added_features);
 		lua_added_features = NULL;
 
-		g_slist_foreach (lua_added_commands, (GFunc) lua_commands_destroy, NULL);
-		g_slist_free (lua_added_commands);
-		lua_added_commands = NULL;
+		cmd_del ("lua");
+
+		lua_close (lua);
+		lua = NULL;
 
 		g_slist_foreach (lua_added_categories, (GFunc) lua_categories_destroy, NULL);
 		g_slist_free (lua_added_categories);
 		lua_added_categories = NULL;
 
-		cmd_del ("lua");
-
-		lua_close (lua);
-		lua = NULL;
-
 #ifdef LLM_LOG_HANDLER
 		// FIXME: shouldn't be here
 		g_log_remove_handler ("lua-lm", lua_lm_log_handler_id);