1# (C) Copyright 2007-2019 Enthought, Inc., Austin, TX 2# All rights reserved. 3# 4# This software is provided without warranty under the terms of the BSD 5# license included in LICENSE.txt and may be redistributed only 6# under the conditions described in the aforementioned license. The license 7# is also available online at http://www.enthought.com/licenses/BSD.txt 8# Thanks for using Enthought open source! 9""" A simple plugin manager implementation. """ 10 11 12from fnmatch import fnmatch 13import logging 14 15from traits.api import Event, HasTraits, Instance, List, Str, provides 16 17from .i_application import IApplication 18from .i_plugin import IPlugin 19from .i_plugin_manager import IPluginManager 20from .plugin_event import PluginEvent 21 22 23 24logger = logging.getLogger(__name__) 25 26 27@provides(IPluginManager) 28class PluginManager(HasTraits): 29 """ A simple plugin manager implementation. 30 31 This implementation manages an explicit collection of plugin instances, 32 e.g:: 33 34 plugin_manager = PluginManager( 35 plugins = [ 36 MyPlugin(), 37 YourPlugin() 38 ] 39 ) 40 41 Plugins can be added and removed after construction time via the methods 42 'add_plugin' and 'remove_plugin'. 43 44 """ 45 46 #### 'IPluginManager' protocol ############################################# 47 48 # Fired when a plugin has been added to the manager. 49 plugin_added = Event(PluginEvent) 50 51 # Fired when a plugin has been removed from the manager. 52 plugin_removed = Event(PluginEvent) 53 54 #### 'PluginManager' protocol ############################################## 55 56 # The application that the plugin manager is part of. 57 application = Instance(IApplication) 58 def _application_changed(self, trait_name, old, new): 59 """ Static trait change handler. """ 60 61 self._update_plugin_application([], self._plugins) 62 63 return 64 65 # An optional list of the Ids of the plugins that are to be excluded by 66 # the manager. 67 # 68 # Each item in the list is actually an 'fnmatch' expression. 69 exclude = List(Str) 70 71 # An optional list of the Ids of the plugins that are to be included by 72 # the manager (i.e. *only* plugins with Ids in this list will be added to 73 # the manager). 74 # 75 # Each item in the list is actually an 'fnmatch' expression. 76 include = List(Str) 77 78 #### 'object' protocol ##################################################### 79 80 def __init__(self, plugins=None, **traits): 81 """ Constructor. 82 83 We allow the caller to specify an initial list of plugins, but the 84 list itself is not part of the public API. To add and remove plugins 85 after construction, use the 'add_plugin' and 'remove_plugin' methods 86 respectively. The manager is also iterable, so to iterate over the 87 plugins use 'for plugin in plugin_manager'. 88 89 """ 90 91 super(PluginManager, self).__init__(**traits) 92 93 if plugins is not None: 94 self._plugins = plugins 95 96 return 97 98 def __iter__(self): 99 """ Return an iterator over the manager's plugins. """ 100 101 plugins = [ 102 plugin for plugin in self._plugins 103 104 if self._include_plugin(plugin.id) 105 ] 106 107 return iter(plugins) 108 109 #### 'IPluginManager' protocol ############################################# 110 111 def add_plugin(self, plugin): 112 """ Add a plugin to the manager. """ 113 114 self._plugins.append(plugin) 115 self.plugin_added = PluginEvent(plugin=plugin) 116 117 return 118 119 def get_plugin(self, plugin_id): 120 """ Return the plugin with the specified Id. """ 121 122 for plugin in self._plugins: 123 if plugin_id == plugin.id: 124 if not self._include_plugin(plugin.id): 125 plugin = None 126 127 break 128 129 else: 130 plugin = None 131 132 return plugin 133 134 def remove_plugin(self, plugin): 135 """ Remove a plugin from the manager. """ 136 137 self._plugins.remove(plugin) 138 self.plugin_removed = PluginEvent(plugin=plugin) 139 140 return 141 142 def start(self): 143 """ Start the plugin manager. """ 144 145 for plugin in self._plugins: 146 self.start_plugin(plugin) 147 148 return 149 150 def start_plugin(self, plugin=None, plugin_id=None): 151 """ Start the specified plugin. """ 152 153 plugin = plugin or self.get_plugin(plugin_id) 154 if plugin is not None: 155 logger.debug('plugin %s starting', plugin.id) 156 plugin.activator.start_plugin(plugin) 157 logger.debug('plugin %s started', plugin.id) 158 159 else: 160 raise SystemError('no such plugin %s' % plugin_id) 161 162 return 163 164 def stop(self): 165 """ Stop the plugin manager. """ 166 167 # We stop the plugins in the reverse order that they were started. 168 stop_order = self._plugins[:] 169 stop_order.reverse() 170 171 for plugin in stop_order: 172 self.stop_plugin(plugin) 173 174 return 175 176 def stop_plugin(self, plugin=None, plugin_id=None): 177 """ Stop the specified plugin. """ 178 179 plugin = plugin or self.get_plugin(plugin_id) 180 if plugin is not None: 181 logger.debug('plugin %s stopping', plugin.id) 182 plugin.activator.stop_plugin(plugin) 183 logger.debug('plugin %s stopped', plugin.id) 184 185 else: 186 raise SystemError('no such plugin %s' % plugin_id) 187 188 return 189 190 #### Protected 'PluginManager' ############################################# 191 192 # The plugins that the manager manages! 193 _plugins = List(IPlugin) 194 def __plugins_changed(self, trait_name, old, new): 195 """ Static trait change handler. """ 196 197 self._update_plugin_application(old, new) 198 199 return 200 201 def __plugins_items_changed(self, trait_name, old, new): 202 """ Static trait change handler. """ 203 204 self._update_plugin_application(new.removed, new.added) 205 206 return 207 208 def _include_plugin(self, plugin_id): 209 """ Return True if the plugin should be included. 210 211 This is just shorthand for:- 212 213 if self._is_included(plugin_id) and not self._is_excluded(plugin_id): 214 ... 215 216 """ 217 218 return self._is_included(plugin_id) and not self._is_excluded(plugin_id) 219 220 #### Private protocol ###################################################### 221 222 def _is_excluded(self, plugin_id): 223 """ Return True if the plugin Id is excluded. 224 225 If no 'exclude' patterns are specified then this method returns False 226 for all plugin Ids. 227 228 """ 229 230 if len(self.exclude) == 0: 231 return False 232 233 for pattern in self.exclude: 234 if fnmatch(plugin_id, pattern): 235 return True 236 237 return False 238 239 def _is_included(self, plugin_id): 240 """ Return True if the plugin Id is included. 241 242 If no 'include' patterns are specified then this method returns True 243 for all plugin Ids. 244 245 """ 246 247 if len(self.include) == 0: 248 return True 249 250 for pattern in self.include: 251 if fnmatch(plugin_id, pattern): 252 return True 253 254 return False 255 256 def _update_plugin_application(self, removed, added): 257 """ Update the 'application' trait of plugins added/removed. """ 258 259 for plugin in removed: 260 plugin.application = None 261 262 for plugin in added: 263 plugin.application = self.application 264 265 return 266 267#### EOF ###################################################################### 268