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
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
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)))
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)
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)
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
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
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
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
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)
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
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)
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)
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
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