Added docstrings in reST format (also with todos). Commented out 'print' statements related to roster window. A few modifications to make code prettier (PyLint driven).
This commit is contained in:
parent
95b1e45920
commit
f62698e28c
|
@ -8,12 +8,12 @@ verbosity: 3
|
|||
imports: yes
|
||||
redundant-details: yes
|
||||
docformat: restructuredtext
|
||||
# top: gajim
|
||||
top: plugins
|
||||
|
||||
# The list of modules to document. Modules can be named using
|
||||
# dotted names, module filenames, or package directory names.
|
||||
# This option may be repeated.
|
||||
modules: src/plugins/*.py
|
||||
modules: src/plugins/*.py test/*.py
|
||||
|
||||
# Write html output to the directory "apidocs"
|
||||
#output: pdf
|
||||
|
|
|
@ -24,16 +24,64 @@ Base class for implementing plugin.
|
|||
:license: GPL
|
||||
'''
|
||||
|
||||
import sys
|
||||
from plugins.helpers import log, log_calls
|
||||
from plugins.helpers import log_calls
|
||||
|
||||
class GajimPlugin(object):
|
||||
'''
|
||||
Base class for implementing Gajim plugins.
|
||||
'''
|
||||
name = ''
|
||||
'''
|
||||
Name of plugin.
|
||||
|
||||
Will be shown in plugins management GUI.
|
||||
|
||||
:type: unicode
|
||||
'''
|
||||
short_name = ''
|
||||
'''
|
||||
Short name of plugin.
|
||||
|
||||
Used for quick indentification of plugin.
|
||||
|
||||
:type: unicode
|
||||
|
||||
:todo: decide whether we really need this one, because class name (with
|
||||
module name) can act as such short name
|
||||
'''
|
||||
version = ''
|
||||
'''
|
||||
Version of plugin.
|
||||
|
||||
:type: unicode
|
||||
|
||||
:todo: decide how to compare version between each other (which one
|
||||
is higher). Also rethink: do we really need to compare versions
|
||||
of plugins between each other? This would be only useful if we detect
|
||||
same plugin class but with different version and we want only the newest
|
||||
one to be active - is such policy good?
|
||||
'''
|
||||
description = ''
|
||||
'''
|
||||
Plugin description.
|
||||
|
||||
:type: unicode
|
||||
|
||||
:todo: should be allow rich text here (like HTML or reStructuredText)?
|
||||
'''
|
||||
authors = []
|
||||
'''
|
||||
Plugin authors.
|
||||
|
||||
:type: [] of unicode
|
||||
|
||||
:todo: should we decide on any particular format of author strings?
|
||||
Especially: should we force format of giving author's e-mail?
|
||||
'''
|
||||
gui_extension_points = {}
|
||||
'''
|
||||
Extension points that plugin wants to connect with.
|
||||
'''
|
||||
|
||||
@log_calls('GajimPlugin')
|
||||
def __init__(self):
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
##
|
||||
|
||||
'''
|
||||
Helper code related to plug-ins management system.
|
||||
Plug-in management related classes.
|
||||
|
||||
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||
:since: 05/30/2008
|
||||
|
@ -32,29 +32,91 @@ import fnmatch
|
|||
|
||||
import common.gajim as gajim
|
||||
|
||||
from helpers import log, log_calls, Singleton
|
||||
from plugin import GajimPlugin
|
||||
from plugins.helpers import log, log_calls, Singleton
|
||||
from plugins.plugin import GajimPlugin
|
||||
|
||||
class PluginManager(object):
|
||||
'''
|
||||
Main plug-in management class.
|
||||
|
||||
Currently:
|
||||
- scans for plugins
|
||||
- activates them
|
||||
- handles GUI extension points, when called by GUI objects after plugin
|
||||
is activated (by dispatching info about call to handlers in plugins)
|
||||
|
||||
:todo: add more info about how GUI extension points work
|
||||
:todo: add list of available GUI extension points
|
||||
:todo: implement mechanism to dynamically load plugins where GUI extension
|
||||
points have been already called (i.e. when plugin is activated
|
||||
after GUI object creation)
|
||||
:todo: implement mechanism to dynamically deactive plugins (call plugin's
|
||||
deactivation handler)
|
||||
|
||||
'''
|
||||
|
||||
__metaclass__ = Singleton
|
||||
|
||||
@log_calls('PluginManager')
|
||||
def __init__(self):
|
||||
self.plugins = []
|
||||
self.active = []
|
||||
'''
|
||||
Detected plugin classes.
|
||||
|
||||
Each class object in list is `GajimPlugin` subclass.
|
||||
|
||||
:type: [] of class objects
|
||||
'''
|
||||
self.active_plugins = []
|
||||
'''
|
||||
Objectsof active plugins.
|
||||
|
||||
These are object instances of classes held `plugins`, but only those
|
||||
that were activated.
|
||||
|
||||
:type: [] of `GajimPlugin` based objects
|
||||
'''
|
||||
self.gui_extension_points = {}
|
||||
'''
|
||||
Registered GUI extension points.
|
||||
'''
|
||||
|
||||
for path in gajim.PLUGINS_DIRS:
|
||||
self.plugins.extend(self._scan_dir_for_plugins(path))
|
||||
self.plugins.extend(PluginManager.scan_dir_for_plugins(path))
|
||||
|
||||
log.debug('plugins: %s'%(self.plugins))
|
||||
|
||||
|
||||
self._activate_all_plugins()
|
||||
|
||||
log.debug('active: %s'%(self.active))
|
||||
|
||||
|
||||
log.debug('active: %s'%(self.active_plugins))
|
||||
|
||||
@log_calls('PluginManager')
|
||||
def gui_extension_point(self, gui_extpoint_name, *args):
|
||||
'''
|
||||
Invokes all handlers (from plugins) for particular GUI extension point.
|
||||
|
||||
:param gui_extpoint_name: name of GUI extension point.
|
||||
:type gui_extpoint_name: unicode
|
||||
:param args: parameters to be passed to extension point handlers
|
||||
(typically and object that invokes `gui_extension_point`; however,
|
||||
this can be practically anything)
|
||||
:type args: tuple
|
||||
|
||||
:todo: GUI extension points must be documented well - names with
|
||||
parameters that will be passed to handlers (in plugins). Such
|
||||
documentation must be obeyed both in core and in plugins. This
|
||||
is a loosely coupled approach and is pretty natural in Python.
|
||||
|
||||
:bug: what if only some handlers are successfully connected? we should
|
||||
revert all those connections that where successfully made. Maybe
|
||||
call 'self._deactivate_plugin()' or sth similar.
|
||||
Looking closer - we only rewrite tuples here. Real check should be
|
||||
made in method that invokes gui_extpoints handlers.
|
||||
|
||||
'''
|
||||
|
||||
log.debug(type(args))
|
||||
|
||||
if gui_extpoint_name in self.gui_extension_points:
|
||||
for handlers in self.gui_extension_points[gui_extpoint_name]:
|
||||
handlers[0](*args)
|
||||
|
@ -62,36 +124,53 @@ class PluginManager(object):
|
|||
@log_calls('PluginManager')
|
||||
def _activate_plugin(self, plugin):
|
||||
'''
|
||||
:param plugin: Plugin to be activated.
|
||||
:type plugin: class object of GajimPlugin subclass
|
||||
:param plugin: plugin to be activated
|
||||
:type plugin: class object of `GajimPlugin` subclass
|
||||
'''
|
||||
p = plugin()
|
||||
|
||||
plugin_object = plugin()
|
||||
|
||||
success = True
|
||||
|
||||
# :fix: what if only some handlers are successfully connected? we should
|
||||
# revert all those connections that where successfully made. Maybe
|
||||
# call 'self._deactivate_plugin()' or sth similar.
|
||||
# Looking closer - we only rewrite tuples here. Real check should be
|
||||
# made in method that invokes gui_extpoints handlers.
|
||||
|
||||
for gui_extpoint_name, gui_extpoint_handlers in \
|
||||
p.gui_extension_points.iteritems():
|
||||
self.gui_extension_points.setdefault(gui_extpoint_name,[]).append(
|
||||
plugin_object.gui_extension_points.iteritems():
|
||||
self.gui_extension_points.setdefault(gui_extpoint_name, []).append(
|
||||
gui_extpoint_handlers)
|
||||
|
||||
|
||||
if success:
|
||||
self.active.append(p)
|
||||
|
||||
self.active_plugins.append(plugin_object)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
@log_calls('PluginManager')
|
||||
def _activate_all_plugins(self):
|
||||
self.active = []
|
||||
'''
|
||||
Activates all plugins in `plugins`.
|
||||
|
||||
Activated plugins are appended to `active_plugins` list.
|
||||
'''
|
||||
self.active_plugins = []
|
||||
for plugin in self.plugins:
|
||||
self._activate_plugin(plugin)
|
||||
|
||||
@staticmethod
|
||||
@log_calls('PluginManager')
|
||||
def _scan_dir_for_plugins(self, path):
|
||||
def scan_dir_for_plugins(path):
|
||||
'''
|
||||
Scans given directory for plugin classes.
|
||||
|
||||
:param path: directory to scan for plugins
|
||||
:type path: unicode
|
||||
|
||||
:return: list of found plugin classes (subclasses of `GajimPlugin`
|
||||
:rtype: [] of class objects
|
||||
|
||||
:note: currently it only searches for plugin classes in '\*.py' files
|
||||
present in given direcotory `path` (no recursion here)
|
||||
|
||||
:todo: add scanning packages
|
||||
:todo: add scanning zipped modules
|
||||
'''
|
||||
plugins_found = []
|
||||
if os.path.isdir(path):
|
||||
dir_list = os.listdir(path)
|
||||
|
@ -100,30 +179,28 @@ class PluginManager(object):
|
|||
sys.path.insert(0, path)
|
||||
log.debug(sys.path)
|
||||
|
||||
for file in fnmatch.filter(dir_list, '*.py'):
|
||||
log.debug('- "%s"'%(file))
|
||||
file_path = os.path.join(path, file)
|
||||
for file_name in fnmatch.filter(dir_list, '*.py'):
|
||||
log.debug('- "%s"'%(file_name))
|
||||
file_path = os.path.join(path, file_name)
|
||||
log.debug(' "%s"'%(file_path))
|
||||
if os.path.isfile(file_path):
|
||||
module_name = os.path.splitext(file)[0]
|
||||
module_name = os.path.splitext(file_name)[0]
|
||||
module = __import__(module_name)
|
||||
filter_out_bad_names = \
|
||||
lambda x: not (x.startswith('__') or
|
||||
x.endswith('__'))
|
||||
for module_attr_name in filter(filter_out_bad_names,
|
||||
dir(module)):
|
||||
for module_attr_name in [f_name for f_name in dir(module)
|
||||
if not (f_name.startswith('__') or
|
||||
f_name.endswith('__'))]:
|
||||
module_attr = getattr(module, module_attr_name)
|
||||
log.debug('%s : %s'%(module_attr_name, module_attr))
|
||||
|
||||
|
||||
try:
|
||||
if issubclass(module_attr, GajimPlugin) and \
|
||||
not module_attr is GajimPlugin:
|
||||
not module_attr is GajimPlugin:
|
||||
log.debug('is subclass of GajimPlugin')
|
||||
plugins_found.append(module_attr)
|
||||
except TypeError, e:
|
||||
except TypeError, type_error:
|
||||
log.debug('module_attr: %s, error : %s'%(
|
||||
module_name+'.'+module_attr_name,
|
||||
e))
|
||||
module_name+'.'+module_attr_name,
|
||||
type_error))
|
||||
|
||||
log.debug(module)
|
||||
|
||||
|
|
|
@ -310,13 +310,13 @@ class RosterWindow:
|
|||
if jids:
|
||||
c4 = time.clock()
|
||||
|
||||
print ""
|
||||
print "--- Add account contacts of %s ---------" % account
|
||||
print "Total Time", c4-c1
|
||||
print "Add contact without draw", c6-c5
|
||||
print "Draw groups and account", c10-c9
|
||||
print "--- contacts added -----------------------------"
|
||||
print ""
|
||||
#print ""
|
||||
#print "--- Add account contacts of %s ---------" % account
|
||||
#print "Total Time", c4-c1
|
||||
#print "Add contact without draw", c6-c5
|
||||
#print "Draw groups and account", c10-c9
|
||||
#print "--- contacts added -----------------------------"
|
||||
#print ""
|
||||
|
||||
|
||||
def _add_entity(self, contact, account, groups = None,
|
||||
|
@ -1216,9 +1216,9 @@ class RosterWindow:
|
|||
self.draw_contact(jid, account)
|
||||
self.draw_avatar(jid, account)
|
||||
yield True
|
||||
print "--- Idle draw of %s -----------" % account
|
||||
print "Draw contact and avatar", time.clock() - t
|
||||
print "-------------------------------"
|
||||
#print "--- Idle draw of %s -----------" % account
|
||||
#print "Draw contact and avatar", time.clock() - t
|
||||
#print "-------------------------------"
|
||||
yield False
|
||||
|
||||
t = time.clock()
|
||||
|
|
|
@ -32,30 +32,62 @@ import unittest
|
|||
gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
||||
sys.path.append(gajim_root + '/src')
|
||||
|
||||
# a temporary version of ~/.gajim for testing
|
||||
configdir = gajim_root + '/test/tmp'
|
||||
|
||||
import time
|
||||
|
||||
# define _ for i18n
|
||||
import __builtin__
|
||||
__builtin__._ = lambda x: x
|
||||
|
||||
# wipe config directory
|
||||
import os
|
||||
if os.path.isdir(configdir):
|
||||
import shutil
|
||||
shutil.rmtree(configdir)
|
||||
|
||||
os.mkdir(configdir)
|
||||
|
||||
import common.configpaths
|
||||
common.configpaths.gajimpaths.init(configdir)
|
||||
common.configpaths.gajimpaths.init_profile()
|
||||
|
||||
# for some reason common.gajim needs to be imported before xmpppy?
|
||||
from common import gajim
|
||||
from common import xmpp
|
||||
|
||||
gajim.DATA_DIR = gajim_root + '/data'
|
||||
|
||||
from common.stanza_session import StanzaSession
|
||||
|
||||
# name to use for the test account
|
||||
account_name = 'test'
|
||||
|
||||
from plugins import PluginManager
|
||||
|
||||
class PluginManagerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.pluginmanager = PluginManager()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_01_Singleton(self):
|
||||
""" 1. Checking whether PluginManger class is singleton. """
|
||||
self.pluginmanager.test_arg = 1
|
||||
secondPluginManager = PluginManager()
|
||||
|
||||
self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager),
|
||||
'Different IDs in references to PluginManager objects (not a singleton)')
|
||||
self.failUnlessEqual(secondPluginManager.test_arg, 1,
|
||||
'References point to different PluginManager objects (not a singleton')
|
||||
|
||||
def setUp(self):
|
||||
self.pluginmanager = PluginManager()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_01_Singleton(self):
|
||||
""" 1. Checking whether PluginManger class is singleton. """
|
||||
self.pluginmanager.test_arg = 1
|
||||
secondPluginManager = PluginManager()
|
||||
|
||||
self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager),
|
||||
'Different IDs in references to PluginManager objects (not a singleton)')
|
||||
self.failUnlessEqual(secondPluginManager.test_arg, 1,
|
||||
'References point to different PluginManager objects (not a singleton')
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase)
|
||||
return suite
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase)
|
||||
return suite
|
||||
|
||||
if __name__=='__main__':
|
||||
runner = unittest.TextTestRunner()
|
||||
test_suite = suite()
|
||||
runner.run(test_suite)
|
||||
runner = unittest.TextTestRunner()
|
||||
test_suite = suite()
|
||||
runner.run(test_suite)
|
Loading…
Reference in New Issue