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