summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/connection.py3
-rw-r--r--src/contact.py12
-rw-r--r--src/core.py72
-rw-r--r--src/events.py6
-rw-r--r--src/plugin.py14
-rw-r--r--src/plugin_manager.py68
-rw-r--r--src/roster.py51
-rw-r--r--src/tabs.py7
-rw-r--r--src/xhtml.py5
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,