diff options
-rw-r--r-- | src/connection.py | 3 | ||||
-rw-r--r-- | src/contact.py | 12 | ||||
-rw-r--r-- | src/core.py | 72 | ||||
-rw-r--r-- | src/events.py | 6 | ||||
-rw-r--r-- | src/plugin.py | 14 | ||||
-rw-r--r-- | src/plugin_manager.py | 68 | ||||
-rw-r--r-- | src/roster.py | 51 | ||||
-rw-r--r-- | src/tabs.py | 7 | ||||
-rw-r--r-- | src/xhtml.py | 5 |
9 files changed, 178 insertions, 60 deletions
diff --git a/src/connection.py b/src/connection.py index ce300261..3f61b8cf 100644 --- a/src/connection.py +++ b/src/connection.py @@ -97,5 +97,8 @@ class Connection(sleekxmpp.ClientXMPP): sleekxmpp.ClientXMPP.send_raw(self, data, now, reconnect) class MatchAll(sleekxmpp.xmlstream.matcher.base.MatcherBase): + """ + Callback to retrieve all the stanzas for the XML tab + """ def match(self, xml): return True diff --git a/src/contact.py b/src/contact.py index 69dfb984..8d3b2bdf 100644 --- a/src/contact.py +++ b/src/contact.py @@ -7,13 +7,13 @@ """ Defines the Resource and Contact classes, which are used in -the roster +the roster. """ import logging log = logging.getLogger(__name__) -from sleekxmpp.xmlstream.stanzabase import JID +from sleekxmpp.xmlstream import JID class Resource(object): """ @@ -21,6 +21,9 @@ class Resource(object): It's a precise resource. """ def __init__(self, jid, data): + """ + data: the dict to use as a source + """ self._jid = JID(jid) # Full jid self._data = data @@ -63,11 +66,12 @@ class Contact(object): @property def bare_jid(self): - """The bare_jid or the contact""" + """The bare jid of the contact""" return self.__item.jid @property def name(self): + """The name of the contact or an empty string.""" return self.__item['name'] or '' @property @@ -77,6 +81,7 @@ class Contact(object): @property def pending_in(self): + """We received a subscribe stanza from this contact.""" return self.__item['pending_in'] @pending_in.setter @@ -85,6 +90,7 @@ class Contact(object): @property def pending_out(self): + """We sent a subscribe stanza to this contact.""" return self.__item['pending_out'] @pending_out.setter diff --git a/src/core.py b/src/core.py index 4cf0a7d4..419ba38f 100644 --- a/src/core.py +++ b/src/core.py @@ -43,7 +43,7 @@ import bookmark from plugin_manager import PluginManager from data_forms import DataFormsTab -from config import config, options +from config import config from logger import logger from roster import roster from contact import Contact, Resource @@ -100,22 +100,19 @@ Status = collections.namedtuple('Status', 'show message') class Core(object): """ - User interface using ncurses + “Main” class of poezion """ def __init__(self): # All uncaught exception are given to this callback, instead # of being displayed on the screen and exiting the program. + sys.excepthook = self.on_exception self.connection_time = time.time() self.status = Status(show=None, message='') - sys.excepthook = self.on_exception self.running = True - self.events = events.EventHandler() self.xmpp = singleton.Singleton(connection.Connection) self.xmpp.core = self roster.set_node(self.xmpp.client_roster) - roster.set_mucs(self.xmpp.plugin['xep_0045'].rooms) - roster.set_self_jid(self.xmpp.boundjid.bare) self.paused = False self.remote_fifo = None # a unique buffer used to store global informations @@ -124,13 +121,21 @@ class Core(object): self.information_buffer = TextBuffer() self.information_win_size = config.get('info_win_height', 2, 'var') self.information_win = windows.TextWin(300) - self.xml_buffer = TextBuffer() - self.tab_win = windows.GlobalInfoBar() self.information_buffer.add_window(self.information_win) - self.tabs = [] + + self.tab_win = windows.GlobalInfoBar() + # Number of xml tabs opened, used to avoid useless memory consumption self.xml_tabs = 0 + self.xml_buffer = TextBuffer() + + self.tabs = [] self.previous_tab_nb = 0 + self.own_nick = config.get('default_nick', '') or self.xmpp.boundjid.user + + self.plugin_manager = PluginManager(self) + self.events = events.EventHandler() + # global commands, available from all tabs # a command is tuple of the form: # (the function executing the command. Takes a string as argument, @@ -138,7 +143,6 @@ class Core(object): # a completion function, taking a Input as argument. Can be None) # The completion function should return True if a completion was # made ; False otherwise - self.plugin_manager = PluginManager(self) self.commands = { 'help': (self.command_help, '\_o< KOIN KOIN KOIN', self.completion_help), 'join': (self.command_join, _("Usage: /join [room_name][@server][/nick] [password]\nJoin: Join the specified room. You can specify a nickname after a slash (/). If no nickname is specified, you will use the default_nick in the configuration file. You can omit the room name: you will then join the room you\'re looking at (useful if you were kicked). You can also provide a room_name without specifying a server, the server of the room you're currently in will be used. You can also provide a password to join the room.\nExamples:\n/join room@server.tld\n/join room@server.tld/John\n/join room2\n/join /me_again\n/join\n/join room@server.tld/my_nick password\n/join / password"), self.completion_join), @@ -171,13 +175,16 @@ class Core(object): 'bookmarks': (self.command_bookmarks, _("Usage: /bookmarks\nBookmarks: Show the current bookmarks."), None), 'remove_bookmark': (self.command_remove_bookmark, _("Usage: /remove_bookmark [jid]\nRemove Bookmark: Remove the specified bookmark, or the bookmark on the current tab, if any."), self.completion_remove_bookmark), 'xml_tab': (self.command_xml_tab, _("Usage: /xml_tab\nXML Tab: Open an XML tab."), None), - } + } + # We are invisible if config.get('send_initial_presence', 'true').lower() == 'false': del self.commands['status'] del self.commands['show'] self.key_func = KeyDict() + # Key bindings associated with handlers + # and pseudo-keys used to map actions below. key_func = { "KEY_PPAGE": self.scroll_page_up, "KEY_NPAGE": self.scroll_page_down, @@ -292,6 +299,9 @@ class Core(object): log.debug("Config reloaded.") def autoload_plugins(self): + """ + Load the plugins on startup. + """ plugins = config.get('plugins_autoload', '') for plugin in plugins.split(): self.plugin_manager.load(plugin) @@ -363,18 +373,24 @@ class Core(object): tabs.Tab.height - 1 - self.information_win_size - tabs.Tab.tab_win_height(), 0) def outgoing_stanza(self, stanza): + """ + We are sending a new stanza, write it in the xml buffer if needed. + """ if self.xml_tabs: self.add_message_to_text_buffer(self.xml_buffer, '\x191}<--\x19o %s' % stanza) - if isinstance(self.current_tab(), tabs.XMLTab): - self.current_tab().refresh() - self.doupdate() + if isinstance(self.current_tab(), tabs.XMLTab): + self.current_tab().refresh() + self.doupdate() def incoming_stanza(self, stanza): + """ + We are receiving a new stanza, write it in the xml buffer if needed. + """ if self.xml_tabs: self.add_message_to_text_buffer(self.xml_buffer, '\x192}-->\x19o %s' % stanza) - if isinstance(self.current_tab(), tabs.XMLTab): - self.current_tab().refresh() - self.doupdate() + if isinstance(self.current_tab(), tabs.XMLTab): + self.current_tab().refresh() + self.doupdate() def command_xml_tab(self, arg=''): """/xml_tab""" @@ -611,6 +627,9 @@ class Core(object): return True def on_chatstate_private_conversation(self, message, state): + """ + Chatstate received in a private conversation from a MUC + """ tab = self.get_tab_by_name(message['from'].full, tabs.PrivateTab) if not tab: return @@ -622,6 +641,9 @@ class Core(object): return True def on_chatstate_groupchat_conversation(self, message, state): + """ + Chatstate received in a MUC + """ nick = message['mucnick'] room_from = message.getMucroom() tab = self.get_tab_by_name(room_from, tabs.MucTab) @@ -634,6 +656,9 @@ class Core(object): self.doupdate() def on_attention(self, message): + """ + Attention probe received. + """ jid_from = message['from'] self.information('%s requests your attention!' % jid_from, 'Info') for tab in self.tabs: @@ -1319,7 +1344,7 @@ class Core(object): def open_conversation_window(self, jid, focus=True): """ - open a new conversation tab and focus it if needed + Open a new conversation tab and focus it if needed """ for tab in self.tabs: # if the room exists, focus it and return if isinstance(tab, tabs.ConversationTab): @@ -1335,6 +1360,9 @@ class Core(object): return new_tab def open_private_window(self, room_name, user_nick, focus=True): + """ + Open a Private conversation in a MUC and focus if needed. + """ complete_jid = room_name+'/'+user_nick for tab in self.tabs: # if the room exists, focus it and return if isinstance(tab, tabs.PrivateTab): @@ -1358,7 +1386,7 @@ class Core(object): def on_groupchat_subject(self, message): """ - triggered when the topic is changed + Triggered when the topic is changed. """ nick_from = message['mucnick'] room_from = message.getMucroom() @@ -1503,6 +1531,7 @@ class Core(object): self.information(msg, 'Help') def completion_help(self, the_input): + """Completion for /help.""" commands = list(self.commands.keys()) + list(self.current_tab().commands.keys()) return the_input.auto_completion(commands, ' ', quotify=False) @@ -1760,6 +1789,7 @@ class Core(object): self.refresh_window() def completion_win(self, the_input): + """Completion for /win""" l = [JID(tab.get_name()).user for tab in self.tabs] l.remove('') return the_input.auto_completion(l, ' ', quotify=False) @@ -1878,6 +1908,7 @@ class Core(object): return the_input.auto_completion([jid for jid in roster.jids()], '', quotify=False) def completion_list(self, the_input): + """Completion for /list""" muc_serv_list = [] for tab in self.tabs: # TODO, also from an history if isinstance(tab, tabs.MucTab) and\ @@ -2380,13 +2411,16 @@ class Core(object): self.refresh_window() def remove_timed_event(self, event): + """Remove an existing timed event""" if event and event in self.timed_events: self.timed_events.remove(event) def add_timed_event(self, event): + """Add a new timed event""" self.timed_events.add(event) def check_timed_events(self): + """Check for the execution of timed events""" now = datetime.now() for event in self.timed_events: if event.has_timed_out(now): diff --git a/src/events.py b/src/events.py index e66c5ee5..6ca8296d 100644 --- a/src/events.py +++ b/src/events.py @@ -5,7 +5,9 @@ # it under the terms of the zlib license. See the COPYING file. """ -Defines the EventHandler class +Defines the EventHandler class. +The list of available events is here: +http://poezio.eu/doc/en/plugins.html#_poezio_events """ import logging @@ -16,7 +18,7 @@ class EventHandler(object): A class keeping a list of possible events that are triggered by poezio. You (a plugin for example) can add an event handler associated with an event name, and whenever that event is triggered, - the callback is called + the callback is called. """ def __init__(self): self.events = { diff --git a/src/plugin.py b/src/plugin.py index f35bdab1..bd5c88be 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -1,3 +1,8 @@ +""" +Define the PluginConfig and Plugin classes, plus the SafetyMetaclass. +These are used in the plugin system added in poezio 0.7.5 +(see plugin_manager.py) +""" import os from configparser import RawConfigParser import config @@ -5,6 +10,11 @@ import inspect import traceback class PluginConfig(config.Config): + """ + Plugin configuration object. + They are accessible inside the plugin with self.config + and behave like the core Config object. + """ def __init__(self, filename, module_name): self.file_name = filename self.module_name = module_name @@ -28,6 +38,10 @@ class PluginConfig(config.Config): self.add_section(self.module_name) def options(self, section=None): + """ + Return the options of the section + If no section is given, it defaults to the plugin name. + """ if not section: section = self.module_name if not self.has_section(section): diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 40367052..32cb2c03 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -1,11 +1,19 @@ +""" +Plugin manager module. +Define the PluginManager class, the one that glues all the plugins and +the API together. Defines also a bunch of variables related to the +plugin env. +""" + import imp import os import sys -import tabs import logging -from config import config from gettext import gettext as _ +import tabs +from config import config + log = logging.getLogger(__name__) plugins_dir = config.get('plugins_dir', '') @@ -33,6 +41,11 @@ except OSError: sys.path.append(plugins_dir) class PluginManager(object): + """ + Plugin Manager + Contains all the references to the plugins + And keeps track of everything the plugin has done through the API. + """ def __init__(self, core): self.core = core self.modules = {} # module name -> module object @@ -44,6 +57,9 @@ class PluginManager(object): self.tab_keys = {} #module name → dict of tab types; tab type → list of keybinds (tuples) def load(self, name, notify=True): + """ + Load a plugin. + """ if name in self.plugins: self.unload(name) @@ -107,13 +123,30 @@ class PluginManager(object): log.debug("Could not unload plugin: \n%s", traceback.format_exc()) self.core.information("Could not unload plugin: %s" % e, 'Error') + def add_command(self, module_name, name, handler, help, completion=None): + """ + Add a global command. + """ + if name in self.core.commands: + raise Exception(_("Command '%s' already exists") % (name,)) + + commands = self.commands[module_name] + commands[name] = (handler, help, completion) + self.core.commands[name] = (handler, help, completion) + def del_command(self, module_name, name): + """ + Remove a global command added through add_command. + """ if name in self.commands[module_name]: del self.commands[module_name][name] if name in self.core.commands: del self.core.commands[name] def add_tab_command(self, module_name, tab_type, name, handler, help, completion=None): + """ + Add a command only for a type of Tab. + """ commands = self.tab_commands[module_name] t = tab_type.__name__ if name in tab_type.plugin_commands: @@ -127,6 +160,9 @@ class PluginManager(object): tab.update_commands() def del_tab_command(self, module_name, tab_type, name): + """ + Remove a command added through add_tab_command. + """ commands = self.tab_commands[module_name] t = tab_type.__name__ if not t in commands: @@ -140,6 +176,9 @@ class PluginManager(object): del tab.commands[name] def add_tab_key(self, module_name, tab_type, key, handler): + """ + Associate a key binding to a handler only for a type of Tab. + """ keys = self.tab_keys[module_name] t = tab_type.__name__ if key in tab_type.plugin_keys: @@ -153,6 +192,9 @@ class PluginManager(object): tab.update_keys() def del_tab_key(self, module_name, tab_type, key): + """ + Remove a key binding added through add_tab_key. + """ keys = self.tab_keys[module_name] t = tab_type.__name__ if not t in keys: @@ -166,6 +208,10 @@ class PluginManager(object): del tab.key_func[key] def add_key(self, module_name, key, handler): + """ + Associate a global key binding to a handler, except if it + already exists. + """ if key in self.core.key_func: raise Exception(_("Key '%s' already exists") % (key,)) keys = self.keys[module_name] @@ -173,20 +219,19 @@ class PluginManager(object): self.core.key_func[key] = handler def del_key(self, module_name, key): + """ + Remove a global key binding added by a plugin. + """ if key in self.keys[module_name]: del self.keys[module_name][key] if key in self.core.key_func: del self.core.commands[key] - def add_command(self, module_name, name, handler, help, completion=None): - if name in self.core.commands: - raise Exception(_("Command '%s' already exists") % (name,)) - - commands = self.commands[module_name] - commands[name] = (handler, help, completion) - self.core.commands[name] = (handler, help, completion) - def add_event_handler(self, module_name, event_name, handler, position=0): + """ + Add an event handler. If event_name isn’t in the event list, assume + it is a sleekxmpp event. + """ eh = self.event_handlers[module_name] eh.append((event_name, handler)) if event_name in self.core.events.events: @@ -195,6 +240,9 @@ class PluginManager(object): self.core.xmpp.add_event_handler(event_name, handler) def del_event_handler(self, module_name, event_name, handler): + """ + Remove an event handler if it exists. + """ if event_name in self.core.events.events: self.core.events.del_event_handler(None, handler) else: diff --git a/src/roster.py b/src/roster.py index e1251024..c86b33ac 100644 --- a/src/roster.py +++ b/src/roster.py @@ -19,6 +19,11 @@ from sleekxmpp.xmlstream.stanzabase import JID from sleekxmpp.exceptions import IqError class Roster(object): + """ + The proxy class to get the roster from SleekXMPP. + Adds a blacklist for the MUC domains (or else they would show here), + and caches Contact and RosterGroup objects. + """ # MUC domains to blacklist from the contacts roster blacklist = set() @@ -26,11 +31,8 @@ class Roster(object): def __init__(self): """ node: the RosterSingle from SleekXMPP - mucs: the dict from the SleekXMPP MUC plugin containing the joined mucs """ self.__node = None - self.__mucs = None - self.jid = None self.contact_filter = None # A tuple(function, *args) # function to filter contacts, # on search, for example @@ -41,19 +43,6 @@ class Roster(object): self.groups = {} self.contacts = {} - def set_node(self, value): - self.__node = value - - def set_mucs(self, value): - self.__mucs = value - - def set_self_jid(self, value): - self.jid = value - - def get_groups(self): - """Return a list of the RosterGroups""" - return [group for group in self.groups.values() if group] - def __getitem__(self, key): """Get a Contact from his bare JID""" key = JID(key).bare @@ -95,6 +84,19 @@ class Roster(object): """True if the bare jid is in the roster, false otherwise""" return JID(key).bare in self.jids() + @property + def jid(self): + """Our JID""" + return self.__node.jid + + def set_node(self, value): + """Set the SleekXMPP RosterSingle for our roster""" + self.__node = value + + def get_groups(self): + """Return a list of the RosterGroups""" + return [group for group in self.groups.values() if group] + def get_group(self, name): """Return a group or create it if not present""" if name in self.groups: @@ -126,6 +128,9 @@ class Roster(object): config.set_and_save('folded_roster_groups', folded_groups, 'var') def get_nb_connected_contacts(self): + """ + Get the number of connected contacts + """ n = 0 for contact in self: if contact.resources: @@ -206,7 +211,9 @@ class RosterGroup(object): It can be Friends/Family etc, but also can be Online/Offline or whatever """ - def __init__(self, name, contacts=[], folded=False): + def __init__(self, name, contacts=None, folded=False): + if not contacts: + contacts = [] self.contacts = set(contacts) self.name = name self.folded = folded # if the group content is to be shown @@ -219,6 +226,7 @@ class RosterGroup(object): return '<Roster_group: %s; %s>' % (self.name, self.contacts) def __len__(self): + """Number of contacts in the group""" return len(self.contacts) def __contains__(self, contact): @@ -250,6 +258,7 @@ class RosterGroup(object): return sorted(contact_list, key=compare_contact, reverse=True) def toggle_folded(self): + """Fold/unfold the group in the roster""" self.folded = not self.folded if self.folded: if self.name not in roster.folded_groups: @@ -259,10 +268,8 @@ class RosterGroup(object): roster.folded_groups.remove(self.name) def get_nb_connected_contacts(self): - l = 0 - for contact in self.contacts.copy(): - if contact.resources: - l += 1 - return l + """Return the number of connected contacts""" + return len([1 for contact in self.contacts if contact.resources]) +# Shared roster object roster = Roster() diff --git a/src/tabs.py b/src/tabs.py index 4e1c5141..1b5c81e2 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -763,6 +763,7 @@ class MucTab(ChatTab): self.core.close_tab() def command_cycle(self, arg): + """/cycle [reason]""" if self.joined: muc.leave_groupchat(self.core.xmpp, self.get_name(), self.own_nick, arg) self.disconnect() @@ -897,7 +898,6 @@ class MucTab(ChatTab): color_other = get_theme().COLOR_USER_NONE[0] color_moderator = get_theme().COLOR_USER_MODERATOR[0] color_participant = get_theme().COLOR_USER_PARTICIPANT[0] - color_information = get_theme().COLOR_INFORMATION_TEXT[0] visitors, moderators, participants, others = [], [], [], [] aff = { 'owner': lambda: get_theme().CHAR_AFFILIATION_OWNER, @@ -940,6 +940,7 @@ class MucTab(ChatTab): return the_input.auto_completion([current_topic], '', quotify=False) def completion_quoted(self, the_input): + """Nick completion, but with quotes""" compare_users = lambda x: x.last_talked word_list = [user.nick for user in sorted(self.users, key=compare_users, reverse=True)\ if user.nick != self.own_nick] @@ -1020,6 +1021,10 @@ class MucTab(ChatTab): self.core.information('Could not set affiliation', 'Error') def command_say(self, line): + """ + /say <message> + Or normal input + enter + """ needed = 'inactive' if self.inactive else 'active' msg = self.core.xmpp.make_message(self.get_name()) msg['type'] = 'groupchat' diff --git a/src/xhtml.py b/src/xhtml.py index 38ec690c..4d93a855 100644 --- a/src/xhtml.py +++ b/src/xhtml.py @@ -13,20 +13,19 @@ poezio colors to xhtml code """ import re -import subprocess import curses from sleekxmpp.xmlstream import ET import xml.sax.saxutils -from xml.etree.ElementTree import ElementTree - from sys import version_info from config import config import logging digits = '0123456789' # never trust the modules + +# HTML named colors colors = { 'aliceblue': 231, 'antiquewhite': 231, |