summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdwin Mons <edwin.mons@isode.com>2014-05-23 09:01:23 (GMT)
committerSwift Review <review@swift.im>2014-06-22 12:35:26 (GMT)
commitbd7f30aec53fc776be678577dbe4f9afec5898a6 (patch)
tree66afad4382dc16f7405a856dd0b5abc38db51653 /Sluift/core.lua
parent1eb14b6bde145ca54ac9b981df339fb8c56d3930 (diff)
downloadswift-contrib-bd7f30aec53fc776be678577dbe4f9afec5898a6.zip
swift-contrib-bd7f30aec53fc776be678577dbe4f9afec5898a6.tar.bz2
Sluift component support
Change-Id: Ib8af01c04c866e198c04d35236dea4da464c9116
Diffstat (limited to 'Sluift/core.lua')
-rw-r--r--Sluift/core.lua202
1 files changed, 202 insertions, 0 deletions
diff --git a/Sluift/core.lua b/Sluift/core.lua
index 7487de1..ffbb5f9 100644
--- a/Sluift/core.lua
+++ b/Sluift/core.lua
@@ -112,70 +112,71 @@ local function copy(object)
return object
end
end
local function clear(table)
setmetatable(table, nil)
for key, value in pairs(table) do
rawset(table, key, nil)
end
end
local function trim(string)
return string:gsub("^%s*(.-)%s*$", "%1")
end
local function keys(table)
local result = {}
for key in pairs(table) do
result[#result+1] = key
end
return result
end
local function insert_all(table, values)
for _, value in pairs(values) do
table[#table+1] = value
end
end
--------------------------------------------------------------------------------
-- Help
--------------------------------------------------------------------------------
-- Contains help for native methods that we want access to from here
local extra_help = {}
+local component_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)
@@ -449,70 +450,79 @@ _H = {
{"type", "Form type, e.g. 'submit'"} }
}
local function create_form(...)
local options = parse_options({}, ...)
local result = { fields = {} }
-- FIXME: make nicer when parse_options binds positional arguments to names
if options.fields then
for var, value in pairs(options.fields) do
result.fields[#result.fields+1] = { name = var, value = value }
end
elseif options[1] then
for var, value in pairs(options[1]) do
result.fields[#result.fields+1] = { name = var, value = value }
end
end
if options.form_type then
result.fields[#result.fields+1] = { name = 'FORM_TYPE', value = options.form_type }
end
result['type'] = options.type
return result
end
--------------------------------------------------------------------------------
-- Metatables
--------------------------------------------------------------------------------
_H = {
[[ Client interface ]]
}
local Client = {
_with_prompt = function(client) return client:jid() end
}
Client.__index = Client
register_class_table_help(Client, "Client")
+_H = {
+ [[ Component interface ]]
+}
+local Component = {
+ _with_prompt = function(component) return component:jid() end
+}
+Component.__index = Component
+register_class_table_help(Component, "Component")
+
_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")
--------------------------------------------------------------------------------
-- with
--------------------------------------------------------------------------------
local original_G
local function with (target, f)
-- Dynamic scope
if f then
with(target)
return call{f, finally = function() with() end}
end
-- No scope
if target then
if not original_G then
original_G = copy(_G)
setmetatable(original_G, getmetatable(_G))
clear(_G)
@@ -751,70 +761,260 @@ local get_set_shortcuts = {
set = {'command', 'mam'}
}
for query_action, query_types in pairs(get_set_shortcuts) do
for _, query_type in ipairs(query_types) do
_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)
+
+--------------------------------------------------------------------------------
+-- Component
+--------------------------------------------------------------------------------
+
+component_extra_help = {
+ ["Component.get_next_event"] = {
+ [[ Returns the next event. ]],
+ parameters = { "self" },
+ options = {
+ type = "The type of event to return (`message`, `presence`). 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"
+ }
+ },
+ ["Component.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",
+ }
+ },
+ ["Component.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.",
+ }
+ },
+ ["Component.async_connect"] = {
+ [[
+ Connect to the server asynchronously.
+
+ This method immediately returns.
+ ]],
+ parameters = { "self" },
+ options = {
+ host = "The host to connect to.",
+ port = "The port to connect to."
+ }
+ }
+}
+
+_H = {
+ [[
+ Connect to the server.
+
+ This method blocks until the connection has been established.
+ ]],
+ parameters = { "self" },
+ options = component_extra_help["Component.async_connect"].options
+}
+function Component:connect (...)
+ local options = parse_options({}, ...)
+ local f = options.f
+ self:async_connect(options)
+ self:wait_connected()
+ if f then
+ return call {function() return f(self) end, finally = function() self:disconnect() end}
+ end
+ return true
+end
+register_help(Component.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 = component_extra_help["Component.get_next_event"].options
+}
+function Component:events (options)
+ local function component_events_iterator(s)
+ return s['component']:get_next_event(s['options'])
+ end
+ return component_events_iterator, {component = self, options = options}
+end
+register_help(Component.events)
+
+
+_H = {
+ [[
+ Calls `f` for each event.
+ ]],
+ parameters = { "self" },
+ options = merge_tables(get_help(Component.events).options, {
+ f = "The functor to call with each event. Required."
+ })
+}
+function Component:for_each_event (...)
+ local options = parse_options({}, ...)
+ if not type(options.f) == 'function' then error('Expected function') end
+ for event in self:events(options) do
+ local result = options.f(event)
+ if result then
+ return result
+ end
+ end
+end
+register_help(Component.for_each_event)
+
+for method, event_type in pairs({message = 'message', presence = 'presence'}) do
+ _H = {
+ "Call `f` for all events of type `" .. event_type .. "`.",
+ parameters = { "self" },
+ options = remove_help_parameters("type", get_help(Component.for_each_event).options)
+ }
+ Component['for_each_' .. method] = function (component, ...)
+ local options = parse_options({}, ...)
+ options['type'] = event_type
+ return component:for_each_event (options)
+ end
+ register_help(Component['for_each_' .. method])
+
+ _H = {
+ "Get the next event of type `" .. event_type .. "`.",
+ parameters = { "self" },
+ options = remove_help_parameters("type", component_extra_help["Component.get_next_event"].options)
+ }
+ Component['get_next_' .. method] = function (component, ...)
+ local options = parse_options({}, ...)
+ options['type'] = event_type
+ return component:get_next_event(options)
+ end
+ register_help(Component['get_next_' .. method])
+end
+
+for method, event_type in pairs({messages = 'message'}) do
+ _H = {
+ "Returns an iterator over all events of type `" .. event_type .. "`.",
+ parameters = { "self" },
+ options = remove_help_parameters("type", get_help(Component.for_each_event).options)
+ }
+ Component[method] = function (component, ...)
+ local options = parse_options({}, ...)
+ options['type'] = event_type
+ return component:events (options)
+ end
+ register_help(Component[method])
+end
+
+_H = {
+ [[
+ Process all pending events
+ ]],
+ parameters = { "self" }
+}
+function Component:process_events ()
+ for event in self:events{timeout=0} do end
+end
+register_help(Component.process_events)
+
+
+--
+-- Register get_* and set_* convenience methods for some type of queries
+--
+-- Example usages:
+-- component:get_software_version{to = 'alice@wonderland.lit'}
+-- component:set_command{to = 'alice@wonderland.lit', command = { type = 'execute', node = 'uptime' }}
+--
+local get_set_shortcuts = {
+ get = {'software_version', 'disco_items', 'xml', 'dom', 'vcard'},
+ set = {'command'}
+}
+for query_action, query_types in pairs(get_set_shortcuts) do
+ for _, query_type in ipairs(query_types) do
+ _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"}, component_extra_help["Component.get"].options),
+ }
+ local method = query_action .. '_' .. query_type
+ Component[method] = function (component, 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 component[query_action](component, options)
+ end
+ register_help(Component[method])
+ end
+end
+
--------------------------------------------------------------------------------
-- PubSub
--------------------------------------------------------------------------------
local function process_pubsub_event (event)
if event._type == 'pubsub_event_items' then
-- Add 'item' shortcut to payload of first item
event.item = event.items and event.items[1] and
event.items[1].data and event.items[1].data[1]
end
end
function PubSub:list_nodes (options)
return self.client:get_disco_items(merge_tables({to = self.jid}, options))
end
function PubSub:node (node)
local result = { client = self.client, jid = self.jid, node = node }
setmetatable(result, PubSubNode)
return result
end
local simple_pubsub_queries = {
get_default_configuration = 'pubsub_owner_default',
get_subscriptions = 'pubsub_subscriptions',
get_affiliations = 'pubsub_affiliations',
get_default_subscription_options = 'pubsub_default',
}
for method, query_type in pairs(simple_pubsub_queries) do
PubSub[method] = function (service, options)
options = options or {}
return service.client:query_pubsub(merge_tables(
{ type = 'get', to = service.jid, query = { _type = query_type } },
options))
end
@@ -991,51 +1191,53 @@ for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
end
end
--------------------------------------------------------------------------------
-- Service discovery
--------------------------------------------------------------------------------
local disco = {
features = {
DISCO_INFO = 'http://jabber.org/protocol/disco#info',
COMMANDS = 'http://jabber.org/protocol/commands',
USER_LOCATION = 'http://jabber.org/protocol/geoloc',
USER_TUNE = 'http://jabber.org/protocol/tune',
USER_AVATAR_METADATA = 'urn:xmpp:avatar:metadata',
USER_ACTIVITY = 'http://jabber.org/protocol/activity',
USER_PROFILE = 'urn:xmpp:tmp:profile'
}
}
--------------------------------------------------------------------------------
_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,
+ Component = Component,
register_help = register_help,
register_class_help = register_class_help,
register_table_tostring = register_table_tostring,
register_table_equals = register_table_equals,
register_get_by_type_index = register_get_by_type_index,
process_pubsub_event = process_pubsub_event,
tprint = tprint,
read_file = read_file,
disco = disco,
get_help = get_help,
help = help,
extra_help = extra_help,
+ component_extra_help = component_extra_help,
copy = copy,
with = with,
create_form = create_form
}