From 26bb5aa9e2f520c3c943797e6143c32e5b16806b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Mon, 30 Dec 2013 12:21:45 +0100
Subject: Sluift: Add help support

Provide a 'help' function that takes a table/function, and prints help
for it. A structured representation can be retrieved through 'get_help'.

Change-Id: I2b3ce8992943ef30cee2604fba9200feed263fa5

diff --git a/Sluift/ElementConvertors/DiscoInfoConvertor.cpp b/Sluift/ElementConvertors/DiscoInfoConvertor.cpp
index ac0cf2e..b4bd2e1 100644
--- a/Sluift/ElementConvertors/DiscoInfoConvertor.cpp
+++ b/Sluift/ElementConvertors/DiscoInfoConvertor.cpp
@@ -99,3 +99,18 @@ void DiscoInfoConvertor::doConvertToLua(lua_State* L, boost::shared_ptr<DiscoInf
 	
 	// TODO: Extension
 }
+
+boost::optional<LuaElementConvertor::Documentation> DiscoInfoConvertor::getDocumentation() const {
+	return Documentation(
+			"DiscoInfo", 
+			"Represents `disco#info` service discovery data.\n\n"
+			"This table has the following structure:\n\n"
+			"- `node`: string\n"
+			"- `identities`: array(table)\n"
+			"  - `name`: string\n"
+			"  - `category`: string\n"
+			"  - `type`: string\n"
+			"  - `language`: string\n"
+			"- `features`: array(string)\n"
+	);
+}
diff --git a/Sluift/ElementConvertors/DiscoInfoConvertor.h b/Sluift/ElementConvertors/DiscoInfoConvertor.h
index 7a2270e..4cf9c71 100644
--- a/Sluift/ElementConvertors/DiscoInfoConvertor.h
+++ b/Sluift/ElementConvertors/DiscoInfoConvertor.h
@@ -19,5 +19,6 @@ namespace Swift {
 
 			virtual boost::shared_ptr<DiscoInfo> doConvertFromLua(lua_State*) SWIFTEN_OVERRIDE;
 			virtual void doConvertToLua(lua_State*, boost::shared_ptr<DiscoInfo>) SWIFTEN_OVERRIDE;
+			virtual boost::optional<Documentation> getDocumentation() const SWIFTEN_OVERRIDE;
 	};
 }
diff --git a/Sluift/GenerateDocumentation.lua b/Sluift/GenerateDocumentation.lua
new file mode 100644
index 0000000..69693ea
--- /dev/null
+++ b/Sluift/GenerateDocumentation.lua
@@ -0,0 +1,90 @@
+--[[
+	Copyright (c) 2013 Remko Tronçon
+	Licensed under the GNU General Public License.
+	See the COPYING file for more information.
+--]]
+
+require "sluift"
+
+local function get_anchor(...)
+	return table.concat({...}, "-")
+end
+
+local function convert_links(text)
+	result = text:gsub("(@{(%w*)})", "[`%2`](#%2)")
+	return result
+end
+
+local function add_help(help, document, class, level)
+	-- Collect help of children
+	local methods = {}
+	for _, method in pairs(help.methods or {}) do
+		local description
+		local method_help = sluift.get_help(method.ref)
+		if method_help and method_help.description then
+			description = method_help.synopsis
+		end
+		methods[#methods+1] = { name = method.name, description = description, ref = method.ref }
+	end
+	local fields = sluift.copy(help.fields or {})
+
+	table.sort(methods, function (a, b) return (a.name or "") < (b.name or "") end)
+	table.sort(fields, function (a, b) return (a.name or "") < (b.name or "") end)
+
+	local classes = {}
+	for _, class in pairs(help.classes or {}) do
+		classes[#classes+1] = { name = class, description = sluift.get_help(class).synopsis }
+	end
+
+	table.insert(document, convert_links(help.description or ""))
+	for _, p in ipairs({
+			{"Parameters", help.parameters}, {"Options", help.options}, {"Fields", fields}, {"Methods", methods}}) do
+		if p[2] and next(p[2]) ~= nil then
+			table.insert(document, "\n\n" .. level .. " " .. p[1] .. "\n")
+			for _, parameter in ipairs(p[2]) do
+				parameter_name = "`" .. parameter.name .. "`"
+				if p[1] == "Methods" then
+					parameter_name = "[" .. parameter_name .. "](#" .. get_anchor(class, parameter.name) .. ")"
+				end
+				if parameter.description then
+					table.insert(document, "- " .. parameter_name .. ": " .. convert_links(parameter.description))
+				else
+					table.insert(document, "- " .. parameter_name)
+				end
+			end
+			if p[1] == "Methods" then
+				for _, method in ipairs(p[2]) do
+					table.insert(document, "\n#### <a name=\"" .. get_anchor(class, method.name) .. "\"></a> `" .. method.name .. "`\n")
+					if method.ref then
+						add_help(sluift.get_help(method.ref) or {}, document, class, level .. "#")
+					end
+				end
+			end
+		end
+	end
+end
+
+local document = {}
+
+table.insert(document, [[
+# Sluift
+
+This document describes the API of the `sluift` module.
+
+The entry points of Sluift are in the `sluift` module, described below.
+
+## `sluift` module
+]])
+
+help = sluift.get_help(sluift)
+add_help(help, document, "sluift", "###")
+for _, class in pairs(help.classes) do
+	class_help = sluift.get_help(class)
+	if class_help then
+		table.insert(document, "\n## <a name=\"" .. class .. "\"></a> `" .. class .. "` class\n")
+		add_help(class_help, document, class, "###")
+	end
+end
+
+document = table.concat(document, "\n") .. "\n"
+print(document)
diff --git a/Sluift/Lua/Check.cpp b/Sluift/Lua/Check.cpp
index cfb726a..65ada7b 100644
--- a/Sluift/Lua/Check.cpp
+++ b/Sluift/Lua/Check.cpp
@@ -43,7 +43,7 @@ std::string Lua::checkString(lua_State* L, int arg) {
 	return std::string(s);
 }
 
-void* Lua::checkUserDataRaw(lua_State* L, int arg, const char* tableName) {
+void* Lua::checkUserDataRaw(lua_State* L, int arg) {
 	void* userData = lua_touserdata(L, arg);
 	if (!userData) {
 		throw Lua::Exception(getArgTypeError(L, arg, LUA_TUSERDATA));
@@ -51,10 +51,6 @@ void* Lua::checkUserDataRaw(lua_State* L, int arg, const char* tableName) {
 	if (!lua_getmetatable(L, arg)) {
 		throw Lua::Exception(getArgTypeError(L, arg, LUA_TUSERDATA));
 	}
-	lua_getfield(L, LUA_REGISTRYINDEX, tableName);
-	if (!lua_rawequal(L, -1, -2)) {
-		throw Lua::Exception(getArgTypeError(L, arg, LUA_TUSERDATA));
-	}
-	lua_pop(L, 2);
+	lua_pop(L, 1);
 	return userData;
 }
diff --git a/Sluift/Lua/Check.h b/Sluift/Lua/Check.h
index a569826..8a8b64a 100644
--- a/Sluift/Lua/Check.h
+++ b/Sluift/Lua/Check.h
@@ -16,11 +16,11 @@ namespace Swift {
 		int checkIntNumber(lua_State* L, int arg);
 		std::string checkString(lua_State* L, int arg);
 
-		void* checkUserDataRaw(lua_State* L, int arg, const char* tableName);
+		void* checkUserDataRaw(lua_State* L, int arg);
 
 		template<typename T>
-		T** checkUserData(lua_State* L, int arg, const char* tableName) {
-			return reinterpret_cast<T**>(checkUserDataRaw(L, arg, tableName));
+		T** checkUserData(lua_State* L, int arg) {
+			return reinterpret_cast<T**>(checkUserDataRaw(L, arg));
 		}
 	}
 }
diff --git a/Sluift/Lua/FunctionRegistration.cpp b/Sluift/Lua/FunctionRegistration.cpp
index b773952..ddfa1f0 100644
--- a/Sluift/Lua/FunctionRegistration.cpp
+++ b/Sluift/Lua/FunctionRegistration.cpp
@@ -8,8 +8,8 @@
 
 using namespace Swift::Lua;
 
-FunctionRegistration::FunctionRegistration(const std::string& name, lua_CFunction function, const std::string& type) {
-	FunctionRegistry::getInstance().addFunction(name, function, type);
+FunctionRegistration::FunctionRegistration(const std::string& name, lua_CFunction function, const std::string& type, const std::string& helpDescription, const std::string& helpParameters, const std::string& helpOptions) {
+	FunctionRegistry::getInstance().addFunction(name, function, type, helpDescription, helpParameters, helpOptions);
 }
 
 FunctionRegistration::~FunctionRegistration() {
diff --git a/Sluift/Lua/FunctionRegistration.h b/Sluift/Lua/FunctionRegistration.h
index 0df1da1..74269e2 100644
--- a/Sluift/Lua/FunctionRegistration.h
+++ b/Sluift/Lua/FunctionRegistration.h
@@ -16,15 +16,19 @@ namespace Swift {
 	namespace Lua {
 		class FunctionRegistration {
 			public:
-				FunctionRegistration(const std::string& name, lua_CFunction function, const std::string& type);
+				FunctionRegistration(
+						const std::string& name, lua_CFunction function, const std::string& type, 
+						const std::string& helpDescription, const std::string& helpParameters, const std::string& helpOptions);
 				~FunctionRegistration();
 		};
 	}
 }
-#define SLUIFT_LUA_FUNCTION(TYPE, NAME) \
+
+
+#define SLUIFT_LUA_FUNCTION_WITH_HELP(TYPE, NAME, HELP_DESCRIPTION, HELP_PARAMETERS, HELP_OPTIONS) \
 	static int TYPE##_##NAME(lua_State* L); \
 	static int TYPE##_##NAME##_wrapper(lua_State* L); \
-	static ::Swift::Lua::FunctionRegistration TYPE##_##NAME##_registration( #NAME , TYPE##_##NAME##_wrapper, #TYPE); \
+	static ::Swift::Lua::FunctionRegistration TYPE##_##NAME##_registration( #NAME , TYPE##_##NAME##_wrapper, #TYPE, HELP_DESCRIPTION, HELP_PARAMETERS, HELP_OPTIONS); \
 	static int TYPE##_##NAME##_wrapper(lua_State* L) { \
 		try { \
 			return TYPE ## _ ## NAME (L); \
@@ -34,3 +38,6 @@ namespace Swift {
 		} \
 	} \
 	static int TYPE ## _ ## NAME (lua_State* L)
+
+#define SLUIFT_LUA_FUNCTION(TYPE, NAME) \
+	SLUIFT_LUA_FUNCTION_WITH_HELP(TYPE, NAME, "", "", "")
diff --git a/Sluift/Lua/FunctionRegistry.cpp b/Sluift/Lua/FunctionRegistry.cpp
index 99ea096..df24d9c 100644
--- a/Sluift/Lua/FunctionRegistry.cpp
+++ b/Sluift/Lua/FunctionRegistry.cpp
@@ -7,6 +7,9 @@
 #include <Sluift/Lua/FunctionRegistry.h>
 
 #include <Swiften/Base/foreach.h>
+#include <Sluift/Lua/LuaUtils.h>
+#include <Sluift/Lua/Exception.h>
+#include <Sluift/globals.h>
 
 using namespace Swift::Lua;
 
@@ -21,25 +24,19 @@ FunctionRegistry& FunctionRegistry::getInstance() {
 	return instance;
 }
 
-void FunctionRegistry::addFunction(const std::string& name, lua_CFunction function, const std::string& type) {
+void FunctionRegistry::addFunction(
+		const std::string& name, lua_CFunction function, const std::string& type,
+		const std::string& helpDescription, const std::string& helpParameters, const std::string& helpOptions) {
 	Registration registration;
 	registration.name = name;
 	registration.function = function;
 	registration.type = type;
+	registration.helpDescription = helpDescription;
+	registration.helpParameters = helpParameters;
+	registration.helpOptions = helpOptions;
 	registrations.push_back(registration);
 }
 
-std::string FunctionRegistry::getMetaTableNameForType(const std::string& type) {
-	return "Sluift_" + type;
-}
-
-void FunctionRegistry::registerTypeMetaTable(lua_State* L, const std::string& type) {
-	luaL_newmetatable(L, getMetaTableNameForType(type).c_str());
-	lua_pushvalue(L, -1);
-	lua_setfield(L, -2, "__index");
-	addFunctionsToTable(L, type);
-}
-
 void FunctionRegistry::createFunctionTable(lua_State* L, const std::string& type) {
 	lua_newtable(L);
 	addFunctionsToTable(L, type);
@@ -49,6 +46,12 @@ void FunctionRegistry::addFunctionsToTable(lua_State* L, const std::string& type
 	foreach(const Registration& registration, registrations) {
 		if (registration.type == type) {
 			lua_pushcclosure(L, registration.function, 0);
+			if (!registration.helpDescription.empty()) {
+				Lua::registerHelp(L, -1, registration.helpDescription, registration.helpParameters, registration.helpOptions);
+			}
+			else {
+				Lua::registerExtraHelp(L, -1, registration.type + "." + registration.name);
+			}
 			lua_setfield(L, -2, registration.name.c_str());
 		}
 	}
diff --git a/Sluift/Lua/FunctionRegistry.h b/Sluift/Lua/FunctionRegistry.h
index e3ad620..b20108d 100644
--- a/Sluift/Lua/FunctionRegistry.h
+++ b/Sluift/Lua/FunctionRegistry.h
@@ -18,10 +18,8 @@ namespace Swift {
 				~FunctionRegistry();
 				static FunctionRegistry& getInstance();
 
-				void addFunction(const std::string& name, lua_CFunction function, const std::string& type);
-
-				static std::string getMetaTableNameForType(const std::string& type);
-				void registerTypeMetaTable(lua_State* L, const std::string& type);
+				void addFunction(const std::string& name, lua_CFunction function, const std::string& type,
+						const std::string& helpDescription, const std::string& helpParameters, const std::string& helpOptions);
 
 				void createFunctionTable(lua_State* L, const std::string& type);
 
@@ -39,6 +37,9 @@ namespace Swift {
 					std::string name;
 					lua_CFunction function;
 					std::string type;
+					std::string helpDescription;
+					std::string helpParameters;
+					std::string helpOptions;
 				};
 				std::vector<Registration> registrations;
 		};
diff --git a/Sluift/Lua/LuaUtils.cpp b/Sluift/Lua/LuaUtils.cpp
index b00ab56..2192689 100644
--- a/Sluift/Lua/LuaUtils.cpp
+++ b/Sluift/Lua/LuaUtils.cpp
@@ -14,6 +14,7 @@
 #include <cassert>
 #include <sstream>
 #include <boost/numeric/conversion/cast.hpp>
+#include <boost/algorithm/string/trim.hpp>
 #include <Sluift/globals.h>
 
 using namespace Swift::Lua;
@@ -77,3 +78,103 @@ boost::optional<int> Swift::Lua::getIntField(lua_State* L, int index, const std:
 	lua_pop(L, 1);
 	return result;
 }
+
+void Swift::Lua::registerHelp(lua_State* L, int index, const std::string& description, const std::string& parameters, const std::string& options) {
+	index = Lua::absoluteOffset(L, index);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
+	lua_getfield(L, -1, "register_help");
+	lua_pushvalue(L, index);
+
+	lua_newtable(L);
+	lua_pushstring(L, description.c_str());
+	lua_rawseti(L, -2, 1);
+
+	if (!parameters.empty()) {
+		std::istringstream s(parameters);
+		lua_newtable(L);
+		int i = 1;
+		for (std::string line; std::getline(s, line); ) {
+			std::string trimmedLine = boost::trim_copy(line);
+			if (trimmedLine.empty()) {
+				continue;
+			}
+			size_t splitIndex = trimmedLine.find_first_of(" \t");
+			std::string key;
+			std::string value;
+			if (splitIndex == std::string::npos) {
+				key = trimmedLine;
+			}
+			else {
+				key = trimmedLine.substr(0, splitIndex);
+				value = boost::trim_copy(trimmedLine.substr(splitIndex+1));
+			}
+			lua_createtable(L, 2, 0);
+			lua_pushstring(L, key.c_str());
+			lua_rawseti(L, -2, 1);
+			lua_pushstring(L, value.c_str());
+			lua_rawseti(L, -2, 2);
+
+			lua_rawseti(L, -2, i++);
+		}
+		lua_setfield(L, -2, "parameters");
+	}
+	if (!options.empty()) {
+		std::istringstream s(options);
+		lua_newtable(L);
+		for (std::string line; std::getline(s, line); ) {
+			std::string trimmedLine = boost::trim_copy(line);
+			if (trimmedLine.empty()) {
+				continue;
+			}
+			size_t splitIndex = trimmedLine.find_first_of(" \t");
+			std::string key;
+			std::string value;
+			if (splitIndex == std::string::npos) {
+				key = trimmedLine;
+			}
+			else {
+				key = trimmedLine.substr(0, splitIndex);
+				value = boost::trim_copy(trimmedLine.substr(splitIndex+1));
+			}
+			lua_pushstring(L, value.c_str());
+			lua_setfield(L, -2, key.c_str());
+		}
+		lua_setfield(L, -2, "options");
+	}
+
+	if (lua_pcall(L, 2, 0, 0) != 0) {
+		throw Lua::Exception(lua_tostring(L, -1));
+	}
+	lua_pop(L, 1);
+}
+
+void Swift::Lua::registerClassHelp(lua_State* L, const std::string& name, const std::string& description) {
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
+	lua_getfield(L, -1, "register_class_help");
+	lua_pushstring(L, name.c_str());
+
+	lua_newtable(L);
+	lua_pushstring(L, description.c_str());
+	lua_rawseti(L, -2, 1);
+
+	if (lua_pcall(L, 2, 0, 0) != 0) {
+		throw Lua::Exception(lua_tostring(L, -1));
+	}
+	lua_pop(L, 1);
+}
+
+void Swift::Lua::registerExtraHelp(lua_State* L, int index, const std::string& name) {
+	index = Lua::absoluteOffset(L, index);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
+	lua_getfield(L, -1, "extra_help");
+	lua_getfield(L, -1, name.c_str());
+	if (!lua_isnil(L, -1)) {
+		lua_getfield(L, -3, "register_help");
+		lua_pushvalue(L, index);
+		lua_pushvalue(L, -3);
+		if (lua_pcall(L, 2, 0, 0) != 0) {
+			throw Lua::Exception(lua_tostring(L, -1));
+		}
+	}
+	lua_pop(L, 3);
+}
diff --git a/Sluift/Lua/LuaUtils.h b/Sluift/Lua/LuaUtils.h
index f677307..19ab74e 100644
--- a/Sluift/Lua/LuaUtils.h
+++ b/Sluift/Lua/LuaUtils.h
@@ -24,6 +24,10 @@ namespace Swift {
 
 		void registerTableToString(lua_State* L, int index);
 		void registerGetByTypeIndex(lua_State* L, int index);
+		void registerHelp(lua_State* L, int index, 
+				const std::string& description, const std::string& parameters, const std::string& options);
+		void registerClassHelp(lua_State* L, const std::string& name, const std::string& description);
+		void registerExtraHelp(lua_State* L, int index, const std::string& name);
 
 		inline int absoluteOffset(lua_State* L, int index) {
 			return index > 0 ? index : lua_gettop(L) + index + 1;
diff --git a/Sluift/LuaElementConvertor.h b/Sluift/LuaElementConvertor.h
index 187ccf1..42bb69f 100644
--- a/Sluift/LuaElementConvertor.h
+++ b/Sluift/LuaElementConvertor.h
@@ -20,9 +20,20 @@ namespace Swift {
 		public:
 			static boost::optional<std::string> NO_RESULT;
 
+			struct Documentation {
+				Documentation(const std::string& className, const std::string& description) : 
+					className(className), description(description) {}
+				std::string className;
+				std::string description;
+			};
+
 			virtual ~LuaElementConvertor();
 
 			virtual boost::shared_ptr<Payload> convertFromLua(lua_State*, int index, const std::string& type) = 0;
 			virtual boost::optional<std::string> convertToLua(lua_State*, boost::shared_ptr<Payload>) = 0;
+
+			virtual boost::optional<Documentation> getDocumentation() const {
+				return boost::optional<Documentation>();
+			}
 	};
 }
diff --git a/Sluift/LuaElementConvertors.h b/Sluift/LuaElementConvertors.h
index 36da15a..65b1f04 100644
--- a/Sluift/LuaElementConvertors.h
+++ b/Sluift/LuaElementConvertors.h
@@ -37,6 +37,10 @@ namespace Swift {
 			 */
 			int convertToLuaUntyped(lua_State*, boost::shared_ptr<Payload>);
 
+			const std::vector< boost::shared_ptr<LuaElementConvertor> >& getConvertors() const {
+				return convertors;
+			}
+
 		private:
 			boost::optional<std::string> doConvertToLuaUntyped(lua_State*, boost::shared_ptr<Payload>);
 			void registerConvertors();
diff --git a/Sluift/SConscript b/Sluift/SConscript
index 116c5f1..d88e948 100644
--- a/Sluift/SConscript
+++ b/Sluift/SConscript
@@ -52,13 +52,16 @@ elif env["SCONS_STAGE"] == "build" :
 	version_header += "#define SLUIFT_VERSION_STRING \"" + Version.getBuildVersion(env.Dir("#").abspath, "sluift") + "\"\n"
 	sluift_env.WriteVal("Version.h", sluift_env.Value(version_header))
 
-	# Generate core.cpp
+	# Generate core.c
 	def generate_embedded_lua(env, target, source) :
 		f = open(source[0].abspath, "r")
 		data = f.read()
 		f.close()
+		data_bytes = bytearray(data)
 		f = open(target[0].abspath, "w")
-		f.write('const char ' + source[0].name.replace(".", "_") + "[] = \"" + data.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"') + "\";")
+		f.write('#include <stddef.h>\n')
+		f.write('const size_t ' + source[0].name.replace(".", "_") + "_size = " + str(len(data_bytes)) + ";\n")
+		f.write('const char ' + source[0].name.replace(".", "_") + "[] = {" + ", ".join([str(b) for b in data_bytes]) + "};\n")
 		f.close()
 	sluift_env.Command("core.c", ["core.lua"], env.Action(generate_embedded_lua, cmdstr="$GENCOMSTR"))
 
diff --git a/Sluift/client.cpp b/Sluift/client.cpp
index 9eac84b..16f1281 100644
--- a/Sluift/client.cpp
+++ b/Sluift/client.cpp
@@ -38,10 +38,8 @@
 using namespace Swift;
 namespace lambda = boost::lambda;
 
-static const std::string SLUIFT_CLIENT = Lua::FunctionRegistry::getMetaTableNameForType("Client");
-
 static inline SluiftClient* getClient(lua_State* L) {
-	return *Lua::checkUserData<SluiftClient>(L, 1, SLUIFT_CLIENT.c_str());
+	return *Lua::checkUserData<SluiftClient>(L, 1);
 }
 
 SLUIFT_LUA_FUNCTION(Client, async_connect) {
@@ -61,22 +59,47 @@ SLUIFT_LUA_FUNCTION(Client, async_connect) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, wait_connected) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, wait_connected,
+		"Block until the client is connected.\n\nThis is useful after an `async_connect`.",
+		"self",
+		""
+) {
 	getClient(L)->waitConnected();
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, is_connected) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, is_connected,
+		"Checks whether this client is still connected.\n\nReturns a boolean.",
+		"self\n",
+		""
+) {
 	lua_pushboolean(L, getClient(L)->isConnected());
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Client, disconnect) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, disconnect,
+		"Disconnect from the server",
+		"self\n",
+		""
+) {
 	getClient(L)->disconnect();
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, set_version) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, set_version,
+
+		"Sets the published version of this client.",
+
+		"self",
+
+		"name  the name of the client software\n"
+		"version  the version identifier of this client\n"
+		"os  the OS this client is running on\n"
+) {
 	Sluift::globals.eventLoop.runOnce();
 	SluiftClient* client = getClient(L);
 	if (boost::shared_ptr<SoftwareVersion> version = boost::dynamic_pointer_cast<SoftwareVersion>(Sluift::globals.elementConvertor.convertFromLuaUntyped(L, 2, "software_version"))) {
@@ -85,7 +108,12 @@ SLUIFT_LUA_FUNCTION(Client, set_version) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, get_contacts) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, get_contacts,
+		"Returns a table of all the contacts in the contact list.",
+		"self\n",
+		""
+) {
 	Sluift::globals.eventLoop.runOnce();
 
 	SluiftClient* client = getClient(L);
@@ -111,7 +139,15 @@ SLUIFT_LUA_FUNCTION(Client, get_contacts) {
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Client, send_message) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, send_message,
+		"Send a message.",
+		"self\n"
+		"body  the body of the message. Can alternatively be specified using the `body` option\n",
+		"to  the JID to send the message to\n"
+		"body  the body of the message\n"
+		"type  the type of message to send (`normal`, `chat`, `error`, `groupchat`, `headline`)\n"
+) {
 	Sluift::globals.eventLoop.runOnce();
 	JID to;
 	std::string body;
@@ -168,7 +204,18 @@ SLUIFT_LUA_FUNCTION(Client, send_message) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, send_presence) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, send_presence,
+		"Send presence.",
+
+		"self\n"
+		"body  the text of the presence. Can alternatively be specified using the `status` option\n",
+
+		"to  the JID to send the message to\n"
+		"status  the text of the presence\n"
+		"priority  the priority of the presence\n"
+		"type  the type of message to send (`available`, `error`, `probe`, `subscribe`, `subscribed`, `unavailable`, `unsubscribe`, `unsubscribed`)\n"
+) {
 	Sluift::globals.eventLoop.runOnce();
 	boost::shared_ptr<Presence> presence = boost::make_shared<Presence>();
 	
@@ -303,7 +350,15 @@ SLUIFT_LUA_FUNCTION(Client, set) {
 	return sendQuery(L, IQ::Set);
 }
 
-SLUIFT_LUA_FUNCTION(Client, send) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, send,
+		"Sends a raw string",
+
+		"self\n"
+		"data  the string to send\n",
+
+		""
+) {
 	Sluift::globals.eventLoop.runOnce();
 
 	getClient(L)->getClient()->sendData(std::string(Lua::checkString(L, 2)));
@@ -311,7 +366,21 @@ SLUIFT_LUA_FUNCTION(Client, send) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, set_options) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, set_options,
+
+		"Sets the connection options of this client.",
+
+		"self",
+
+		"host  The host to connect to. When omitted, is determined from resolving the JID domain.\n"
+		"port  The port to connect to. When omitted, is determined from resolving the JID domain.\n"
+		"ack  Request acknowledgements\n"
+		"compress  Use stream compression when available\n"
+		"tls  Use TLS when available\n"
+		"bosh_url  Connect using the specified BOSH URL\n"
+		"allow_plain_without_tls  Allow PLAIN authentication without a TLS encrypted connection\n"
+) {
 	SluiftClient* client = getClient(L);
 	Lua::checkType(L, 2, LUA_TTABLE);
 	lua_getfield(L, 2, "host");
@@ -498,7 +567,13 @@ SLUIFT_LUA_FUNCTION(Client, get_next_event) {
 }
 
 
-SLUIFT_LUA_FUNCTION(Client, add_contact) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, add_contact,
+		"Add a contact to the contact list.",
+		"self\n",
+		"jid  The JID of the contact to add\n"
+		"name  The name to use in the contact list\n"
+		"groups  An array of group names to add the contact to\n") {
 	Sluift::globals.eventLoop.runOnce();
 	SluiftClient* client = getClient(L);
 	RosterItemPayload item;
@@ -550,7 +625,13 @@ SLUIFT_LUA_FUNCTION(Client, add_contact) {
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Client, remove_contact) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, remove_contact,
+		"Remove a contact from the contact list.",
+		"self\n"
+		"jid  the JID of the contact to remove\n",
+		""
+) {
 	Sluift::globals.eventLoop.runOnce();
 	SluiftClient* client = getClient(L);
 	JID jid(Lua::checkString(L, 2));
@@ -562,7 +643,13 @@ SLUIFT_LUA_FUNCTION(Client, remove_contact) {
 		SetRosterRequest::create(roster, client->getClient()->getIQRouter()), -1).convertToLuaResult(L);
 }
 
-SLUIFT_LUA_FUNCTION(Client, confirm_subscription) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, confirm_subscription,
+		"Confirm subscription of a contact.",
+		"self\n"
+		"jid  the JID of the contact to confirm the subscription of\n",
+		""
+) {
 	Sluift::globals.eventLoop.runOnce();
 	SluiftClient* client = getClient(L);
 	JID jid(Lua::checkString(L, 2));
@@ -570,7 +657,13 @@ SLUIFT_LUA_FUNCTION(Client, confirm_subscription) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, cancel_subscription) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, cancel_subscription,
+		"Cancel the subscription of a contact.",
+		"self\n"
+		"jid  the JID of the contact to cancel the subscription of\n",
+		""
+) {
 	Sluift::globals.eventLoop.runOnce();
 	SluiftClient* client = getClient(L);
 	JID jid(Lua::checkString(L, 2));
@@ -578,7 +671,13 @@ SLUIFT_LUA_FUNCTION(Client, cancel_subscription) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, set_disco_info) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, set_disco_info,
+		"Sets the service discovery information for this client",
+		"self\n"
+		"disco_info  A structured representation of the service discovery information\n",
+		""
+) {
 	SluiftClient* client = getClient(L);
 	if (!lua_istable(L, 2)) {
 		throw Lua::Exception("Missing disco info");
@@ -592,14 +691,25 @@ SLUIFT_LUA_FUNCTION(Client, set_disco_info) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, set_caps_node) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, set_caps_node,
+		"Sets the caps node of this client",
+		"self\n"
+		"node  The caps node (e.g. 'http://swift.im/sluift')\n",
+		""
+) {
 	SluiftClient* client = getClient(L);
 	std::string node(Lua::checkString(L, 2));
 	client->getClient()->getDiscoManager()->setCapsNode(Lua::checkString(L, 2));
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Client, jid) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Client, jid,
+		"Returns the JID of this client",
+		"self\n",
+		""
+) {
 	SluiftClient* client = getClient(L);
 	lua_pushstring(L, client->getClient()->getJID().toString().c_str());
 	return 1;
diff --git a/Sluift/core.lua b/Sluift/core.lua
index e2d4f8e..aeb3286 100644
--- a/Sluift/core.lua
+++ b/Sluift/core.lua
@@ -5,47 +5,17 @@
 --]]
 
 local _G = _G
-local pairs, ipairs, print, tostring, type, error = pairs, ipairs, print, tostring, type, error
+local pairs, ipairs, print, tostring, type, error, assert, next, rawset, xpcall, unpack = pairs, ipairs, print, tostring, type, error, assert, next, rawset, xpcall, unpack
 local setmetatable, getmetatable = setmetatable, getmetatable
-local string = string
+local string = require "string"
+local table = require "table"
+local debug = require "debug"
 _ENV = nil
 
-local Client = {}
-local PubSub = {}
-local PubSubNode = {}
-
 --------------------------------------------------------------------------------
--- Utility methods
+-- Table 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
@@ -89,22 +59,311 @@ local function table_tostring(table, print_functions, indent, accumulator, histo
 	return accumulator
 end
 
-local function tprint(table)
-	print(table_tostring(table, true))
-end
-
-local function register_table_tostring(table)
+local function register_table_tostring(table, print_functions)
 	if type(table) == 'table' then
 		local metatable = getmetatable(table)
 		if not metatable then
 			metatable = {}
 			setmetatable(table, metatable)
 		end
-		metatable.__tostring = table_tostring
+		if print_functions then
+			metatable.__tostring = function(table) return table_tostring(table, true) end
+		else
+			metatable.__tostring = table_tostring
+		end
 	end
 	return table
 end
 
+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 copy(object)
+	if type(object) == 'table' then
+		local copy = {}
+		for key, value in pairs(object) do
+			copy[key] = value
+		end
+		return copy
+	else
+		return object
+	end
+end
+
+local function trim(string)
+	return string:gsub("^%s*(.-)%s*$", "%1")
+end
+
+--------------------------------------------------------------------------------
+-- Help
+--------------------------------------------------------------------------------
+
+-- Contains help for native methods that we want access to from here
+local extra_help = {}
+local help_data = {}
+local help_classes = {}
+local help_class_metatables = {}
+
+local _H
+
+local function get_synopsis(description) 
+	return description:gsub("[\n\r].*", "")
+end
+
+local function format_description(text)
+	local result = {}
+	local trim_whitespace
+	for line in (text .. "\n"):gmatch"(.-)\n" do
+		if not trim_whitespace and line:find('[^%s]') then
+			trim_whitespace = line:match("^(%s*)")
+		end
+		if trim_whitespace then
+			line = line:gsub("^" .. trim_whitespace, "")
+		end
+		table.insert(result, line)
+	end
+	return trim(table.concat(result, "\n"))
+end
+
+local function strip_links(text)
+	return text:gsub("(@{(%w*)})", "`%2`")
+end
+
+local function register_help(target, help) 
+	assert(target)
+	if not help then
+		help = _H
+	end
+	assert(help)
+
+	-- Transform description into canonical representation
+	local parameters = {}
+	for _, parameter in pairs(help.parameters or {}) do
+		local parameter_description = parameter[2]
+		if parameter_description and #parameter_description == 0 then
+			parameter_description = nil
+		end
+		if type(parameter) == "table" then
+			parameters[#parameters+1] = { name = parameter[1], description = parameter_description }
+		else
+			parameters[#parameters+1] = { name = parameter }
+		end
+	end
+	local options = {}
+	for option_name, option_description in pairs(help.options or {}) do
+		if type(option_description) == "table" then
+			options[#options+1] = { name = option_description.name, description = option_description.description }
+		else
+			options[#options+1] = { name = option_name, description = option_description }
+		end
+	end
+	local description = format_description(help[1] or help.description or "")
+	local synopsis = get_synopsis(description)
+	if #description == 0 then
+		synopsis = nil
+		description = nil
+	end
+	local data = {
+		description = description,
+		synopsis = synopsis,
+		parameters = parameters,
+		options = options,
+		classes = help.classes
+	}
+	register_table_tostring(data, true)
+	help_data[target] = data
+end
+
+local function register_class_help(class, help)
+	help_classes[#help_classes+1] = class
+	register_help(class, help)
+end
+
+local function register_class_table_help(target, class, help)
+	register_help(target, help)
+	help_class_metatables[class] = target
+	register_class_help(class, help)
+end
+
+_H = {
+	[[ 
+		Retrieves the help information from `target`.
+
+		Returns a table with the following fields: 
+
+		- `description`: the description of `target`
+		- `parameters`: an array of parameters of `target` represented as tables with `name` and `description` fields.
+		- `options`: an array of options (named parameters) of `target` represented as tables with `name` and 
+		  `description` fields.
+		- `methods`: an array of methods
+		- `fields`: an array of fields
+	]],
+	parameters = { {"target", "The target to retrieve help of"} }
+}
+local function get_help(target) 
+	if not target then error("Nil argument or argument missing") end
+	local help = help_data[target] or help_data[getmetatable(target)] or {}
+
+	-- Collect child methods and fields
+	local children = {}
+	if type(target) == "table" then children = target end
+	local mt
+	if type(target) == "string" then
+		mt = help_class_metatables[target]
+	else
+		mt = getmetatable(target)
+	end
+	if mt and type(mt.__index) == "table" then
+		children = merge_tables(children, mt.__index)
+	end
+
+	local methods = {}
+	local fields = {}
+	for name, value in pairs(children) do
+		if name:sub(1, 1) ~= "_" then 
+			if type(value) == "function" then
+				methods[#methods+1] = { name = name, ref = value }
+			else
+				fields[#fields+1] = { name = name, description = nil }
+			end
+		end
+	end
+	if next(methods) ~= nil then
+		help.methods = methods
+	end
+	if next(fields) ~= nil then
+		help.fields = fields
+	end
+	if next(help) then
+		return help
+	else
+		return nil
+	end
+end
+register_help(get_help)
+
+_H = {
+	[[ 
+		Prints the help of `target`.
+
+		`target` can be any object. When `target` is a string, prints the help of the class with
+		the given name.
+	]],
+	parameters = { {"target", "The target to retrieve help of"} }
+}
+local function help(target)
+	print()
+	if not target then 
+		print("Call `help(target)` to get the help of a specific `target`.")
+		print("`target` can be any object. When `target` is a string, prints")
+		print("the help of the class with the given name.")
+		print()
+		print("For general information about sluift, type:")
+		print("  help(sluift)")
+		print()
+		return
+	end
+	local data = get_help(target)
+	if not data then
+		print("No help available\n")
+		return
+	end
+
+	-- Collect help of children
+	local methods = {}
+	for _, method in pairs(data.methods or {}) do
+		local description
+		local method_help = get_help(method.ref)
+		if method_help and method_help.description then
+			description = method_help.synopsis
+		end
+		methods[#methods+1] = { name = method.name, description = description }
+	end
+	local fields = copy(data.fields or {})
+
+	table.sort(methods, function (a, b) return (a.name or "") < (b.name or "") end)
+	table.sort(fields, function (a, b) return (a.name or "") < (b.name or "") end)
+
+	local classes = {}
+	for _, class in pairs(data.classes or {}) do
+		classes[#classes+1] = { name = class, description = get_help(class).synopsis }
+	end
+
+	print(strip_links(data.description) or "(No description available)")
+	for _, p in ipairs({
+			{"Parameters", data.parameters}, {"Options", data.options}, {"Methods", methods}, {"Fields", fields}, {"Classes", classes}}) do
+		if p[2] and next(p[2]) ~= nil then
+			print()
+			print(p[1] .. ":")
+			for _, parameter in ipairs(p[2]) do
+				if parameter.description then
+					print("  " .. parameter.name .. ": " .. strip_links(parameter.description))
+				else
+					print("  " .. parameter.name)
+				end
+			end
+		end
+	end
+
+	print()
+end
+register_help(help)
+
+--------------------------------------------------------------------------------
+-- Utility methods
+--------------------------------------------------------------------------------
+
+_H = {
+	[[ Perform a shallow copy of `object`. ]],
+	parameters = {{"object", "the object to copy"}}
+}
+register_help(copy)
+
+_H = {
+	[[ Pretty-print a table ]],
+	parameters = {{"table", "the table to print"}}
+}
+local function tprint(table)
+	print(table_tostring(table, true))
+end
+register_help(tprint)
+
+local function remove_help_parameters(elements, table)
+	if type(elements) ~= "table" then
+		elements = {elements}
+	end
+	local result = copy(table)
+	for k, v in ipairs(table) do
+		for _, element in ipairs(elements) do
+			if v.name == element then
+				result[k] = nil
+			end
+		end
+	end
+	return result
+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 copy(options)
+end
+
+
 local function get_by_type(table, typ)
 	for _, v in ipairs(table) do
 		if v['_type'] == typ then
@@ -140,9 +399,86 @@ local function call(options)
 end
 
 --------------------------------------------------------------------------------
+-- Metatables
+--------------------------------------------------------------------------------
+
+_H = {
+	[[ Client interface ]]
+}
+local Client = {}
+Client.__index = Client
+register_class_table_help(Client, "Client")
+
+
+_H = {
+	[[ Interface to communicate with a PubSub service ]]
+}
+local PubSub = {}
+PubSub.__index = PubSub
+register_class_table_help(PubSub, "PubSub")
+
+_H = {
+	[[ Interface to communicate with a PubSub node on a service ]]
+}
+local PubSubNode = {}
+PubSubNode.__index = PubSubNode
+register_class_table_help(PubSubNode, "PubSubNode")
+
+--------------------------------------------------------------------------------
 -- Client
 --------------------------------------------------------------------------------
 
+extra_help = {
+	["Client.get_next_event"] = {
+		[[ Returns the next event. ]],
+		parameters = { "self" },
+		options = {
+			type = "The type of event to return (`message`, `presence`, `pubsub`). When omitted, all event types are returned.",
+			timeout = "The amount of time to wait for events.",
+			["if"] = "A function to filter events. When this function, called with the event as a parameter, returns true, the event will be returned"
+		}
+	},
+	["Client.get"] = {
+		[[ Sends a `get` query. ]],
+		parameters = { "self" },
+		options = {
+			to = "The JID of the target to send the query to",
+			query = "The query to send",
+			timeout = "The amount of time to wait for the query to finish",
+		}
+	},
+	["Client.set"] = {
+		[[ Sends a `set` query. ]],
+		parameters = { "self" },
+		options = {
+			to = "The JID of the target to send the query to",
+			query = "The query to send.",
+			timeout = "The amount of time to wait for the query to finish.",
+		}
+	},
+	["Client.async_connect"] = {
+		[[ 
+			Connect to the server asynchronously.
+			
+			This method immediately returns.
+		]],
+		parameters = { "self" },
+		options = {
+			host = "The host to connect to. When omitted, is determined by resolving the client JID.",
+			port = "The port to connect to. When omitted, is determined by resolving the client JID."
+		}
+	}
+}
+
+_H = {
+	[[
+		Connect to the server.
+
+		This method blocks until the connection has been established.
+	]],
+	parameters = { "self" },
+	options = extra_help["Client.async_connect"].options
+}
 function Client:connect (...)
 	local options = parse_options({}, ...)
 	local f = options.f
@@ -153,14 +489,36 @@ function Client:connect (...)
 	end
 	return true
 end
+register_help(Client.connect)
+
+
+_H = {
+	[[
+		Returns an iterator over all events.
 
+		This function blocks until `timeout` is reached (or blocks forever if it is omitted).
+	]],
+	parameters = { "self" },
+	options = extra_help["Client.get_next_event"].options
+}
 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
+register_help(Client.events)
+
 
+_H = {
+	[[
+		Calls `f` for each event.
+	]],
+	parameters = { "self" },
+	options = merge_tables(get_help(Client.events).options, {
+		f = "The functor to call with each event. Required."
+	})
+}
 function Client:for_each_event (...)
 	local options = parse_options({}, ...)
 	if not type(options.f) == 'function' then error('Expected function') end
@@ -171,33 +529,59 @@ function Client:for_each_event (...)
 		end
 	end
 end
+register_help(Client.for_each_event)
 
 for method, event_type in pairs({message = 'message', presence = 'presence', pubsub_event = 'pubsub'}) do
+	_H = {
+		"Call `f` for all events of type `" .. event_type .. "`.",
+		parameters = { "self" },
+		options = remove_help_parameters("type", get_help(Client.for_each_event).options)
+	}
 	Client['for_each_' .. method] = function (client, ...)
 		local options = parse_options({}, ...)
 		options['type'] = event_type
 		return client:for_each_event (options)
 	end
+	register_help(Client['for_each_' .. method])
 
+	_H = {
+		"Get the next event of type `" .. event_type .. "`.",
+		parameters = { "self" },
+		options = remove_help_parameters("type", extra_help["Client.get_next_event"].options)
+	}
 	Client['get_next_' .. method] = function (client, ...)
 		local options = parse_options({}, ...)
 		options['type'] = event_type
 		return client:get_next_event(options)
 	end
+	register_help(Client['get_next_' .. method])
 end
 
 for method, event_type in pairs({messages = 'message', pubsub_events = 'pubsub'}) do
+	_H = {
+		"Returns an iterator over all events of type `" .. event_type .. "`.",
+		parameters = { "self" },
+		options = remove_help_parameters("type", get_help(Client.for_each_event).options)
+	}
 	Client[method] = function (client, ...)
 		local options = parse_options({}, ...)
 		options['type'] = event_type
 		return client:events (options)
 	end
+	register_help(Client[method])
 end
 
--- Process all pending events
+_H = {
+	[[ 
+		Process all pending events
+	]],
+	parameters = { "self" }
+}
 function Client:process_events ()
 	for event in self:events{timeout=0} do end
 end
+register_help(Client.process_events)
+
 
 --
 -- Register get_* and set_* convenience methods for some type of queries
@@ -212,27 +596,41 @@ local get_set_shortcuts = {
 }
 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)
+		_H = {
+			"Sends a `" .. query_action .. "` query of type `" .. query_type .. "`.\n" ..
+			"Apart from the options below, all top level elements of `" .. query_type .. "` can be passed.",
+			parameters = { "self" },
+			options = remove_help_parameters({"query", "type"}, extra_help["Client.get"].options),
+		}
+		local method = query_action .. '_' .. query_type
+		Client[method] = 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
+		register_help(Client[method])
 	end
 end
 
+_H = {
+	[[ Returns a @{PubSub} object for communicating with the PubSub service at `jid`. ]],
+	parameters = { 
+		"self", 
+		{"jid", "The JID of the PubSub service"}
+	}
+}
 function Client:pubsub (jid)
 	local result = { client = self, jid = jid }
 	setmetatable(result, PubSub)
 	return result
 end
+register_help(Client.pubsub)
 
 --------------------------------------------------------------------------------
 -- 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
@@ -280,8 +678,6 @@ end
 -- PubSubNode
 --------------------------------------------------------------------------------
 
-PubSubNode.__index = PubSubNode
-
 local function pubsub_node_configuration_to_form(configuration)
 	if not configuration then
 		return
@@ -463,11 +859,29 @@ local disco = {
 
 --------------------------------------------------------------------------------
 
+_H = nil
+
+extra_help['sluift'] = {
+	[[
+		This module provides methods for XMPP communication.
+
+		The main entry point of this module is the `new_client` method, which creates a
+		new client for communicating with an XMPP server.
+	]],
+	classes = help_classes
+}
+
 return {
 	Client = Client,
+	register_help = register_help,
+	register_class_help = register_class_help,
 	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,
+	get_help = get_help,
+	help = help,
+	extra_help = extra_help,
+	copy = copy,
 }
diff --git a/Sluift/main.cpp b/Sluift/main.cpp
index fcff2aa..e2fa9c8 100644
--- a/Sluift/main.cpp
+++ b/Sluift/main.cpp
@@ -32,7 +32,7 @@ using namespace Swift;
 #endif
 
 static const std::string SLUIFT_WELCOME_STRING(
-		"== Sluift XMPP Console (" SLUIFT_VERSION_STRING ")\nPress Ctrl-" EXIT_KEY " to exit");
+		"== Sluift XMPP Console (" SLUIFT_VERSION_STRING ")\nPress Ctrl-" EXIT_KEY " to exit. Type help() for help.");
 
 static const luaL_Reg defaultLibraries[] = {
 	{"", luaopen_base},
@@ -57,9 +57,14 @@ static void checkResult(lua_State* L, int result) {
 static void initialize(lua_State* L) {
 	lua_gc(L, LUA_GCSTOP, 0);
 	for (const luaL_Reg* lib = defaultLibraries; lib->func; lib++) {
+#if LUA_VERSION_NUM >= 502
+		luaL_requiref(L, lib->name, lib->func, 1);
+		lua_pop(L, 1);
+#else
 		lua_pushcfunction(L, lib->func);
 		lua_pushstring(L, lib->name);
 		lua_call(L, 1, 0);
+#endif
 	}
 	lua_gc(L, LUA_GCRESTART, 0);
 }
@@ -81,10 +86,6 @@ static void runScript(lua_State* L, const std::string& script, const std::vector
 	checkResult(L, Console::call(L, boost::numeric_cast<int>(scriptArguments.size()), false));
 }
 
-// void runConsole() {
-	// contents = contents.replace("LUA_RELEASE", "\"== Sluift XMPP Console (%(version)s) == \\nPress Ctrl-%(key)s to exit\"" % {"version": source[1].get_contents(), "key" : key})
-// }
-
 int main(int argc, char* argv[]) {
 	// Parse program options
 	boost::program_options::options_description visibleOptions("Options");
@@ -148,6 +149,11 @@ int main(int argc, char* argv[]) {
 
 		// Run console
 		if (arguments.count("interactive") || arguments.count("script") == 0) {
+			// Import some useful functions into the global namespace
+			lua_getglobal(L, "sluift");
+			lua_getfield(L, -1, "help");
+			lua_setglobal(L, "help");
+			
 			std::cout << SLUIFT_WELCOME_STRING << std::endl;
 #ifdef HAVE_EDITLINE
 			EditlineTerminal& terminal = EditlineTerminal::getInstance();
diff --git a/Sluift/sluift.cpp b/Sluift/sluift.cpp
index e6b2bb6..b2bdc29 100644
--- a/Sluift/sluift.cpp
+++ b/Sluift/sluift.cpp
@@ -18,6 +18,7 @@
 #include <Sluift/SluiftClient.h>
 #include <Sluift/globals.h>
 #include <Sluift/Lua/Exception.h>
+#include <Sluift/Lua/LuaUtils.h>
 #include <Sluift/Lua/FunctionRegistration.h>
 #include <Swiften/Base/sleep.h>
 #include <Swiften/Base/foreach.h>
@@ -25,6 +26,7 @@
 #include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
 #include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h>
 #include <Swiften/Serializer/PayloadSerializer.h>
+#include <Sluift/LuaElementConvertor.h>
 #include <Sluift/Lua/Debug.h>
 #include <Swiften/StringCodecs/Base64.h>
 #include <Swiften/StringCodecs/Hexify.h>
@@ -41,25 +43,43 @@ namespace Swift {
 }
 
 extern "C" const char core_lua[];
+extern "C" size_t core_lua_size;
 
 /*******************************************************************************
  * Module functions
  ******************************************************************************/
 
-SLUIFT_LUA_FUNCTION(Sluift, new_client) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, new_client,
+
+		"Creates a new client.\n\nReturns a @{Client} object.\n",
+
+		"jid  The JID to connect as\n"
+		"passphrase  The passphrase to use\n",
+
+		""
+) {
 	Lua::checkString(L, 1);
 	JID jid(std::string(Lua::checkString(L, 1)));
 	std::string password(Lua::checkString(L, 2));
 
 	SluiftClient** client = reinterpret_cast<SluiftClient**>(lua_newuserdata(L, sizeof(SluiftClient*)));
-	luaL_getmetatable(L, Lua::FunctionRegistry::getMetaTableNameForType("Client").c_str());
-	lua_setmetatable(L, -2);
+
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
+	lua_getfield(L, -1, "Client");
+	lua_setmetatable(L, -3);
+	lua_pop(L, 1);
 
 	*client = new SluiftClient(jid, password, &Sluift::globals.networkFactories, &Sluift::globals.eventLoop, &Sluift::globals);
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, sha1) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, sha1,
+		"Compute the SHA-1 hash of given data",
+		"data  the data to hash",
+		""
+) {
 	static boost::shared_ptr<CryptoProvider> crypto(PlatformCryptoProvider::create());
 	if (!lua_isstring(L, 1)) {
 		throw Lua::Exception("Expected string");
@@ -71,7 +91,12 @@ SLUIFT_LUA_FUNCTION(Sluift, sha1) {
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, sleep) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, sleep,
+		"Sleeps for the given time.",
+		"milliseconds  the amount of milliseconds to sleep",
+		""
+) {
 	Sluift::globals.eventLoop.runOnce();
 	int timeout = Lua::checkIntNumber(L, 1);
 	Watchdog watchdog(timeout, Sluift::globals.networkFactories.getTimerFactory());
@@ -82,7 +107,10 @@ SLUIFT_LUA_FUNCTION(Sluift, sleep) {
 	return 0;
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, new_uuid) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, new_uuid,
+		"Generates a new UUID", "", ""
+) {
 	lua_pushstring(L, IDGenerator().generateID().c_str());
 	return 1;
 }
@@ -122,7 +150,12 @@ static int sluift_newindex(lua_State* L) {
 	}
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, from_xml) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, from_xml,
+		"Convert a raw XML string into a structured representation.",
+		"string  the string to convert",
+		""
+) {
 	PayloadsParserTester parser;
 	if (!parser.parse(Lua::checkString(L, 1))) {
 		throw Lua::Exception("Error in XML");
@@ -130,7 +163,12 @@ SLUIFT_LUA_FUNCTION(Sluift, from_xml) {
 	return Sluift::globals.elementConvertor.convertToLua(L, parser.getPayload());
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, to_xml) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, to_xml,
+		"Convert a structured element into XML.",
+		"element  the element to convert",
+		""
+) {
 	static FullPayloadSerializerCollection serializers;
 	boost::shared_ptr<Payload> payload = Sluift::globals.elementConvertor.convertFromLua(L, 1);
 	if (!payload) {
@@ -144,7 +182,12 @@ SLUIFT_LUA_FUNCTION(Sluift, to_xml) {
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, hexify) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, hexify,
+		"Convert binary data into hexadecimal format.",
+		"data  the data to convert",
+		""
+) {
 	if (!lua_isstring(L, 1)) {
 		throw Lua::Exception("Expected string");
 	}
@@ -154,7 +197,12 @@ SLUIFT_LUA_FUNCTION(Sluift, hexify) {
 	return 1;
 }
 
-SLUIFT_LUA_FUNCTION(Sluift, unhexify) {
+SLUIFT_LUA_FUNCTION_WITH_HELP(
+		Sluift, unhexify,
+		"Convert hexadecimal data into binary data.",
+		"data  the data in hexadecimal format",
+		""
+) {
 	if (!lua_isstring(L, 1)) {
 		throw Lua::Exception("Expected string");
 	}
@@ -273,7 +321,7 @@ SLUIFT_API int luaopen_sluift(lua_State* L) {
 	luaL_register(L, lua_tostring(L, 1), sluift_functions);
 
 	// Load core lib code
-	if (luaL_loadbuffer(L, core_lua, strlen(core_lua), "core.lua") != 0) {
+	if (luaL_loadbuffer(L, core_lua, core_lua_size, "core.lua") != 0) {
 		lua_error(L);
 	}
 	lua_call(L, 0, 1);
@@ -291,7 +339,7 @@ SLUIFT_API int luaopen_sluift(lua_State* L) {
 	// Register convenience functions
 	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
 	std::vector<std::string> coreLibExports = boost::assign::list_of
-		("tprint")("disco");
+		("tprint")("disco")("help")("get_help")("copy");
 	foreach (const std::string& coreLibExport, coreLibExports) {
 		lua_getfield(L, -1, coreLibExport.c_str());
 		lua_setfield(L, -3, coreLibExport.c_str());
@@ -307,21 +355,25 @@ SLUIFT_API int luaopen_sluift(lua_State* L) {
 	lua_setmetatable(L, -2);
 	
 	// Load client metatable
+	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex);
 	std::vector<std::string> tables = boost::assign::list_of("Client");
-	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.coreLibIndex);
+	foreach(const std::string& table, tables) {
 		lua_getfield(L, -1, table.c_str());
-		if (!lua_isnil(L, -1)) {
-			for (lua_pushnil(L); lua_next(L, -2); ) {
-				lua_pushvalue(L, -2);
-				lua_pushvalue(L, -2);
-				lua_settable(L, -7);
-				lua_pop(L, 1);
-			}
+		Lua::FunctionRegistry::getInstance().addFunctionsToTable(L, table);
+		lua_pop(L, 1);
+	}
+	lua_pop(L, 1);
+
+	// Register documentation for all elements
+	foreach (boost::shared_ptr<LuaElementConvertor> convertor, Sluift::globals.elementConvertor.getConvertors()) {
+		boost::optional<LuaElementConvertor::Documentation> documentation = convertor->getDocumentation();
+		if (documentation) {
+			Lua::registerClassHelp(L, documentation->className, documentation->description);
 		}
-		lua_pop(L, 2);
 	}
+
+	// Register global documentation
+	Lua::registerExtraHelp(L, -1, "sluift");
+
 	return 1;
 }
-- 
cgit v0.10.2-6-g49f6