# Copyright (C) 2009, Tutorius.org # Copyright (C) 2009, Simon Poirier # # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ This module manages the loading and listing of tutorius addons. Addons are modular actions and events that are package in such a way that they can be autodetected and can integrate with Tutorius components (the editor) without any configuration or explicit dependencies (python import). An action addon is expected to have a metadata dict such as this one: __action__ = { "name" : "HelloWorld", "display_name" : "Hello World!", "icon" : "hello", "class" : HelloAction, "mandatory_props" : ["text"], } """ import os import sys import re import logging PREFIX = __name__+"s" PATH = re.sub("addon\\.py[c]$", "", __file__)+"addons" TYPE_ACTION = 'action' TYPE_EVENT = 'event' STAT_MODIFY = 8 # whether to auto reload modules on modifications AUTO_RELOAD = True __cache__ = {} def _reload_addons(): """ Scan for all add-ons under the tutorius 'addons' folder and (re)cache all modules found. """ __cache__.clear() for addon in (x for x in os.listdir(PATH) if x.endswith("py")): mod_name = PREFIX+'.'+re.sub("\\.py$", "", addon) _reload_module(mod_name) def _reload_module(mod_name): """ Reload a specific module and update the cache. If the module changed the module will be reloaded and future calls to create() will use the new module. Existing instances won't be changed. @param mod_name: the python name of the module. Ex: 'sugar.tutorius.addons.foo' """ # ensure module reloaded, by removing sys reference sys.modules.pop(mod_name, False) mod = __import__(name=mod_name, fromlist=[""]) if hasattr(mod, "__action__"): __cache__[mod.__action__['name']] = mod.__action__ mod.__action__['type'] = TYPE_ACTION mod.__action__['ts'] = os.stat(mod.__file__)[STAT_MODIFY] elif hasattr(mod, "__event__"): __cache__[mod.__event__['name']] = mod.__event__ mod.__event__['type'] = TYPE_EVENT mod.__event__['ts'] = os.stat(mod.__file__)[STAT_MODIFY] def create(name, *args, **kwargs): """ Create an instance of an addon from its name. If it cannot be instanciated for whatever reason, a message will be logged to the console. Note: properties can usually be initialized kwargs having the same name. However, args and kwargs are always optionals. @param name: the short name of the addon, as found in the 'name' key of the __action__ or __event__ dict. @returns: an instance of the add-on or None. """ if (not __cache__) or (name not in __cache__): _reload_addons() try: metadata = __cache__[name] module_name = metadata['class'].__module__ module_file = sys.modules[module_name].__file__ if AUTO_RELOAD and metadata['ts'] != os.stat(module_file)[STAT_MODIFY]: _reload_module(module_name) metadata = __cache__[name] try: return metadata['class'](*args, **kwargs) except TypeError: logging.error("Could not instantiate %s with parameters %s, %s" \ %(metadata['name'],str(args), str(kwargs))) return None except KeyError: logging.error("Addon not found for class '%s'", name) return None def list_addons(): """ Returns a list of all loaded add-ons, filling the cache if necesary. @returns: a list of addon names. """ if not __cache__: _reload_addons() return __cache__.keys() def get_addon_meta(name): """ Returns an add-on's meta data from its name. @param name: the short name of the add-on, as found in the 'name' metadata. @returns: a reference to the dictionary of meta data. """ if not __cache__ or (name not in __cache__): _reload_addons() return __cache__[name] def get_name_from_type(typ): """ Lookup an addon's name from the type of an object returned by create(). Note: After refreshing the cache, this function should still return the correct name of old instances but calls using that name will refer to the new code. @param typ: the type (class) of an add-on. @returns: the short name of the add-on, as used in this module's functions. """ mod = sys.modules[typ.__module__] meta = getattr(mod, '__action__', None) or getattr(mod, '__event__') return meta['name'] # vim:set ts=4 sts=4 sw=4 et: