Tools to scan for, find, register, and setup plugins.
from __future__ import with_statement
import traceback
import logging
import config
import util.primitives
import util.data_importer as importers
from contextlib import closing
from util.data_importer import zipopen
from babel.messages.pofile import read_po
from babel.messages.mofile import write_mo
from StringIO import StringIO
import peak.util.plugins as Plugins
from path import path
log = logging.getLogger('plugin_registry')
type_handlers = {}
def register_plugin_default(name, metainfo):
    Default handler for a discovered plugin.
    log.error('Not registering type %r because of unknown "type" key (%r). its metainfo is: %r', name, metainfo.get('type', None), metainfo)
def register_type_handler(typename, handler):
    Sets 'handler' as the callable for plugins with type == typename. 'handler' should be a callable
    that takes two arguments, (str name, mapping metainfo)
    old_handler = type_handlers.get(typename, None)
    if old_handler is not None:
        log.warning('Overwriting handler for type %r: was %r, now %r', typename, old_handler, handler)
    type_handlers[typename] = handler
def get_type_handler(typename):
    Returns the handler for 'typename', or the default handler if none is registerd.
        return type_handlers[typename]
    except KeyError:
        return register_plugin_default
def type_handler(typename):
    Decorator to mark a function as the type handler for 'typename'
    def register_handler(f):
        register_type_handler(typename, f)
        return f
    return register_handler
def register_type(name, metainfo):
    str name:         name of the plugin, ex: 'oscar', 'nowplaying'
    mapping metainfo: information about the plugin, ex: {'type' : 'im', 'shortname' : 'aim'}
    # TODO: move protocolmeta.protocols to this module and get rid of protolmeta?
    # use this as a central hub for all protocol meta info.
    # XXX: what should we do with functions like is_compatible and things like that from
    # protocolmeta?
    a_type = metainfo.get('type', None)
    if a_type is None:
        log.error('Not registering type %r because it did not have a "type" key. its metainfo is: %r', name, metainfo)
        platforms = metainfo.get('platforms', None)
        if platforms is None or config.platformName in platforms:
            PluginType = get_type_handler(a_type)
            # TODO: store these things somewhere?
            plugin = PluginType(metainfo.__file__.parent, metainfo)
            return plugin
class PluginLoader(object):
    def __init__(self, dirpath, metainfo):
        self.path = dirpath
        self.shortname = metainfo.get('shortname', self.path.name)
        self.info = metainfo
    def init(self):
        self.name = self.info.name
        log.info('registered plugin type: %r', self.name)
    def init_skin(self):
        # self.skin contains transformed data with paths made relative to the
        # plugin's directory, while
        # self.info.skin contains the "raw info" from the plugin's metadata.
        # when looking up skin values, you should really use self.skin
        self.skin = self.resolve_paths(self.get_component('skin'))
    def resolve_paths(self, d):
        '''recursively transform any string leafs in a mapping into paths
        relative to this plugin's directory, if those paths exist.'''
        if not isinstance(d, dict):
            return d
        ret = {}
        for k,v in d.items():
            if isinstance(v, basestring):
                pth = self.path / v
                if pth.isfile() or pth.isdir():
                    v = pth
            elif isinstance(v, dict):
                v = self.resolve_paths(v)
            ret[k] = v
        return ret
    def init_actions(self):
        actions = self.get_component('actions')
        if actions is None:
        import common.actions
    def init_notifications(self):
        nots = self.get_component('notifications')
        if nots is None:
        import common.notifications as n
        #import gui.skin.skintransform as gst
        #nots.notifications = map(lambda x: gst.transform(x, False), nots.notifications)
        defaults = {}
        for d in nots:
            for k in d:
                not_ = d[k]
                default = not_.get('default', {})
                default_reactions = [{'reaction' : reaction} for reaction in default.get('reaction', [])]
                defaults[k.replace('_','.')] = default_reactions
        self.set_component('notifications', nots)
    def get_component(self, attr, yamlname = None):
        attempts = [
                    lambda: getattr(self, attr, None),
                    lambda: getattr(self.info, attr, None),
                    lambda: getattr(self.yaml_load(yamlname or attr), '__content__', None),
        for attempt in attempts:
            thing = attempt()
            if thing is not None:
            return None
        self.set_component(attr, thing)
        return thing
    def set_component(self, attr, thing):
        setattr(self, attr, thing)
        setattr(self.info, attr, thing)
    def yaml_load(self, yamlname):
            return importers.yaml_import(yamlname, loadpath = [self.path])
        except ImportError:
            return None
    def load_entry_points(self):
        """Connects plugin entry points defined in info.yaml"""
        import pkg_resources
        from .plugin_resources import PluginDistribution
        import config as digsbyconfig
        platforms = self.get_component('platforms')
        if platforms is None or digsbyconfig.platformName in platforms:
            pkg_resources.working_set.add(PluginDistribution(location = str(self.path.parent),
                                                             project_name = self.info.name,
                                                             ep_yaml = self.get_component('entry_points')))
    def __repr__(self):
        return "<%s %r>" % (type(self).__name__, self.name)
class ProtocolPluginLoader(PluginLoader):
    def init(self, dictnames = None):
    def init_info(self, dictnames = None):
        import common.protocolmeta as pm
        plugin_info = util.primitives.mapping.Storage(self.info)
        if dictnames is not None:
            for dictname in dictnames:
                d = getattr(pm, dictname, None)
                if d is not None:
                    d[self.shortname] = plugin_info
        pm.protocols[self.shortname] = plugin_info
class IMProtocolPluginLoader(ProtocolPluginLoader):
    def init(self):
        return ProtocolPluginLoader.init(self, ['improtocols'])
class EmailProtocolPluginLoader(ProtocolPluginLoader):
    def init(self):
        return ProtocolPluginLoader.init(self, ['emailprotocols'])
class SocialProtocolPluginLoader(ProtocolPluginLoader):
    def init(self):
        result = ProtocolPluginLoader.init(self, ['socialprotocols'])
        return result
class MetaProtocolPluginLoader(ProtocolPluginLoader):
    def init(self):
        return ProtocolPluginLoader.init(self)
class PurePluginLoader(PluginLoader):
    def init(self):
        super(PurePluginLoader, self).init()
class MultiPluginLoader(PurePluginLoader):
    def init(self):
        self.plugins = []
        plugin_dicts = self.info['plugins']
        for name, plugin in plugin_dicts.items():
            plugin_obj = register_type(name, plugin)
            if plugin_obj is not None:
        super(MultiPluginLoader, self).init()
class ServiceProviderPlugin(PurePluginLoader):
    def init(self):
        res = super(ServiceProviderPlugin, self).init()
        self.type = self.info.type
        self.name = self.info.name
        self.provider_id = self.info.provider_id
        return res
class ServiceComponentPlugin(ProtocolPluginLoader):
    def init(self):
        pm_key = {
                  'social' : 'socialprotocols',
                  'email'  : 'emailprotocols',
                  'im'     : 'improtocols',
                  }.get(self.info.component_type, None)
        dictnames = []
        if pm_key is not None:
        result = ProtocolPluginLoader.init(self, dictnames)
        self.service_provider = self.provider_id = self.info.service_provider
        self.component_type = self.info.component_type
        return result
class LangPluginLoader(PurePluginLoader):
    def init(self):
        super(LangPluginLoader, self).init()
    def get_catalog(self):
        if self.info.get('catalog_format') == 'po':
            return self.get_po_catalog()
        return self.get_mo_catalog()
    def file_base(self):
        return self.path / (self.info['domain'] + '-' + self.info['language'])
    def get_mo_catalog(self):
        with closing(zipopen(self.file_base + '.mo')) as f:
            return StringIO(f.read())
    def get_po_catalog(self):
        cat = StringIO()
        with closing(zipopen(self.file_base + '.po')) as f:
            po = read_po(f)
            write_mo(cat, po)
            return cat
pkg_dirs = set()
def scan(dirname):
    Search 'dirname' for .zip, .egg, or directories with an info.yaml.
    for each found, loads the plugin info and calls register_type with
    the discovered name and information.
    root = path(dirname)
    plugins = []
    for pkg_dir in root.dirs() + root.files('*.zip') + root.files('*.egg'):
        name = pkg_dir.namebase
        if exclude_dir(name):
#        sys.path.append(pkg_dir)
            plugin = _load_plugin_info_from_item(pkg_dir)
            if plugin is None:
    #            sys.path.remove(pkg_dir)
                log.info("No protocol info found in %r", pkg_dir)
                ret = register_type(name, plugin)
                if ret is None:
                if hasattr(ret, 'plugins'):
        except Exception:
    return plugins
def exclude_dir(dirname):
    return dirname.startswith('.')
def _load_plugin_info_from_item(pkgdir):
    Tries really hard to load the plugin info from the specified item.
        return importers.yaml_import('info', loadpath = [pkgdir])
    except ImportError:
            return importers.yaml_load(pkgdir / 'info.yaml')
        except Exception:
            return None
def plugins_skintrees():
    'All info.yamls can define a "skin:" section that gets merged with the skin tree.'
    import wx
    plugins = wx.GetApp().plugins
    trees = []
    for plugin in plugins:
        skin = plugin.get_component('skin') or None
        if skin is not None:
    return trees
def plugins_skinpaths():
    'Returns paths images for plugins can be loaded from.'
    return list(pkg_dirs) # todo: limit skin lookups for plugins to their own res/ directory
Plugins.Hook('digsby.skin.load.trees', 'plugins_skin').register(plugins_skintrees)
Plugins.Hook('digsby.skin.load.skinpaths', 'plugins_skin').register(plugins_skinpaths)
if __name__ == '__main__':
    import wx
    a = wx.App()