summaryrefslogtreecommitdiffstats
path: root/Sluift
diff options
context:
space:
mode:
authorRemko Tronçon <git@el-tramo.be>2013-12-30 11:21:45 (GMT)
committerRemko Tronçon <git@el-tramo.be>2014-01-03 11:09:06 (GMT)
commit26bb5aa9e2f520c3c943797e6143c32e5b16806b (patch)
tree0caa41938acf53da946847f8803c62e579525af9 /Sluift
parent0b19dc7292b7672c9fbb711a411c392bc5b2bb34 (diff)
downloadswift-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
Diffstat (limited to 'Sluift')
-rw-r--r--Sluift/ElementConvertors/DiscoInfoConvertor.cpp15
-rw-r--r--Sluift/ElementConvertors/DiscoInfoConvertor.h1
-rw-r--r--Sluift/GenerateDocumentation.lua90
-rw-r--r--Sluift/Lua/Check.cpp8
-rw-r--r--Sluift/Lua/Check.h6
-rw-r--r--Sluift/Lua/FunctionRegistration.cpp4
-rw-r--r--Sluift/Lua/FunctionRegistration.h13
-rw-r--r--Sluift/Lua/FunctionRegistry.cpp27
-rw-r--r--Sluift/Lua/FunctionRegistry.h9
-rw-r--r--Sluift/Lua/LuaUtils.cpp101
-rw-r--r--Sluift/Lua/LuaUtils.h4
-rw-r--r--Sluift/LuaElementConvertor.h11
-rw-r--r--Sluift/LuaElementConvertors.h4
-rw-r--r--Sluift/SConscript7
-rw-r--r--Sluift/client.cpp148
-rw-r--r--Sluift/core.lua508
-rw-r--r--Sluift/main.cpp16
-rw-r--r--Sluift/sluift.cpp100
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;
}