From 5a89265623214164fa7ce36721de05183d53058d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Thu, 26 Dec 2013 16:56:45 +0100
Subject: Sluift: Refactoring

- Rename boot.lua to core.lua
- Support Lua 5.2
- Support version prefixes for sluift module
- Add Client:process_events

Change-Id: I3fa6d06d1dbdf86f65b9f4203bd2ec5b5526b104

diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct
index 9eeaabe..8c5258e 100644
--- a/BuildTools/SCons/SConstruct
+++ b/BuildTools/SCons/SConstruct
@@ -422,7 +422,7 @@ if env.get("lua_includedir", None) :
 	lua_flags["CPPPATH"] = [env["lua_includedir"]]
 lua_conf_env.MergeFlags(lua_flags)
 conf = Configure(lua_conf_env)
-if not env.get("lua_force_bundled", False) and conf.CheckCXXHeader("lua.hpp") and conf.CheckLib(env["lua_libname"]) :
+if not env.get("lua_force_bundled", False) and conf.CheckLibWithHeader(env["lua_libname"], "lua.hpp", "cxx", autoadd = 0) :
 	env["HAVE_LUA"] = 1
 	env["LUA_FLAGS"] = { "LIBS": [env["lua_libname"]] }
 	lua_version = GetVersion(conf, "LUA_VERSION_NUM", "lua.h")
diff --git a/Sluift/.gitignore b/Sluift/.gitignore
index e5fd1e5..940fc86 100644
--- a/Sluift/.gitignore
+++ b/Sluift/.gitignore
@@ -2,6 +2,6 @@ lua.c
 sluift_dll.cpp
 sluift
 dll.c
-boot.c
+core.c
 dll/
 exe/
diff --git a/Sluift/Lua/LuaUtils.cpp b/Sluift/Lua/LuaUtils.cpp
index 7052abe..b00ab56 100644
--- a/Sluift/Lua/LuaUtils.cpp
+++ b/Sluift/Lua/LuaUtils.cpp
@@ -22,7 +22,7 @@ static const std::string INDENT = "  ";
 
 void Swift::Lua::registerTableToString(lua_State* L, int index) {
 	index = Lua::absoluteOffset(L, index);
-	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.bootIndex);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
 	lua_getfield(L, -1, "register_table_tostring");
 	lua_pushvalue(L, index);
 	if (lua_pcall(L, 1, 0, 0) != 0) {
@@ -33,7 +33,7 @@ void Swift::Lua::registerTableToString(lua_State* L, int index) {
 
 void Swift::Lua::registerGetByTypeIndex(lua_State* L, int index) {
 	index = Lua::absoluteOffset(L, index);
-	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.bootIndex);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
 	lua_getfield(L, -1, "register_get_by_type_index");
 	lua_pushvalue(L, index);
 	if (lua_pcall(L, 1, 0, 0) != 0) {
diff --git a/Sluift/SConscript b/Sluift/SConscript
index 1a29e43..c8f1108 100644
--- a/Sluift/SConscript
+++ b/Sluift/SConscript
@@ -32,7 +32,7 @@ elif env["SCONS_STAGE"] == "build" :
 		"ElementConvertors/CommandConvertor.cpp",
 		"ClientHelpers.cpp",
 		"SluiftClient.cpp",
-		"boot.c",
+		"core.c",
 		"client.cpp",
 		"sluift.cpp"
 	]
@@ -42,6 +42,8 @@ elif env["SCONS_STAGE"] == "build" :
 	sluift_env.UseFlags(env.get("LUA_FLAGS", {}))
 	sluift_env.UseFlags(env["SWIFTEN_FLAGS"])
 	sluift_env.UseFlags(env["SWIFTEN_DEP_FLAGS"])
+	# Support compilation on both Lua 5.1 and Lua 5.2
+	sluift_env.Append(CPPDEFINES = ["LUA_COMPAT_ALL"])
 	if sluift_env["PLATFORM"] == "win32" :
 		sluift_env.Append(CPPDEFINES = ["SLUIFT_BUILD_DLL"])
 
@@ -62,7 +64,7 @@ elif env["SCONS_STAGE"] == "build" :
 		f.close()
 	sluift_env.Command("lua.c", ["#/3rdParty/Lua/src/lua.c", sluift_env.Value(sluift_env["SLUIFT_VERSION"])], env.Action(patchLua, cmdstr = "$GENCOMSTR"))
 
-	# Generate boot.cpp
+	# Generate core.cpp
 	def generate_embedded_lua(env, target, source) :
 		f = open(source[0].abspath, "r")
 		data = f.read()
@@ -70,7 +72,7 @@ elif env["SCONS_STAGE"] == "build" :
 		f = open(target[0].abspath, "w")
 		f.write('const char ' + source[0].name.replace(".", "_") + "[] = \"" + data.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"') + "\";")
 		f.close()
-	sluift_env.Command("boot.c", ["boot.lua"], env.Action(generate_embedded_lua, cmdstr="$GENCOMSTR"))
+	sluift_env.Command("core.c", ["core.lua"], env.Action(generate_embedded_lua, cmdstr="$GENCOMSTR"))
 
 	if sluift_env.get("HAVE_READLINE", False) :
 		sluift_env.Append(CPPDEFINES = ["LUA_USE_READLINE"])
diff --git a/Sluift/SluiftGlobals.h b/Sluift/SluiftGlobals.h
index 03c1c1a..18a90c2 100644
--- a/Sluift/SluiftGlobals.h
+++ b/Sluift/SluiftGlobals.h
@@ -19,6 +19,6 @@ namespace Swift {
 		LuaElementConvertors elementConvertor;
 		SimpleEventLoop eventLoop;
 		BoostNetworkFactories networkFactories;
-		int bootIndex;
+		int coreLibIndex;
 	};
 }
diff --git a/Sluift/boot.lua b/Sluift/boot.lua
deleted file mode 100644
index a28a189..0000000
--- a/Sluift/boot.lua
+++ /dev/null
@@ -1,454 +0,0 @@
---[[
-	Copyright (c) 2013 Remko Tronçon
-	Licensed under the GNU General Public License.
-	See the COPYING file for more information.
---]]
-
-local Client = {}
-local PubSub = {}
-local PubSubNode = {}
-
---------------------------------------------------------------------------------
--- Utility methods
---------------------------------------------------------------------------------
-
-local function merge_tables(...)
-	local result = {}
-	for _, table in ipairs({...}) do
-		for k, v in pairs(table) do
-			result[k] = v
-		end
-	end
-	return result
-end
-
-local function clone_table(table) 
- return merge_tables(table)
-end
-
-local function parse_options(unnamed_parameters, arg1, arg2)
-	local options = {}
-	if type(arg1) == 'table' then
-		options = arg1
-		f = arg2
-	elseif type(arg1) == 'function' then
-		f = arg1
-	end
-	options.f = f or options.f
-	return clone_table(options)
-end
-
-
-local function table_value_tostring(value)
-	local result = tostring(value)
-	if type(value) == 'number' then return result
-	elseif type(value) == 'boolean' then return result
-	elseif type(value) == 'string' then return "'" .. result .. "'"
-	else return '<' .. result .. '>'
-	end
-end
-
-local function table_tostring(table, print_functions, indent, accumulator, history)
-	local INDENT = '  '
-	local accumulator = accumulator or ''
-	local history = history or {}
-	local indent = indent or ''
-	accumulator = accumulator .. '{'
-	history[table] = true
-	local is_first = true
-	for key, value in pairs(table) do
-		if print_functions or type(value) ~= 'function' then
-			if not is_first then
-				accumulator = accumulator .. ','
-			end
-			is_first = false
-			accumulator = accumulator .. '\n' .. indent .. INDENT .. '[' .. table_value_tostring(key) .. '] = '
-			if type(value) == 'table' then
-				if history[value] then
-					accumulator = accumulator .. "..."
-				else
-					accumulator = table_tostring(value, print_functions, indent .. INDENT, accumulator, history)
-				end
-			else
-				accumulator = accumulator .. table_value_tostring(value)
-			end
-		end
-	end
-	history[table] = false
-	if not is_first then
-		accumulator = accumulator .. '\n' .. indent
-	end
-	accumulator = accumulator .. '}'
-	return accumulator
-end
-
-local function tprint(table)
-	print(table_tostring(table, true))
-end
-
-local function register_table_tostring(table)
-	if type(table) == 'table' then
-		local metatable = getmetatable(table)
-		if not metatable then
-			metatable = {}
-			setmetatable(table, metatable)
-		end
-		metatable.__tostring = table_tostring
-	end
-	return table
-end
-
-local function get_by_type(table, typ)
-	for _, v in ipairs(table) do
-		if v['_type'] == typ then
-			return v
-		end
- end
-end
-
-local function register_get_by_type_index(table)
-	if type(table) == 'table' then
-		local metatable = getmetatable(table)
-		if not metatable then
-			metatable = {}
-			setmetatable(table, metatable)
-		end
-		metatable.__index = get_by_type
-	end
-	return table
-end
-
---------------------------------------------------------------------------------
--- Client
---------------------------------------------------------------------------------
-
-function Client:connect (...)
-	local options = parse_options({}, ...)
-	local f = options.f
-	self:async_connect(options)
-	self:wait_connected()
-	if f then
-		local result = { xpcall(function() return f(self) end, debug.traceback) }
-		self:disconnect()
-		if result[1] then
-			table.remove(result, 1)
-			return unpack(result)
-		else
-			error(result[2])
-		end
-	end
-	return true
-end
-
-function Client:events (options)
-	local function client_events_iterator(s)
-		return s['client']:get_next_event(s['options'])
-	end
-	return client_events_iterator, {client = self, options = options}
-end
-
-function Client:for_each_event (...)
-	local options = parse_options({}, ...)
-	if not type(options.f) == 'function' then error('Expected function') end
-	for event in self:events(options) do
-		local result = options.f(event)
-		if result then
-			return result
-		end
-	end
-end
-
-for method, event_type in pairs({message = 'message', presence = 'presence', pubsub_event = 'pubsub'}) do
-	Client['for_each_' .. method] = function (client, ...)
-		local options = parse_options({}, ...)
-		options['type'] = event_type
-		return client:for_each_event (options)
-	end
-
-	Client['get_next_' .. method] = function (client, ...)
-		local options = parse_options({}, ...)
-		options['type'] = event_type
-		return client:get_next_event(options)
-	end
-end
-
-for method, event_type in pairs({messages = 'message', pubsub_events = 'pubsub'}) do
-	Client[method] = function (client, ...)
-		local options = parse_options({}, ...)
-		options['type'] = event_type
-		return client:events (options)
-	end
-end
-
---
--- Register get_* and set_* convenience methods for some type of queries
---
--- Example usages:
---	client:get_software_version{to = 'alice@wonderland.lit'}
---	client:set_command{to = 'alice@wonderland.lit', command = { type = 'execute', node = 'uptime' }}
---
-local get_set_shortcuts = {
-	get = {'software_version', 'disco_items', 'xml', 'dom', 'vcard'},
-	set = {'command'}
-}
-for query_action, query_types in pairs(get_set_shortcuts) do
-	for _, query_type in ipairs(query_types) do
-		Client[query_action .. '_' .. query_type] = function (client, options)
-			options = options or {}
-			if type(options) ~= 'table' then error('Invalid options: ' .. options) end 
-			options['query'] = merge_tables({_type = query_type}, options[query_type] or {})
-			return client[query_action](client, options)
-		end
-	end
-end
-
-function Client:pubsub (jid)
-	local result = { client = self, jid = jid }
-	setmetatable(result, PubSub)
-	return result
-end
-
---------------------------------------------------------------------------------
--- PubSub
---------------------------------------------------------------------------------
-
-PubSub.__index = PubSub
-
-local function process_pubsub_event (event)
-	if event._type == 'pubsub_event_items' then
-		-- Add 'item' shortcut to payload of first item
-		event.item = event.items and event.items[1] and 
-			event.items[1].data and event.items[1].data[1]
-	end
-end
-
-function PubSub:list_nodes (options)
-	return self.client:get_disco_items(merge_tables({to = self.jid}, options))
-end
-
-function PubSub:node (node)
-	local result = { client = self.client, jid = self.jid, node = node }
-	setmetatable(result, PubSubNode)
-	return result
-end
-
-local simple_pubsub_queries = {
-	get_default_configuration = 'pubsub_owner_default',
-	get_subscriptions = 'pubsub_subscriptions',
-	get_affiliations = 'pubsub_affiliations',
-	get_default_subscription_options = 'pubsub_default',
-}
-for method, query_type in pairs(simple_pubsub_queries) do
-	PubSub[method] = function (service, options)
-		options = options or {}
-		return service.client:query_pubsub(merge_tables(
-			{ type = 'get', to = service.jid, query = { _type = query_type } },
-			options))
-	end
-end
-
-for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
-	PubSub[method] = function (node, ...)
-		local options = parse_options({}, ...)
-		options['if'] = function (event) 
-			return event.type == 'pubsub' and event.from == node.jid and event.node == node
-		end
-		return node.client[method](node.client, options)
- end
-end
-
---------------------------------------------------------------------------------
--- PubSubNode
---------------------------------------------------------------------------------
-
-PubSubNode.__index = PubSubNode
-
-local function pubsub_node_configuration_to_form(configuration)
-	if not configuration then
-		return
-	end
-	local fields = { {name = 'form_type', value = 'http://jabber.org/protocol/pubsub#node_config'} }
-	for var, value in pairs(configuration) do
-		fields[#fields+1] = { name = var, value = value }
-	end
-	return { type = "submit", fields = fields }
-end
-
-function PubSubNode:list_items (options)
-	return self.client:get_disco_items(merge_tables({to = self.jid, disco_items = { node = self.node }}, options))
-end
-
-local simple_pubsub_node_queries = {
-	get_configuration = 'pubsub_owner_configure',
-	get_subscriptions = 'pubsub_subscriptions',
-	get_affiliations = 'pubsub_affiliations',
-	get_owner_subscriptions = 'pubsub_owner_subscriptions',
-	get_owner_affiliations = 'pubsub_owner_affiliations',
-	get_default_subscription_options = 'pubsub_default',
-}
-for method, query_type in pairs(simple_pubsub_node_queries) do
-	PubSubNode[method] = function (node, options)
-		return node.client:query_pubsub(merge_tables({ 
-			type = 'get', to = node.jid, query = {
-					_type = query_type, node = node.node 
-			}}, options))
-	end
-end
-
-function PubSubNode:get_items (...)
-	local options = parse_options({}, ...)
-	local items = options.items or {}
-	if options.maximum_items then
-		items = merge_tables({maximum_items = options.maximum_items}, items)
-	end
-	items = merge_tables({_type = 'pubsub_items', node = self.node}, items)
-	return self.client:query_pubsub(merge_tables({ 
-		type = 'get', to = self.jid, query = items}, options))
-end
-
-function PubSubNode:get_item (...)
-	local options = parse_options({}, ...)
-	if not type(options.id) == 'string' then error('Expected ID') end
-	return self:get_items{items = {{id = options.id}}}
-end
-
-function PubSubNode:create (options)
-	options = options or {}
-	local configure
-	if options['configuration'] then
-		configure = { data = pubsub_node_configuration_to_form(options['configuration']) }
-	end
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_create', node = self.node, configure = configure }
-		}, options))
-end
-
-function PubSubNode:delete (options)
-	options = options or {}
-	local redirect
-	if options['redirect'] then
-		redirect = {uri = options['redirect']}
-	end
-	return self.client:query_pubsub(merge_tables({ type = 'set', to = self.jid, query = { 
-			_type = 'pubsub_owner_delete', node = self.node, redirect = redirect 
-		}}, options))
-end
-
-function PubSubNode:set_configuration(options)
-	options = options or {}
-	local configuration = pubsub_node_configuration_to_form(options['configuration'])
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_owner_configure', node = self.node, data = configuration }
-		}, options))
-end
-
-function PubSubNode:set_owner_affiliations(...)
-	local options = parse_options({}, ...)
-	return self.client:query_pubsub(merge_tables({ 
-		type = 'set', to = self.jid, query = merge_tables({
-				_type = 'pubsub_owner_affiliations', node = self.node, 
-		}, options.affiliations)}, options))
-end
-
-
-function PubSubNode:subscribe(...)
-	local options = parse_options(...)
-	local jid = options.jid or sluift.jid.to_bare(self.client:jid())
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_subscribe', node = self.node, jid = jid }
-		}, options))
-end
-
-function PubSubNode:unsubscribe(options)
-	options = options or {}
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_unsubscribe', node = self.node, jid = options['jid'] }
-		}, options))
-end
-
-function PubSubNode:get_subscription_options (options)
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'get', to = self.jid, query = { 
-				_type = 'pubsub_options', node = self.node, jid = options['jid'] }
-		}, options))
-end
-
-function PubSubNode:publish(...)
-	local options = parse_options({}, ...)
-	local items = options.items or {}
-	if options.item then
-		if type(options.item) == 'string' or options.item._type then
-			items = {{id = options.id, data = { options.item } }}
-			options.id = nil 
-		else
-			items = { options.item }
-		end
-		options.item = nil
-	end
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_publish', node = self.node, items = items }
-		}, options))
-end
-
-function PubSubNode:retract(...)
-	local options = parse_options({}, ...)
-	local items = options.items
-	if options.id then
-		items = {{id = options.id}}
-	end
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_retract', node = self.node, items = items, notify = options['notify']
-		}}, options))
-end
-
-function PubSubNode:purge(...)
-	local options = parse_options({}, ...)
-	return self.client:query_pubsub(merge_tables(
-		{ type = 'set', to = self.jid, query = { 
-				_type = 'pubsub_owner_purge', node = self.node
-		}}, options))
-end
-
--- Iterators over events
-for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
-	PubSubNode[method] = function (node, ...)
-		local options = parse_options({}, ...)
-		options['if'] = function (event) 
-			return event.type == 'pubsub' and event.from == node.jid and event.node == node.node
-		end
-		return node.client[method](node.client, options)
-	end
-end
-
---------------------------------------------------------------------------------
--- Service discovery
---------------------------------------------------------------------------------
-
-local disco = {
-	features = {
-		DISCO_INFO = 'http://jabber.org/protocol/disco#info',
-		COMMANDS = 'http://jabber.org/protocol/commands',
-		USER_LOCATION = 'http://jabber.org/protocol/geoloc',
-		USER_TUNE = 'http://jabber.org/protocol/tune',
-		USER_AVATAR_METADATA = 'urn:xmpp:avatar:metadata',
-		USER_ACTIVITY = 'http://jabber.org/protocol/activity',
-		USER_PROFILE = 'urn:xmpp:tmp:profile'
-	}
-}
-
---------------------------------------------------------------------------------
-
-return {
-	Client = Client,
-	register_table_tostring = register_table_tostring,
-	register_get_by_type_index = register_get_by_type_index,
-	process_pubsub_event = process_pubsub_event,
-	tprint = tprint,
-	disco = disco,
-}
diff --git a/Sluift/client.cpp b/Sluift/client.cpp
index 97f9106..9eac84b 100644
--- a/Sluift/client.cpp
+++ b/Sluift/client.cpp
@@ -419,7 +419,7 @@ static void pushEvent(lua_State* L, const SluiftClient::Event& event) {
 			lua_pushstring(L, event.from.toString().c_str());
 			lua_setfield(L, -2, "from");
 
-			lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.bootIndex);
+			lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
 			lua_getfield(L, -1, "process_pubsub_event");
 			lua_pushvalue(L, -3);
 			lua_call(L, 1, 0);
diff --git a/Sluift/core.lua b/Sluift/core.lua
new file mode 100644
index 0000000..e2d4f8e
--- /dev/null
+++ b/Sluift/core.lua
@@ -0,0 +1,473 @@
+--[[
+	Copyright (c) 2013 Remko Tronçon
+	Licensed under the GNU General Public License.
+	See the COPYING file for more information.
+--]]
+
+local _G = _G
+local pairs, ipairs, print, tostring, type, error = pairs, ipairs, print, tostring, type, error
+local setmetatable, getmetatable = setmetatable, getmetatable
+local string = string
+_ENV = nil
+
+local Client = {}
+local PubSub = {}
+local PubSubNode = {}
+
+--------------------------------------------------------------------------------
+-- Utility methods
+--------------------------------------------------------------------------------
+
+local function merge_tables(...)
+	local result = {}
+	for _, table in ipairs({...}) do
+		for k, v in pairs(table) do
+			result[k] = v
+		end
+	end
+	return result
+end
+
+local function clone_table(table) 
+	return merge_tables(table)
+end
+
+local function parse_options(unnamed_parameters, arg1, arg2)
+	local options = {}
+	local f
+	if type(arg1) == 'table' then
+		options = arg1
+		f = arg2
+	elseif type(arg1) == 'function' then
+		f = arg1
+	end
+	options.f = f or options.f
+	return clone_table(options)
+end
+
+
+local function table_value_tostring(value)
+	local result = tostring(value)
+	if type(value) == 'number' then return result
+	elseif type(value) == 'boolean' then return result
+	elseif type(value) == 'string' then return "'" .. result .. "'"
+	else return '<' .. result .. '>'
+	end
+end
+
+local function table_tostring(table, print_functions, indent, accumulator, history)
+	local INDENT = '  '
+	local accumulator = accumulator or ''
+	local history = history or {}
+	local indent = indent or ''
+	accumulator = accumulator .. '{'
+	history[table] = true
+	local is_first = true
+	for key, value in pairs(table) do
+		if print_functions or type(value) ~= 'function' then
+			if not is_first then
+				accumulator = accumulator .. ','
+			end
+			is_first = false
+			accumulator = accumulator .. '\n' .. indent .. INDENT .. '[' .. table_value_tostring(key) .. '] = '
+			if type(value) == 'table' then
+				if history[value] then
+					accumulator = accumulator .. "..."
+				else
+					accumulator = table_tostring(value, print_functions, indent .. INDENT, accumulator, history)
+				end
+			else
+				accumulator = accumulator .. table_value_tostring(value)
+			end
+		end
+	end
+	history[table] = false
+	if not is_first then
+		accumulator = accumulator .. '\n' .. indent
+	end
+	accumulator = accumulator .. '}'
+	return accumulator
+end
+
+local function tprint(table)
+	print(table_tostring(table, true))
+end
+
+local function register_table_tostring(table)
+	if type(table) == 'table' then
+		local metatable = getmetatable(table)
+		if not metatable then
+			metatable = {}
+			setmetatable(table, metatable)
+		end
+		metatable.__tostring = table_tostring
+	end
+	return table
+end
+
+local function get_by_type(table, typ)
+	for _, v in ipairs(table) do
+		if v['_type'] == typ then
+			return v
+		end
+	end
+end
+
+local function register_get_by_type_index(table)
+	if type(table) == 'table' then
+		local metatable = getmetatable(table)
+		if not metatable then
+			metatable = {}
+			setmetatable(table, metatable)
+		end
+		metatable.__index = get_by_type
+	end
+	return table
+end
+
+local function call(options)
+	local f = options[1]
+	local result = { xpcall(f, debug.traceback) }
+	if options.finally then
+		options.finally()
+	end
+	if result[1] then
+		table.remove(result, 1)
+		return unpack(result)
+	else
+		error(result[2])
+	end
+end
+
+--------------------------------------------------------------------------------
+-- Client
+--------------------------------------------------------------------------------
+
+function Client:connect (...)
+	local options = parse_options({}, ...)
+	local f = options.f
+	self:async_connect(options)
+	self:wait_connected()
+	if f then
+		return call {function() return f(self) end, finally = function() self:disconnect() end}
+	end
+	return true
+end
+
+function Client:events (options)
+	local function client_events_iterator(s)
+		return s['client']:get_next_event(s['options'])
+	end
+	return client_events_iterator, {client = self, options = options}
+end
+
+function Client:for_each_event (...)
+	local options = parse_options({}, ...)
+	if not type(options.f) == 'function' then error('Expected function') end
+	for event in self:events(options) do
+		local result = options.f(event)
+		if result then
+			return result
+		end
+	end
+end
+
+for method, event_type in pairs({message = 'message', presence = 'presence', pubsub_event = 'pubsub'}) do
+	Client['for_each_' .. method] = function (client, ...)
+		local options = parse_options({}, ...)
+		options['type'] = event_type
+		return client:for_each_event (options)
+	end
+
+	Client['get_next_' .. method] = function (client, ...)
+		local options = parse_options({}, ...)
+		options['type'] = event_type
+		return client:get_next_event(options)
+	end
+end
+
+for method, event_type in pairs({messages = 'message', pubsub_events = 'pubsub'}) do
+	Client[method] = function (client, ...)
+		local options = parse_options({}, ...)
+		options['type'] = event_type
+		return client:events (options)
+	end
+end
+
+-- Process all pending events
+function Client:process_events ()
+	for event in self:events{timeout=0} do end
+end
+
+--
+-- Register get_* and set_* convenience methods for some type of queries
+--
+-- Example usages:
+--	client:get_software_version{to = 'alice@wonderland.lit'}
+--	client:set_command{to = 'alice@wonderland.lit', command = { type = 'execute', node = 'uptime' }}
+--
+local get_set_shortcuts = {
+	get = {'software_version', 'disco_items', 'xml', 'dom', 'vcard'},
+	set = {'command'}
+}
+for query_action, query_types in pairs(get_set_shortcuts) do
+	for _, query_type in ipairs(query_types) do
+		Client[query_action .. '_' .. query_type] = function (client, options)
+			options = options or {}
+			if type(options) ~= 'table' then error('Invalid options: ' .. options) end 
+			options['query'] = merge_tables({_type = query_type}, options[query_type] or {})
+			return client[query_action](client, options)
+		end
+	end
+end
+
+function Client:pubsub (jid)
+	local result = { client = self, jid = jid }
+	setmetatable(result, PubSub)
+	return result
+end
+
+--------------------------------------------------------------------------------
+-- PubSub
+--------------------------------------------------------------------------------
+
+PubSub.__index = PubSub
+
+local function process_pubsub_event (event)
+	if event._type == 'pubsub_event_items' then
+		-- Add 'item' shortcut to payload of first item
+		event.item = event.items and event.items[1] and 
+			event.items[1].data and event.items[1].data[1]
+	end
+end
+
+function PubSub:list_nodes (options)
+	return self.client:get_disco_items(merge_tables({to = self.jid}, options))
+end
+
+function PubSub:node (node)
+	local result = { client = self.client, jid = self.jid, node = node }
+	setmetatable(result, PubSubNode)
+	return result
+end
+
+local simple_pubsub_queries = {
+	get_default_configuration = 'pubsub_owner_default',
+	get_subscriptions = 'pubsub_subscriptions',
+	get_affiliations = 'pubsub_affiliations',
+	get_default_subscription_options = 'pubsub_default',
+}
+for method, query_type in pairs(simple_pubsub_queries) do
+	PubSub[method] = function (service, options)
+		options = options or {}
+		return service.client:query_pubsub(merge_tables(
+			{ type = 'get', to = service.jid, query = { _type = query_type } },
+			options))
+	end
+end
+
+for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
+	PubSub[method] = function (node, ...)
+		local options = parse_options({}, ...)
+		options['if'] = function (event) 
+			return event.type == 'pubsub' and event.from == node.jid and event.node == node
+		end
+		return node.client[method](node.client, options)
+	end
+end
+
+--------------------------------------------------------------------------------
+-- PubSubNode
+--------------------------------------------------------------------------------
+
+PubSubNode.__index = PubSubNode
+
+local function pubsub_node_configuration_to_form(configuration)
+	if not configuration then
+		return
+	end
+	local fields = { {name = 'form_type', value = 'http://jabber.org/protocol/pubsub#node_config'} }
+	for var, value in pairs(configuration) do
+		fields[#fields+1] = { name = var, value = value }
+	end
+	return { type = "submit", fields = fields }
+end
+
+function PubSubNode:list_items (options)
+	return self.client:get_disco_items(merge_tables({to = self.jid, disco_items = { node = self.node }}, options))
+end
+
+local simple_pubsub_node_queries = {
+	get_configuration = 'pubsub_owner_configure',
+	get_subscriptions = 'pubsub_subscriptions',
+	get_affiliations = 'pubsub_affiliations',
+	get_owner_subscriptions = 'pubsub_owner_subscriptions',
+	get_owner_affiliations = 'pubsub_owner_affiliations',
+	get_default_subscription_options = 'pubsub_default',
+}
+for method, query_type in pairs(simple_pubsub_node_queries) do
+	PubSubNode[method] = function (node, options)
+		return node.client:query_pubsub(merge_tables({ 
+			type = 'get', to = node.jid, query = {
+					_type = query_type, node = node.node 
+			}}, options))
+	end
+end
+
+function PubSubNode:get_items (...)
+	local options = parse_options({}, ...)
+	local items = options.items or {}
+	if options.maximum_items then
+		items = merge_tables({maximum_items = options.maximum_items}, items)
+	end
+	items = merge_tables({_type = 'pubsub_items', node = self.node}, items)
+	return self.client:query_pubsub(merge_tables({ 
+		type = 'get', to = self.jid, query = items}, options))
+end
+
+function PubSubNode:get_item (...)
+	local options = parse_options({}, ...)
+	if not type(options.id) == 'string' then error('Expected ID') end
+	return self:get_items{items = {{id = options.id}}}
+end
+
+function PubSubNode:create (options)
+	options = options or {}
+	local configure
+	if options['configuration'] then
+		configure = { data = pubsub_node_configuration_to_form(options['configuration']) }
+	end
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_create', node = self.node, configure = configure }
+		}, options))
+end
+
+function PubSubNode:delete (options)
+	options = options or {}
+	local redirect
+	if options['redirect'] then
+		redirect = {uri = options['redirect']}
+	end
+	return self.client:query_pubsub(merge_tables({ type = 'set', to = self.jid, query = { 
+			_type = 'pubsub_owner_delete', node = self.node, redirect = redirect 
+		}}, options))
+end
+
+function PubSubNode:set_configuration(options)
+	options = options or {}
+	local configuration = pubsub_node_configuration_to_form(options['configuration'])
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_owner_configure', node = self.node, data = configuration }
+		}, options))
+end
+
+function PubSubNode:set_owner_affiliations(...)
+	local options = parse_options({}, ...)
+	return self.client:query_pubsub(merge_tables({ 
+		type = 'set', to = self.jid, query = merge_tables({
+				_type = 'pubsub_owner_affiliations', node = self.node, 
+		}, options.affiliations)}, options))
+end
+
+
+function PubSubNode:subscribe(...)
+	local options = parse_options(...)
+	local jid = options.jid or sluift.jid.to_bare(self.client:jid())
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_subscribe', node = self.node, jid = jid }
+		}, options))
+end
+
+function PubSubNode:unsubscribe(options)
+	options = options or {}
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_unsubscribe', node = self.node, jid = options['jid'] }
+		}, options))
+end
+
+function PubSubNode:get_subscription_options (options)
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'get', to = self.jid, query = { 
+				_type = 'pubsub_options', node = self.node, jid = options['jid'] }
+		}, options))
+end
+
+function PubSubNode:publish(...)
+	local options = parse_options({}, ...)
+	local items = options.items or {}
+	if options.item then
+		if type(options.item) == 'string' or options.item._type then
+			items = {{id = options.id, data = { options.item } }}
+			options.id = nil 
+		else
+			items = { options.item }
+		end
+		options.item = nil
+	end
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_publish', node = self.node, items = items }
+		}, options))
+end
+
+function PubSubNode:retract(...)
+	local options = parse_options({}, ...)
+	local items = options.items
+	if options.id then
+		items = {{id = options.id}}
+	end
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_retract', node = self.node, items = items, notify = options['notify']
+		}}, options))
+end
+
+function PubSubNode:purge(...)
+	local options = parse_options({}, ...)
+	return self.client:query_pubsub(merge_tables(
+		{ type = 'set', to = self.jid, query = { 
+				_type = 'pubsub_owner_purge', node = self.node
+		}}, options))
+end
+
+-- Iterators over events
+for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
+	PubSubNode[method] = function (node, ...)
+		local options = parse_options({}, ...)
+		options['if'] = function (event) 
+			return event.type == 'pubsub' and event.from == node.jid and event.node == node.node
+		end
+		return node.client[method](node.client, options)
+	end
+end
+
+--------------------------------------------------------------------------------
+-- Service discovery
+--------------------------------------------------------------------------------
+
+local disco = {
+	features = {
+		DISCO_INFO = 'http://jabber.org/protocol/disco#info',
+		COMMANDS = 'http://jabber.org/protocol/commands',
+		USER_LOCATION = 'http://jabber.org/protocol/geoloc',
+		USER_TUNE = 'http://jabber.org/protocol/tune',
+		USER_AVATAR_METADATA = 'urn:xmpp:avatar:metadata',
+		USER_ACTIVITY = 'http://jabber.org/protocol/activity',
+		USER_PROFILE = 'urn:xmpp:tmp:profile'
+	}
+}
+
+--------------------------------------------------------------------------------
+
+return {
+	Client = Client,
+	register_table_tostring = register_table_tostring,
+	register_get_by_type_index = register_get_by_type_index,
+	process_pubsub_event = process_pubsub_event,
+	tprint = tprint,
+	disco = disco,
+}
diff --git a/Sluift/linit.c b/Sluift/linit.c
index 8f0614b..ad27b37 100644
--- a/Sluift/linit.c
+++ b/Sluift/linit.c
@@ -4,33 +4,34 @@
 #include "sluift.h"
 
 static const luaL_Reg lualibs[] = {
-  {"", luaopen_base},
-  {LUA_LOADLIBNAME, luaopen_package},
-  {LUA_TABLIBNAME, luaopen_table},
-  {LUA_IOLIBNAME, luaopen_io},
-  {LUA_OSLIBNAME, luaopen_os},
-  {LUA_STRLIBNAME, luaopen_string},
-  {LUA_MATHLIBNAME, luaopen_math},
-  {LUA_DBLIBNAME, luaopen_debug},
-  {"sluift", luaopen_sluift},
-  {NULL, NULL}
+	{"", luaopen_base},
+	{LUA_LOADLIBNAME, luaopen_package},
+	{LUA_TABLIBNAME, luaopen_table},
+	{LUA_IOLIBNAME, luaopen_io},
+	{LUA_OSLIBNAME, luaopen_os},
+	{LUA_STRLIBNAME, luaopen_string},
+	{LUA_MATHLIBNAME, luaopen_math},
+	{LUA_DBLIBNAME, luaopen_debug},
+	{"sluift", luaopen_sluift},
+	{NULL, NULL}
 };
 
 
 LUALIB_API void luaL_openlibs (lua_State *L) {
-  const luaL_Reg *lib = lualibs;
-  for (; lib->func; lib++) {
-    lua_pushcfunction(L, lib->func);
-    lua_pushstring(L, lib->name);
-    lua_call(L, 1, 0);
-  }
+	const luaL_Reg *lib = lualibs;
+	for (; lib->func; lib++) {
+		lua_pushcfunction(L, lib->func);
+		lua_pushstring(L, lib->name);
+		lua_call(L, 1, 0);
+	}
 
 	/* Import sluift into global namespace */
-  lua_getfield(L, LUA_GLOBALSINDEX, "sluift");
+	lua_pushglobaltable(L);
+  lua_getfield(L, -1, "sluift");
 	for (lua_pushnil(L); lua_next(L, -2); ) {
 		lua_pushvalue(L, -2);
 		lua_pushvalue(L, -2);
-		lua_settable(L, LUA_GLOBALSINDEX);
+		lua_settable(L, -6);
 		lua_pop(L, 1);
 	}
 }
diff --git a/Sluift/sluift.cpp b/Sluift/sluift.cpp
index 95e7101..e6b2bb6 100644
--- a/Sluift/sluift.cpp
+++ b/Sluift/sluift.cpp
@@ -40,7 +40,7 @@ namespace Swift {
 	}
 }
 
-extern "C" const char boot_lua[];
+extern "C" const char core_lua[];
 
 /*******************************************************************************
  * Module functions
@@ -263,21 +263,21 @@ SLUIFT_LUA_FUNCTION(IDN, stringprep) {
  * Module registration
  ******************************************************************************/
 
-static const luaL_reg sluift_functions[] = { {NULL, NULL} };
+static const luaL_Reg sluift_functions[] = { {NULL, NULL} };
 
 SLUIFT_API int luaopen_sluift(lua_State* L) {
 	// Initialize globals
 	Sluift::globals.debug = false;
 	Sluift::globals.timeout = -1;
 
-	luaL_register(L, "sluift", sluift_functions);
+	luaL_register(L, lua_tostring(L, 1), sluift_functions);
 
-	// Load bootstrap code
-	if (luaL_loadbuffer(L, boot_lua, strlen(boot_lua), "boot.lua") != 0) {
+	// Load core lib code
+	if (luaL_loadbuffer(L, core_lua, strlen(core_lua), "core.lua") != 0) {
 		lua_error(L);
 	}
 	lua_call(L, 0, 1);
-	Sluift::globals.bootIndex = luaL_ref(L, LUA_REGISTRYINDEX);
+	Sluift::globals.coreLibIndex = luaL_ref(L, LUA_REGISTRYINDEX);
 
 	// Register functions
 	Lua::FunctionRegistry::getInstance().addFunctionsToTable(L, "Sluift");
@@ -289,14 +289,13 @@ SLUIFT_API int luaopen_sluift(lua_State* L) {
 	lua_setfield(L, -2, "idn");
 
 	// Register convenience functions
-	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.bootIndex);
-	lua_getfield(L, -1, "tprint");
-	lua_setfield(L, -3, "tprint");
-	lua_pop(L, 1);
-
-	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.bootIndex);
-	lua_getfield(L, -1, "disco");
-	lua_setfield(L, -3, "disco");
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
+	std::vector<std::string> coreLibExports = boost::assign::list_of
+		("tprint")("disco");
+	foreach (const std::string& coreLibExport, coreLibExports) {
+		lua_getfield(L, -1, coreLibExport.c_str());
+		lua_setfield(L, -3, coreLibExport.c_str());
+	}
 	lua_pop(L, 1);
 
 	// Set read only
@@ -312,7 +311,7 @@ SLUIFT_API int luaopen_sluift(lua_State* L) {
 	foreach (const std::string& table, tables) {
 		Lua::FunctionRegistry::getInstance().registerTypeMetaTable(L, table);
 		luaL_getmetatable(L, Lua::FunctionRegistry::getMetaTableNameForType(table).c_str());
-		lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.bootIndex);
+		lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
 		lua_getfield(L, -1, table.c_str());
 		if (!lua_isnil(L, -1)) {
 			for (lua_pushnil(L); lua_next(L, -2); ) {
diff --git a/Sluift/sluift.h b/Sluift/sluift.h
index b82e1c4..2613370 100644
--- a/Sluift/sluift.h
+++ b/Sluift/sluift.h
@@ -20,6 +20,10 @@
 #include <lua.h>
 #endif
 
+#if LUA_VERSION_NUM < 502
+#define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX)
+#endif
+
 #if defined(__cplusplus)
 extern "C"
 #endif
-- 
cgit v0.10.2-6-g49f6