diff options
Diffstat (limited to 'Sluift/core.lua')
| -rw-r--r-- | Sluift/core.lua | 202 |
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 } |
Swift