Commit 0af1c7fe authored by mathieui's avatar mathieui

Docstrings, and small cleanup

parent 837b46d6
......@@ -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
......@@ -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
......
......@@ -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):
......
......@@ -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 = {
......
"""
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):
......
"""
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:
......
......@@ -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()
......@@ -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'
......
......@@ -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,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment