# Copyright: 2012 MoinMoin:MiksKalnins <mikskalnins@maikumori.com># License: GNU GPL v2 (or any later version), see LICENSE.txt for details."""MoinMoin plugin system."""from__future__importabsolute_import,divisionimportosimportsysfromabcimportABCMeta,abstractproperty,abstractmethodfrominspectimportgetmembers,isclassfromjinja2.loadersimportFileSystemLoaderfromMoinMoinimportloglogging=log.getLogger(__name__)classPluginBase(object):""" This is the very basic plugin interface. All plugins must implement it. """__metaclass__=ABCMeta@abstractpropertydefname(self):""" Name of the plugin. """pass@abstractpropertydefauthor(self):""" Author name of the plugin. """pass@abstractpropertydefdescription(self):""" Short description of the plugin. """pass@propertydefidentifier(self):returnself.__class__.__name__defmake_path(self,*p):""" Make a path relative to the plugin directory. """# Little trick to work around __file__ pointing to file where the ABC declaration is located at.path=os.path.dirname(os.path.abspath(sys.modules[self.__class__.__module__].__file__))returnos.path.join(path,*p)def__init__(self,cfg):passclassThemeBase(PluginBase):""" Theme interface. Influenced by Flask Themes (by Matthew "LeafStorm" Frazier) All inbuilt themes use this interface so you can use them as example to create a new theme. The theme packages are: * :keyword:`MoinMoin.themes.modernized` * :keyword:`MoinMoin.themes.foobar` """@propertydefstatic_path(self):""" The absolute path to the theme's static files directory. """returnself.make_path('static')@propertydeftemplates_path(self):""" The absolute path to the theme's templates directory. """returnself.make_path('templates')@propertydefjinja_loader(self):""" This is a Jinja2 template loader that loads templates from the theme's ``templates`` directory. """returnFileSystemLoader(self.templates_path)classStoreBase(PluginBase):""" Store interface. An example of implementation is Moin's FSStore located at :keyword:`MoinMoin.storage.stores.fs.FSStore` """@propertydefstore_name(self):""" Store name used in storage URIs. """@abstractmethoddefgetByteStoreFromURI(self,uri):""" :param uri: URI to be passed to ByteStore constructor :returns: MoinMoin.Storages.Store.ByteStore instance """@abstractmethoddefgetFileStoreFromURI(self,uri):""" :param uri: URI to be passed to FileStore constructor :returns: MoinMoin.Storages.Store.FileStore instance """classBlueprintBase(PluginBase):""" Blueprint interface. See :file:`contrib/ExamplePlugin' for an example of how to implement Blueprint plugin. """@propertydefblueprint(self):""" Flask Blueprint which will be registered with the MoinMoin application """returnNone@propertydefblueprint_options(self):""" Main use case is to set url_prefix option, but can be used to setup any other options when the blueprint is registered. """returndict()classAuthBase(PluginBase):""" Auth interface. An example of implementation is Moin's MoinLoginFormAuth located at :keyword:`MoinMoin.auth.MoinLoginFormAuth` """@abstractmethoddefgetAuthInstance(self,**kw):""" Get instance of class implementing MoinMoin.auth.BaseAuth :returns: MoinMoin.auth.BaseAuth instance """defload(plugins,ignore_exceptions=True):""" Imports plugin packages from list of names. :param plugins: list of plugin package names to import :param ignore_exceptions: (optional) true, if you want to ignore exceptions when importing :returns: dict of all loaded module objects """ifpluginsisNone:return{}loaded_modules={}forplugininplugins:try:loaded_modules[plugin]=__import__(plugin)except:ifignore_exceptions:logging.error("Failed to import: {0}".format(plugin))else:raisereturnloaded_modulesdefget_abcs():""" Gets all classes defined in this file. All of those should be abstract base classes for plugins. :returns: list of all classes in this file """members=getmembers(sys.modules[__name__],lambdamember:isclass(member)andmember.__module__==__name__)return[x[1]forxinmembers]definit_all_plugins(plugin_config,ignore_exceptions=True):""" Initializes all PluginBase subclasses. :param plugin_config: dict containing plugin configuration where key is plugin package name :param ignore_exceptions: (optional) true, if you want to ignore exceptions when initializing :returns: dict of all plugin instances """# Find all the subclasses of plugin abstract classes.plugin_classes=[]classes=PluginBase.__subclasses__()abcs=get_abcs()# XXX: This potentialy has problems.# Before we only took leaf classes which means there can't be Plugin class and another class which only# slightly modifies it. Now we allow this situation, but we can't have ABC declared anywhere# else but this file because those ABCs will be interpreted as plugins and will raise exceptions.# (Those can be ignored and should be ignored in non-development environment).whileclasses:cls=classes.pop()subcls=cls.__subclasses__()# If the class is not ABC, it must be a plugin.ifclsnotinabcs:plugin_classes.append(cls)# If the class has subclasses process them as well.ifsubcls:classes+=subcls# Initialize the plugin classes.initialized_plugins={}forclsinplugin_classes:package_name=cls.__module__.split(".",1)[0]cfg=plugin_config.get(package_name,{})class_id="{0}.{1}".format(cls.__module__,cls.__name__)try:initialized_plugins[class_id]=cls(cfg)except:ifignore_exceptions:logging.error("Failed to initialize: {0}".format(class_id))else:raisereturninitialized_pluginsdefget_by_type(plugin_type,initialized_plugins):""" Get all plugins of specific type from the list of initialized plugins. :param plugin_type: plugin type to search for. :param initialized_plugins: list of initialized plugins where to search in :returns: list of all plugin of the given type """clsname=plugin_type.capitalize()+"Base"plugins=[]forpluginininitialized_plugins.values():forbaseinplugin.__class__.__bases__:ifclsname==base.__name__:plugins.append(plugin)returnplugins