# -*- coding: utf-8 -*- # Copyright (C) 2003-2006 by Igor E. Novikov # Copyright (C) 1998, 1999, 2000, 2001, 2002 by Bernhard Herzog # # This library is covered by GNU Library General Public License. # For more info see COPYRIGHTS file in sK1 root directory. import sys, os import re, imp from string import join, split from app.events.skexceptions import SketchError from app.events.warn import warn_tb, warn, USER, INTERNAL, pdebug from app import config from app import _, dgettext, gettext, bindtextdomain, message_dir, Subscribe, config, const # All plugins are loaded as modules in the _plugin_package_name package # to keep the namespaces clean _plugin_package_name = "app.Plugins" def create_packages(package): """ Return the package's module and create it and it's parents if necessary """ names = split(package, '.') for i in range(1, len(names) + 1): name = join(names[:i], '.') if not sys.modules.has_key(name): module = imp.new_module(name) module.__path__ = [] sys.modules[name] = module return sys.modules[name] class ConfigInfo: module = None package = _plugin_package_name def __init__(self, module_name, dir, version = '1.0.0', unload = None, load_immediately = 0, standard_messages = 0): self.module_name = module_name self.dir = dir self.unload = unload self.load_immediately = load_immediately self.version = version self.plugin_list.append(self) self.standard_messages = standard_messages if not self.standard_messages: bindtextdomain(self.module_name, message_dir) if self.load_immediately: self.load_module() def load_module(self): if self.module is not None: return self.module try: file, filename, desc = imp.find_module(self.module_name, [self.dir]) except: warn_tb(INTERNAL, 'Cannot find plugin module %s', self.module_name) return None try: try: create_packages(self.package) module_name = self.package + '.' + self.module_name self.module = imp.load_module(module_name, file, filename, desc) except: warn_tb(USER, _("Cannot load plugin module %s"), self.module_name) raise finally: if file is not None: file.close() self.module._ = self.nls_function() return self.module def UnloadPlugin(self): if self.unload: module_name = self.package + '.' + self.module_name try: module = sys.modules[module_name] except KeyError: # might happen if the module wasn't even loaded return self.module = None if type(self.unload) == type(""): getattr(module, self.unload)(module) else: name = module.__name__ module.__dict__.clear() del sys.modules[name] def nls_function(self): if self.standard_messages: _ = gettext else: domain = self.module_name def _(text, domain = domain): #print domain, text result = dgettext(domain, text) #print '->', result return result return _ def gettext(self, text): if self.standard_messages: return gettext(text) else: return dgettext(self.module_name, text) NativeFormat = 'sK1' import_plugins = [] class ImportInfo(ConfigInfo): plugin_list = import_plugins def __init__(self, module_name, dir, rx_magic, class_name, format_name, tk_file_type = (), version = '1.0.0', unload = None, load_immediately = 0, standard_messages = 0): ConfigInfo.__init__(self, module_name, dir, version = version, unload = unload, load_immediately = load_immediately, standard_messages = standard_messages) self.rx_magic = re.compile(rx_magic) self.class_name = class_name self.format_name = format_name self.tk_file_type = tk_file_type self.translate() def translate(self): name, ext = self.tk_file_type name = self.gettext(name) self.tk_file_type = name, ext def __call__(self, *args, **kw): try: module = self.load_module() if module is not None: return apply(getattr(module, self.class_name), args, kw) except: warn_tb(INTERNAL, 'When importing plugin %s', self.module_name) raise SketchError('Cannot load filter %(name)s.%(message)s' % {'name':self.module_name, 'message':self.class_name}) def UnloadPlugin(self): if config.preferences.unload_import_filters: ConfigInfo.UnloadPlugin(self) export_plugins = [] export_formats = {} class ExportInfo(ConfigInfo): plugin_list = export_plugins def __init__(self, module_name, dir, format_name, tk_file_type = (), extensions = (), version = '1.0.0', unload = None, load_immediately = 0, standard_messages = 0): ConfigInfo.__init__(self, module_name, dir, version = version, unload = unload, load_immediately = load_immediately, standard_messages = standard_messages) self.format_name = format_name self.tk_file_type = tk_file_type if type(extensions) != type(()): extensions = (extensions,) self.extensions = extensions export_formats[format_name] = self def translate(self): name, ext = self.tk_file_type name = self.gettext(name) self.tk_file_type = name, ext def __call__(self, document, filename, file = None, options = None): if options is None: options = {} try: module = self.load_module() except: warn_tb(INTERNAL, 'When importing plugin %s', self.module_name) raise SketchError(_("Cannot load filter %(name)s") % {'name':self.module_name}) if file is None: file = open(filename, 'w') close = 1 else: close = 0 if module is not None: module.save(document, file, filename, options) if close: file.close() if self.format_name == NativeFormat: document.ClearEdited() self.UnloadPlugin() def UnloadPlugin(self): if config.preferences.unload_import_filters: ConfigInfo.UnloadPlugin(self) def find_export_plugin(name): return export_formats.get(name) def guess_export_plugin(extension): for plugin in export_plugins: if extension in plugin.extensions: return plugin.format_name return '' object_plugins = {} compound_plugins = [] class PluginCompoundInfo(ConfigInfo): plugin_list = compound_plugins def __init__(self, module_name, dir, class_name, menu_text, factory = '', version = '1.0.0', parameters = (), uses_selection = 0, custom_dialog = '', load_immediately = 0, standard_messages = 0): ConfigInfo.__init__(self, module_name, dir, version = version, load_immediately = load_immediately, standard_messages = standard_messages) self.class_name = class_name self.factory = factory self.menu_text = menu_text self.parameters = parameters self.uses_selection = uses_selection self.custom_dialog = custom_dialog object_plugins[class_name] = self self.translate() def translate(self): gettext = self.gettext self.menu_text = gettext(self.menu_text) parameters = [] for parameter in self.parameters: parameters.append(parameter[:-1] + (gettext(parameter[-1]),)) self.parameters = parameters def load_module_attr(self, attr): try: module = self.load_module() if module is not None: return getattr(module, attr) except: warn_tb(INTERNAL, 'When importing plugin %s', self.module_name) raise SketchError(_("Cannot load plugin %(name)s.%(attr)s") % {'name':self.module_name, 'attr':attr}) def Constructor(self): return self.load_module_attr(self.class_name) def CallFactory(self, *args, **kw): if self.factory: attr = self.factory else: attr = self.class_name return apply(self.load_module_attr(attr), args, kw) __call__ = CallFactory def HasParameters(self): return len(self.parameters) def UsesSelection(self): return self.uses_selection def HasCustomDialog(self): return self.custom_dialog def CreateCustomDialog(self, root, mainwindow, document): dialog = self.load_module_attr(self.custom_dialog) return dialog(root, mainwindow, document) def find_object_plugin(name): return object_plugins.get(name) config_types = {'Import': ImportInfo, 'Export': ExportInfo, 'PluginCompound': PluginCompoundInfo } def extract_cfg(file): rx_start = re.compile('^###Sketch[ \t]+Config') rx_end = re.compile('^###End') file = open(file, 'r') cfg = [] for line in file.readlines(): if rx_end.match(line): break elif cfg or rx_start.match(line): if line[0] == '#': line = line[1:] if line[-2] == '\\': line = line[:-2] cfg.append(line) return join(cfg, '') def _search_dir(dir, recurse, package = 'app.Plugins'): try: files = os.listdir(dir) except os.error, value: warn(USER, _("Cannot list directory %(filename)s\n%(message)s"), filename = dir, message = value[1]) return for file in files: filename = os.path.join(dir, file) if os.path.isdir(filename): if file == "Lib": # A Lib directory on the plugin path. It's assumed to # hold library files for some plugin. Append it to the # current package's .Lib package's __path__ so that it's # modules can be imported by prefixing their names with # Lib. lib_pkg = create_packages(package + '.Lib') lib_pkg.__path__.append(filename) elif recurse: # an ordinary directory and we should recurse into it to # find more modules, so do that. _search_dir(filename, recurse - 1, package + '.' + file) elif filename[-3:] == '.py': try: module_name = os.path.splitext(os.path.basename(filename))[0] vars = {'_':_} # hack cfg = extract_cfg(filename) exec cfg in config_types, vars infoclass = vars.get('type') if infoclass is None: warn(USER, _("No plugin-type information in %(filename)s"), filename = filename) else: del vars['type'] del vars['_'] info = apply(infoclass, (module_name, dir), vars) info.package = package except: warn_tb(INTERNAL, 'In config file %s', filename) warn(USER, _("can't read configuration information from " "%(filename)s"), filename = filename) def load_plugin_configuration(): #path): if __debug__: import time start = time.clock() path = config.plugin_path for dir in path: # XXX unix specific if len(dir) >= 2 and dir[-1] == '/': if dir[-2] == '/': recurse = -1 else: recurse = 1 else: recurse = 0 _search_dir(dir, recurse) if __debug__: pdebug('timing', 'time to scan cfg files: %g', time.clock()-start) # rearrange import plugins to ensure that native format is first for loader in import_plugins: if loader.format_name == NativeFormat: import_plugins.remove(loader) import_plugins.insert(0, loader) Subscribe(const.INITIALIZE, load_plugin_configuration)