Exemple #1
0
    def load(self):
        """ Load the plugin information from file. """
        parser = configparser.SafeConfigParser()

        file = profile.get_file(profile.join(self.path, self.file))
        if not file:
            raise IOError("Can't read plugin information file: %s" %
                          profile.join(self.path, self.file))

        parser.readfp(file)
        file.close()

        if parser.has_section('Plugin'):
            # Read core information.
            pairs = dict(parser.items('Plugin'))
            self.name = pairs.get('name', self.name)
            self.module = pairs.get('module', self.module)
            self.version = Version(pairs.get('version', self.version))

        if parser.has_section('Requires'):
            # Read requirements.
            for key, value in parser.items('Requires'):
                name, match = VersionMatch.from_string(value)
                self.requirements[name] = match

        if parser.has_section('Description'):
            for key, value in parser.items('Description'):
                self.description[key] = value
Exemple #2
0
    def load(self):
        """ Load the plugin information from file. """
        parser = ConfigParser.SafeConfigParser()

        file = profile.get_file(profile.join(self.path, self.file))
        if not file:
            raise IOError("Can't read plugin information file: %s" %
                          profile.join(self.path, self.file))

        parser.readfp(file)
        file.close()

        if parser.has_section('Plugin'):
            # Read core information.
            pairs = dict(parser.items('Plugin'))
            self.name = pairs.get('name', self.name)
            self.module = pairs.get('module', self.module)
            self.version = Version(pairs.get('version', self.version))

        if parser.has_section('Requires'):
            # Read requirements.
            for key, value in parser.items('Requires'):
                name, match = VersionMatch.from_string(value)
                self.requirements[name] = match

        if parser.has_section('Description'):
            for key, value in parser.items('Description'):
                self.description[key] = value
Exemple #3
0
    def _walk_for_plugins(self, path):
        """
        Walk through a directory, reading information on every plugin we run
        across.
        """
        ext = '.%s' % self.info_extension

        for root, dirs, files in profile.walk(path):
            log.debug('Searching in: %s' % root)
            for file in files:
                if not file.endswith(ext):
                    continue
                name = file[:-len(ext)]

                # If we already have information on this plugin, skip it.
                if name in self._infos:
                    continue

                # The only acceptable error from the constructor of a
                # PluginInfo class is an IOError.
                try:
                    info = self.info_class(self, name, root, file)
                except IOError:
                    log.exception("Error loading plugin information for: %s" %
                                  file)
                    continue

                # Store the PluginInfo class and log a message.
                self._infos[info.name] = info
                log.debug('Discovered %r at: %s' %
                          (info, profile.join(root, file)))
Exemple #4
0
   def get_path(self, file, use_inheritance=True, secure=True,
                _suppress=False):
       """
       Return a path to the given ``file`` relative to this style. If
       ``secure`` is True, the path will be checked to ensure it resides
       within the root of this style.
 
       If ``use_inheritance`` is True and the file doesn't exist within this
       style, it will be searched for in the style this style inherits.
 
       If the file cannot be found at all, returns ``None``.
       """
       path = profile.normpath(profile.join(self.path, file))
       if secure and not path.startswith(self.path):
           raise ValueError("File path not within style.")
 
       if not profile.exists(path, source=self.path_source):
           if use_inheritance and self.inherits:
               return loaded_styles[self.inherits].get_path(file,
                                                            secure=secure)
           if not _suppress:
               log.warning('Cannot find %r in style: %s' % (file, self.name))
           return None
 
       return profile.get_filename(path, source=self.path_source)
Exemple #5
0
    def _walk_for_plugins(self, path):
        """
        Walk through a directory, reading information on every plugin we run
        across.
        """
        ext = '.%s' % self.info_extension

        for root, dirs, files in profile.walk(path):
            log.debug('Searching in: %s' % root)
            for file in files:
                if not file.endswith(ext):
                    continue
                name = file[:-len(ext)]

                # If we already have information on this plugin, skip it.
                if name in self._infos:
                    continue

                # The only acceptable error from the constructor of a
                # PluginInfo class is an IOError.
                try:
                    info = self.info_class(self, name, root, file)
                except IOError:
                    log.exception("Error loading plugin information for: %s" %
                            file)
                    continue

                # Store the PluginInfo class and log a message.
                self._infos[info.name] = info
                log.debug('Discovered %r at: %s' % (info,
                                                    profile.join(root, file)))
Exemple #6
0
   def __init__(self, name, path=None):
       self.name = name
 
       # Make sure we've got a path.
       if path and os.path.isabs(path):
           if not os.path.exists(path):
               log.error('Cannot find style: %s' % name)
               raise IOError('Cannot find style: %s' % name)
           source = 0
 
       else:
           if not path:
               path = profile.join(u'styles', name)
 
           if not profile.exists(path):
               log.error('Cannot find style: %s' % name)
               raise IOError('Cannot find style: %s' % name)
 
           source = profile.get_source(path)
 
       # Store the path.
       self.path = path
       self.path_source = source
 
       # Now, load the theme.
       self.reload(False)
Exemple #7
0
    def __init__(self, name, path=None):
        self.name = name

        # Make sure we've got a path.
        if path and os.path.isabs(path):
            if not os.path.exists(path):
                log.error('Cannot find style: %s' % name)
                raise IOError('Cannot find style: %s' % name)
            source = 0

        else:
            if not path:
                path = profile.join(u'styles', name)

            if not profile.exists(path):
                log.error('Cannot find style: %s' % name)
                raise IOError('Cannot find style: %s' % name)

            source = profile.get_source(path)

        # Store the path.
        self.path = path
        self.path_source = source

        # Now, load the theme.
        self.reload(False)
Exemple #8
0
    def get_path(self,
                 file,
                 use_inheritance=True,
                 secure=True,
                 _suppress=False):
        """
        Return a path to the given ``file`` relative to this style. If
        ``secure`` is True, the path will be checked to ensure it resides
        within the root of this style.
        
        If ``use_inheritance`` is True and the file doesn't exist within this
        style, it will be searched for in the style this style inherits.
        
        If the file cannot be found at all, returns ``None``.
        """
        path = profile.normpath(profile.join(self.path, file))
        if secure and not path.startswith(self.path):
            raise ValueError("File path not within style.")

        if not profile.exists(path, source=self.path_source):
            if use_inheritance and self.inherits:
                return loaded_styles[self.inherits].get_path(file,
                                                             secure=secure)
            if not _suppress:
                log.warning('Cannot find %r in style: %s' % (file, self.name))
            return None

        return profile.get_filename(path, source=self.path_source)
Exemple #9
0
def list_styles():
    """ List all the available styles. """
    styles = []
    for name in profile.listdir('styles'):
        if profile.isfile(profile.join('styles', name, 'style.ini')):
            styles.append(name)
  
    return styles
Exemple #10
0
def list_styles():
    """ List all the available styles. """
    styles = []
    for name in profile.listdir('styles'):
        if profile.isfile(profile.join('styles', name, 'style.ini')):
            styles.append(name)

    return styles
Exemple #11
0
    def icon(self,
             name,
             extension='png',
             use_inheritance=True,
             allow_theme=True):
        """
        Find an icon with the given ``name`` and return a
        :class:`~PySide.QtGui.QIcon` of that icon. If ``use_inheritance`` is
        True and this style doesn't have an icon with the given name, the
        icon will be searched for in the style this style inherits.
        
        If ``allow_theme`` is True and the icon can't be located in a style, it
        will be retrieved with :func:`PySide.QtGui.QIcon.fromTheme` as a last
        resort as long as the style allows the use of system icons.
        """
        icon = None

        fn = '%s.%s' % (name, extension)
        path = profile.join('images', fn)

        if self.path_source != profile.SOURCE_PKG_RESOURCES:
            file = self.get_path(path, use_inheritance)
            if file and os.path.exists(file):
                icon = QIcon(file)
        else:
            if self.has_file(path, use_inheritance):
                f = self.get_file(path, use_inheritance=use_inheritance)
                if f:
                    pixmap = QPixmap()
                    pixmap.loadFromData(f.read())
                    icon = QIcon(pixmap)
                    del pixmap
                    f.close()

        if not icon and use_inheritance and self.inherits:
            icon = loaded_styles[self.inherits].icon(name, extension,
                                                     use_inheritance,
                                                     allow_theme)

        if not icon and allow_theme:
            if QIcon.hasThemeIcon(name):
                icon = QIcon.fromTheme(name)

        if not icon:
            icon = QIcon()

        return icon
Exemple #12
0
   def has_file(self, file, use_inheritance=True, secure=True,
                _suppress=False):
       """
       Return True if the given file exists in this style, otherwise False. If
       ``use_inheritance`` is True, styles this style inherits from will be
       checked as well.
       """
       path = profile.normpath(profile.join(self.path, file))
       if secure and not path.startswith(self.path):
           raise ValueError("File path not within style.")
 
       if not profile.exists(path, source=self.path_source):
           if use_inheritance and self.inherits:
               return loaded_styles[self.inherits].has_file(file,
                                                            secure=secure)
           return False
 
       return True
Exemple #13
0
   def icon(self, name, extension='png', use_inheritance=True,
            allow_theme=True):
       """
       Find an icon with the given ``name`` and return a
       :class:`~PySide.QtGui.QIcon` of that icon. If ``use_inheritance`` is
       True and this style doesn't have an icon with the given name, the
       icon will be searched for in the style this style inherits.
 
       If ``allow_theme`` is True and the icon can't be located in a style, it
       will be retrieved with :func:`PySide.QtGui.QIcon.fromTheme` as a last
       resort as long as the style allows the use of system icons.
       """
       icon = None
 
       fn = '%s.%s' % (name, extension)
       path = profile.join('images', fn)
 
       if self.path_source != profile.SOURCE_PKG_RESOURCES:
           file = self.get_path(path, use_inheritance)
           if file and os.path.exists(file):
               icon = QIcon(file)
       else:
           if self.has_file(path, use_inheritance):
               f = self.get_file(path, use_inheritance=use_inheritance)
               if f:
                   pixmap = QPixmap()
                   pixmap.loadFromData(f.read())
                   icon = QIcon(pixmap)
                   del pixmap
                   f.close()
 
       if not icon and use_inheritance and self.inherits:
           icon = loaded_styles[self.inherits].icon(name, extension,
                                                 use_inheritance, allow_theme)
 
       if not icon and allow_theme:
           if QIcon.hasThemeIcon(name):
               icon = QIcon.fromTheme(name)
 
       if not icon:
           icon = QIcon()
 
       return icon
Exemple #14
0
    def has_file(self,
                 file,
                 use_inheritance=True,
                 secure=True,
                 _suppress=False):
        """
        Return True if the given file exists in this style, otherwise False. If
        ``use_inheritance`` is True, styles this style inherits from will be
        checked as well.
        """
        path = profile.normpath(profile.join(self.path, file))
        if secure and not path.startswith(self.path):
            raise ValueError("File path not within style.")

        if not profile.exists(path, source=self.path_source):
            if use_inheritance and self.inherits:
                return loaded_styles[self.inherits].has_file(file,
                                                             secure=secure)
            return False

        return True
Exemple #15
0
   def load_qss(self, path):
       """
       Load a Qt style sheet from a source file and do some simple
       pre-processing to allow the use of relative URLs and an @import
       statement. Examples:
 
       .. code-block:: css
 
           QPushButton {
               @import 'button_base.qss';
 
               some-invalid-property: url('relative/url.png');
               background-image: url('/images/button.png');
           }
 
       Assuming that ``button_base.qss`` contains ``"color: red;"``, and this
       style lives in ``/path/to/example`` while the Qt style sheet we're
       processing is at ``/path/to/example/qss/file.qss``, the following would
       result:
 
       .. code-block:: css
 
           QPushButton {
               color: red;
 
               some-invalid-property: url('/path/to/example/qss/relative/url.png');
               background-image: url('/path/to/example/images/button.png');
           }
 
       """
       if not self.has_file(path):
           return ''
 
       # Build the relative path for relative URLs.
       start_path = os.path.dirname(path)
       if start_path.startswith(self.path):
           start_path = start_path[len(self.path)+1:]
 
       start_path = profile.join(start_path, 'test')
 
       # Get the file.
       f = self.get_file(path, 'rU')
       if not f:
           return ''
       qss = f.read()
       f.close()
 
       log.debug('Begin Loading QSS: %s' % path)
       log.debug('---- Before ----')
       log.debug(qss)
 
       # Process #IFAERO .. #ELSE .. #END
       qss = QSS_AERO.sub(self._qss_aero, qss)
 
       # Process the @import statement.
       qss = QSS_IMPORT.sub(lambda m: self._import_qss(start_path, m), qss)
 
       # Process the rest of the urls.
       qss = QSS_URL.sub(lambda m: self._update_url(start_path, m), qss)
 
       log.debug('---- After ----')
       log.debug(qss)
       log.debug('End Loading QSS: %s' % path)
 
       return qss
Exemple #16
0
   def _qss_url(self, path, url, allow_inheritance=True, _suppress=False,
                for_import=False):
       """
       Process a url() and return an absolute URL, or None if the URL isn't
       valid.
       """
       if (url.startswith('"') and url.endswith('"')) or \
               (url.startswith("'") and url.endswith("'")):
           url = url[1:-1]
 
       # Make a QUrl.
       url = QUrl(url.decode('unicode_escape'))
 
       # Is it a data uri?
       if url.scheme() == 'data':
           # Extract the useful information from the path.
           format, sep, data = url.path().partition(',')
           if not sep and not data:
               data = format
               format = ''
 
           mimetype, _, format = format.partition(';')
           if not mimetype:
               ext = 'txt'
           else:
               _, _, ext = mimetype.rpartition('/')
           if not format:
               format = 'charset=US-ASCII'
 
           # Build the filename.
           fn = os.path.join(profile.cache_path, u'data-uris',
                   '%s.%s' % (hashlib.md5(data).hexdigest(), ext))
 
           # Ensure the path exists and write the file.
           try:
               if not os.path.exists(os.path.dirname(fn)):
                   os.makedirs(os.path.dirname(fn))
               with open(fn, 'wb') as f:
                   if format == 'base64':
                       f.write(base64.b64decode(data))
                   elif format.startswith('charset='):
                       data = urllib.unquote(data).encode('latin1')
                       cs = format[8:]
                       if cs and cs.lower() not in ('utf-8','utf8'):
                           data = data.decode(cs).encode('utf-8')
                       f.write(data)
                   else:
                       return
           except (ValueError, OSError, IOError, TypeError):
               log.debug('Error parsing data URI.', exc_info=1)
               return
 
           # Substitute the right / on Windows, and return the path.
           if os.name == 'nt':
               fn = fn.replace('\\', '/')
           return fn
 
       # If it's relative, build an absolute URL. If not, return.
       if not url.isRelative():
           return
 
       url = url.toLocalFile()
       if url.startswith('/'):
           url = url[1:]
       else:
           url = profile.join(path, url)
 
       # If we're dealing with import, return a relative path.
       if for_import:
           return url
 
       return self.get_path(url, allow_inheritance, False, _suppress)
Exemple #17
0
    def load_qss(self, path):
        """
        Load a Qt style sheet from a source file and do some simple
        pre-processing to allow the use of relative URLs and an @import
        statement. Examples:

        .. code-block:: css

            QPushButton {
                @import 'button_base.qss';

                some-invalid-property: url('relative/url.png');
                background-image: url('/images/button.png');
            }

        Assuming that ``button_base.qss`` contains ``"color: red;"``, and this
        style lives in ``/path/to/example`` while the Qt style sheet we're
        processing is at ``/path/to/example/qss/file.qss``, the following would
        result:

        .. code-block:: css

            QPushButton {
                color: red;

                some-invalid-property: url('/path/to/example/qss/relative/url.png');
                background-image: url('/path/to/example/images/button.png');
            }

        """
        if not self.has_file(path):
            return ''

        # Build the relative path for relative URLs.
        start_path = os.path.dirname(path)
        if start_path.startswith(self.path):
            start_path = start_path[len(self.path) + 1:]

        start_path = profile.join(start_path, 'test')

        # Get the file.
        f = self.get_file(path, 'rU')
        if not f:
            return ''
        qss = f.read()
        f.close()

        log.debug('Begin Loading QSS: %s' % path)
        log.debug('---- Before ----')
        log.debug(qss)

        # Process #IFAERO .. #ELSE .. #END
        qss = QSS_AERO.sub(self._qss_aero, qss)

        # Process the @import statement.
        qss = QSS_IMPORT.sub(lambda m: self._import_qss(start_path, m), qss)

        # Process the rest of the urls.
        qss = QSS_URL.sub(lambda m: self._update_url(start_path, m), qss)

        log.debug('---- After ----')
        log.debug(qss)
        log.debug('End Loading QSS: %s' % path)

        return qss
Exemple #18
0
                self._load_plugin(dname, use_blacklist, new_chain)
            except DependencyError, err:
                raise DependencyError("Unsatisfied dependency: %s %s\n%s" %
                                        (dname, match, err))
            except (ImportError, VersionError):
                log.exception("Unable to load plugin: %s" % dinfo.nice_name)
                raise DependencyError("Unsatisfied dependency: %s %s" %
                                        (dname, match))

        # Figure out what we're loading.
        modname = info.module if info.module else info.name

        # Now, try determining whether we're loading a single .py file, or an
        # actual module.
        file = None
        if profile.isfile(profile.join(info.path, modname, '__init__.py')):
            # We're doing a whole folder.
            path = profile.get_filename(profile.join(info.path, modname),
                                        False)
            try:
                file, pathname, description = imp.find_module(modname,
                                                    [os.path.join(path, '..')])
                module = imp.load_module(modname, file, pathname, description)
            finally:
                if file:
                    file.close()

        elif profile.isfile(profile.join(info.path, '%s.py' % modname)):
            # We're doing a single file.
            path = profile.join(info.path, '%s.py' % modname)
            file = profile.get_file(path)
Exemple #19
0
    def _qss_url(self,
                 path,
                 url,
                 allow_inheritance=True,
                 _suppress=False,
                 for_import=False):
        """
        Process a url() and return an absolute URL, or None if the URL isn't
        valid.
        """
        if (url.startswith('"') and url.endswith('"')) or \
                (url.startswith("'") and url.endswith("'")):
            url = url[1:-1]

        # Make a QUrl.
        url = QUrl(url.decode('unicode_escape'))

        # Is it a data uri?
        if url.scheme() == 'data':
            # Extract the useful information from the path.
            format, sep, data = url.path().partition(',')
            if not sep and not data:
                data = format
                format = ''

            mimetype, _, format = format.partition(';')
            if not mimetype:
                ext = 'txt'
            else:
                _, _, ext = mimetype.rpartition('/')
            if not format:
                format = 'charset=US-ASCII'

            # Build the filename.
            fn = os.path.join(profile.cache_path, u'data-uris',
                              '%s.%s' % (hashlib.md5(data).hexdigest(), ext))

            # Ensure the path exists and write the file.
            try:
                if not os.path.exists(os.path.dirname(fn)):
                    os.makedirs(os.path.dirname(fn))
                with open(fn, 'wb') as f:
                    if format == 'base64':
                        f.write(base64.b64decode(data))
                    elif format.startswith('charset='):
                        data = urllib.unquote(data).encode('latin1')
                        cs = format[8:]
                        if cs and cs.lower() not in ('utf-8', 'utf8'):
                            data = data.decode(cs).encode('utf-8')
                        f.write(data)
                    else:
                        return
            except (ValueError, OSError, IOError, TypeError):
                log.debug('Error parsing data URI.', exc_info=1)
                return

            # Substitute the right / on Windows, and return the path.
            if os.name == 'nt':
                fn = fn.replace('\\', '/')
            return fn

        # If it's relative, build an absolute URL. If not, return.
        if not url.isRelative():
            return

        url = url.toLocalFile()
        if url.startswith('/'):
            url = url[1:]
        else:
            url = profile.join(path, url)

        # If we're dealing with import, return a relative path.
        if for_import:
            return url

        return self.get_path(url, allow_inheritance, False, _suppress)
Exemple #20
0
    def _load_plugin(self, name, use_blacklist=True, _chain=tuple()):
        """ Load the plugin with the given name. """
        info = self._infos[name]

        # Walk the dependency tree.
        for dname, match in info.requirements.iteritems():
            # Check the name against the blacklist.
            if use_blacklist and isblacklisted(name):
                raise DependencyError("Dependency %r is blacklisted." % dname)

            # Get the version.
            if dname == "__app__":
                version = app_version()
            else:
                dinfo = self._infos.get(dname)
                version = dinfo.version if dinfo else None

            # Test the version.
            if not match.test(version):
                raise DependencyError("Unsatisfied dependency: %s %s" %
                                      (dname, match))

            # If the dependency is __app__, continue.
            if dname == "__app__":
                continue

            # Check for loops.
            new_chain = list(_chain)
            new_chain.append(info.name)
            if dname in new_chain:
                raise DependencyError("Dependency loop: %s" %
                                      ", ".join(new_chain))

            # Store reverse requirements.
            if not info.name in dinfo.required:
                dinfo.required.append(info.name)

            # Now, load the plugin.
            try:
                self._load_plugin(dname, use_blacklist, new_chain)
            except DependencyError as err:
                raise DependencyError("Unsatisfied dependency: %s %s\n%s" %
                                      (dname, match, err))
            except (ImportError, VersionError):
                log.exception("Unable to load plugin: %s" % dinfo.nice_name)
                raise DependencyError("Unsatisfied dependency: %s %s" %
                                      (dname, match))

        # Figure out what we're loading.
        modname = info.module if info.module else info.name

        # Now, try determining whether we're loading a single .py file, or an
        # actual module.
        file = None
        if profile.isfile(profile.join(info.path, modname, '__init__.py')):
            # We're doing a whole folder.
            path = profile.get_filename(profile.join(info.path, modname),
                                        False)
            try:
                file, pathname, description = imp.find_module(
                    modname, [os.path.join(path, '..')])
                module = imp.load_module(modname, file, pathname, description)
            finally:
                if file:
                    file.close()

        elif profile.isfile(profile.join(info.path, '%s.py' % modname)):
            # We're doing a single file.
            path = profile.join(info.path, '%s.py' % modname)
            file = profile.get_file(path)
            try:
                module = imp.load_module(modname, file, path,
                                         ('.py', 'rb', imp.PY_SOURCE))
            finally:
                if file:
                    file.close()

        else:
            raise ImportError('Unable to find module: %s' % modname)

        # We have our module. Store both it, and any IPlugins it made.
        plugs = []
        for key in dir(module):
            val = getattr(module, key)
            if inspect.isclass(val) and issubclass(val, IPlugin):
                try:
                    plugs.append(val(self, info.name))
                except Exception:
                    log.exception(
                        'Unable to instance plugin class %r for plugin: %s' %
                        (key, info.nice_name))
                    raise ImportError

        # Store it!
        log.info('Loaded plugin: %s' % info.nice_name)
        self._plugins[info.name] = module, plugs
        return module, plugs
Exemple #21
0
    def _load_plugin(self, name, use_blacklist=True, _chain=tuple()):
        """ Load the plugin with the given name. """
        info = self._infos[name]

        # Walk the dependency tree.
        for dname, match in info.requirements.iteritems():
            # Check the name against the blacklist.
            if use_blacklist and isblacklisted(name):
                raise DependencyError("Dependency %r is blacklisted." % dname)

            # Get the version.
            if dname == "__app__":
                version = app_version()
            else:
                dinfo = self._infos.get(dname)
                version = dinfo.version if dinfo else None

            # Test the version.
            if not match.test(version):
                raise DependencyError("Unsatisfied dependency: %s %s" %
                                        (dname, match))

            # If the dependency is __app__, continue.
            if dname == "__app__":
                continue

            # Check for loops.
            new_chain = list(_chain)
            new_chain.append(info.name)
            if dname in new_chain:
                raise DependencyError("Dependency loop: %s" % ", ".join(
                                                                    new_chain))

            # Store reverse requirements.
            if not info.name in dinfo.required:
                dinfo.required.append(info.name)

            # Now, load the plugin.
            try:
                self._load_plugin(dname, use_blacklist, new_chain)
            except DependencyError as err:
                raise DependencyError("Unsatisfied dependency: %s %s\n%s" %
                                        (dname, match, err))
            except (ImportError, VersionError):
                log.exception("Unable to load plugin: %s" % dinfo.nice_name)
                raise DependencyError("Unsatisfied dependency: %s %s" %
                                        (dname, match))

        # Figure out what we're loading.
        modname = info.module if info.module else info.name

        # Now, try determining whether we're loading a single .py file, or an
        # actual module.
        file = None
        if profile.isfile(profile.join(info.path, modname, '__init__.py')):
            # We're doing a whole folder.
            path = profile.get_filename(profile.join(info.path, modname),
                                        False)
            try:
                file, pathname, description = imp.find_module(modname,
                                                    [os.path.join(path, '..')])
                module = imp.load_module(modname, file, pathname, description)
            finally:
                if file:
                    file.close()

        elif profile.isfile(profile.join(info.path, '%s.py' % modname)):
            # We're doing a single file.
            path = profile.join(info.path, '%s.py' % modname)
            file = profile.get_file(path)
            try:
                module = imp.load_module(modname, file, path,
                                         ('.py', 'rb', imp.PY_SOURCE))
            finally:
                if file:
                    file.close()

        else:
            raise ImportError('Unable to find module: %s' % modname)

        # We have our module. Store both it, and any IPlugins it made.
        plugs = []
        for key in dir(module):
            val = getattr(module, key)
            if inspect.isclass(val) and issubclass(val, IPlugin):
                try:
                    plugs.append(val(self, info.name))
                except Exception:
                    log.exception(
                        'Unable to instance plugin class %r for plugin: %s' %
                            (key, info.nice_name))
                    raise ImportError

        # Store it!
        log.info('Loaded plugin: %s' % info.nice_name)
        self._plugins[info.name] = module, plugs
        return module, plugs