diff options
| author | Remko Tronçon <git@el-tramo.be> | 2013-12-30 11:21:45 (GMT) | 
|---|---|---|
| committer | Remko Tronçon <git@el-tramo.be> | 2014-01-03 11:09:06 (GMT) | 
| commit | 26bb5aa9e2f520c3c943797e6143c32e5b16806b (patch) | |
| tree | 0caa41938acf53da946847f8803c62e579525af9 | |
| parent | 0b19dc7292b7672c9fbb711a411c392bc5b2bb34 (diff) | |
| download | swift-26bb5aa9e2f520c3c943797e6143c32e5b16806b.zip swift-26bb5aa9e2f520c3c943797e6143c32e5b16806b.tar.bz2  | |
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
| -rw-r--r-- | Sluift/ElementConvertors/DiscoInfoConvertor.cpp | 15 | ||||
| -rw-r--r-- | Sluift/ElementConvertors/DiscoInfoConvertor.h | 1 | ||||
| -rw-r--r-- | Sluift/GenerateDocumentation.lua | 90 | ||||
| -rw-r--r-- | Sluift/Lua/Check.cpp | 8 | ||||
| -rw-r--r-- | Sluift/Lua/Check.h | 6 | ||||
| -rw-r--r-- | Sluift/Lua/FunctionRegistration.cpp | 4 | ||||
| -rw-r--r-- | Sluift/Lua/FunctionRegistration.h | 13 | ||||
| -rw-r--r-- | Sluift/Lua/FunctionRegistry.cpp | 27 | ||||
| -rw-r--r-- | Sluift/Lua/FunctionRegistry.h | 9 | ||||
| -rw-r--r-- | Sluift/Lua/LuaUtils.cpp | 101 | ||||
| -rw-r--r-- | Sluift/Lua/LuaUtils.h | 4 | ||||
| -rw-r--r-- | Sluift/LuaElementConvertor.h | 11 | ||||
| -rw-r--r-- | Sluift/LuaElementConvertors.h | 4 | ||||
| -rw-r--r-- | Sluift/SConscript | 7 | ||||
| -rw-r--r-- | Sluift/client.cpp | 148 | ||||
| -rw-r--r-- | Sluift/core.lua | 508 | ||||
| -rw-r--r-- | Sluift/main.cpp | 16 | ||||
| -rw-r--r-- | Sluift/sluift.cpp | 100 | 
18 files changed, 945 insertions, 127 deletions
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;  }  | 
 Swift